File size: 7,915 Bytes
fe0e2a2
 
 
 
 
 
 
 
 
 
 
2c4dee7
fe0e2a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2148855
fe0e2a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
from __future__ import annotations

from pathlib import Path
from typing import Optional

import os
import yaml
from fastapi import FastAPI, HTTPException, APIRouter, UploadFile, File, Query
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field

from refinement.multiagent_refinement import (
    execute_refinement,
    execute_refinement_for_video,
    _load_refinement_flags,
)



# --- Config y autenticación sencilla por token ---

ROOT = Path(__file__).resolve().parent
CONFIG_PATH = "config.yaml"
router = APIRouter(prefix="/refinement", tags=["Refinement Process"])


def _load_engine_token() -> Optional[str]:
    """Carga el token compartido del engine desde config.yaml o variables de entorno.

    Sigue la misma convención que engine/api.py: usa API_SHARED_TOKEN si está
    definido por entorno; en caso contrario, intenta leer demo/config.yaml.
    """

    env_token = os.getenv("API_SHARED_TOKEN")
    if env_token:
        return env_token

    # Fallback: leer demo/config.yaml desde la raíz del repo
    try:
        repo_root = ROOT
        # Intentar detectar la carpeta demo en el mismo repo
        demo_cfg = repo_root / "demo" / "config.yaml"
        if demo_cfg.exists():
            with demo_cfg.open("r", encoding="utf-8") as f:
                cfg = yaml.safe_load(f) or {}
            api_cfg = cfg.get("api", {}) or {}
            token = api_cfg.get("token")
            if token and isinstance(token, str):
                # Cuando viene de YAML con "${API_SHARED_TOKEN}" puede no estar
                # resuelto; en ese caso preferimos None para forzar uso de entorno.
                if "${" in token and "}" in token:
                    return None
                return token
    except Exception:
        pass

    return None


ENGINE_TOKEN = _load_engine_token()


def _assert_valid_token(token: str | None) -> None:
    expected = ENGINE_TOKEN
    if not expected:
        # Si no hay token configurado, consideramos que la auth está desactivada
        return
    if not token or token != expected:
        raise HTTPException(status_code=401, detail="Invalid or missing engine token")


# --- Esquemas de entrada/salida ---


class ApplyRefinementRequest(BaseModel):
    token: Optional[str] = Field(None, description="Engine shared token")
    srt_content: Optional[str] = Field(
        None,
        description=(
            "Contenido del SRT a refinar. Opcional si se proporciona sha1sum+version, "
            "en cuyo caso se leerá el SRT desde audiodescriptions.db."
        ),
    )
    sha1sum: Optional[str] = Field(
        None,
        description=(
            "Identificador sha1sum del vídeo. Si se proporciona junto con version, "
            "se utilizará el pipeline basat en BDs (audiodescriptions.db, casting.db, scenarios.db)."
        ),
    )
    version: Optional[str] = Field(
        None,
        description=(
            "Versió de l'audiodescripció (p.ex. 'MoE', 'Salamandra', 'HITL'). "
            "Necessària si s'especifica sha1sum per utilitzar el pipeline de vídeo."
        ),
    )
    reflection_enabled: bool = Field(True, description="Activar paso de reflection")
    reflexion_enabled: bool = Field(False, description="Activar paso de reflexion")
    introspection_enabled: bool = Field(False, description="Activar paso de introspection")


class ApplyRefinementResponse(BaseModel):
    refined_srt: str


class TrainMultiagentRefinementRequest(BaseModel):
    audiodescriptions_db_path: str = Field(..., description="Ruta a la base de datos tipo audiodescriptions.db")
    videos_db_path: str = Field(..., description="Ruta a la base de datos tipo videos.db")
    casting_db_path: str = Field(..., description="Ruta a la base de datos tipo casting.db")
    scenarios_db_path: str = Field(..., description="Ruta a la base de datos tipo scenarios.db")
    system_to_train: str = Field(..., pattern="^(reflexion|introspection)$", description="Sistema a entrenar: 'reflexion' o 'introspection'")


class TrainMultiagentRefinementResponse(BaseModel):
    ok: bool
    detail: str


# --- Endpoints ---


@router.post("/apply_refinement", tags=["Refinement Process"], response_model=ApplyRefinementResponse)
def apply_refinement(payload: ApplyRefinementRequest) -> ApplyRefinementResponse:
    """Aplica el pipeline multi‑agente de refinamiento sobre un SRT.

    - Valida el token del engine.
    - Aplica los pasos reflection/reflexion/introspection según los flags
      recibidos en la petición.
    - Devuelve el SRT refinado.
    """

    _assert_valid_token(payload.token)

    # Partimos de los flags por defecto de config.yaml y los sobreescribimos con
    # los que llegan en la petición para este job concreto.
    flags = _load_refinement_flags()
    flags["reflection_enabled"] = bool(payload.reflection_enabled)
    flags["reflexion_enabled"] = bool(payload.reflexion_enabled)
    flags["introspection_enabled"] = bool(payload.introspection_enabled)

    # Ejecutar el pipeline con los flags actuales. Como execute_refinement y
    # execute_refinement_for_video actualmente solo leen flags desde
    # config.yaml, para no romper sus firmas guardamos temporalmente una copia
    # de demo/config.yaml con los flags ajustados para esta llamada.
    # NOTA: esta implementación asume ús en contextos de un sol procés.

    # Localizar demo/config.yaml en la raíz del repo
    repo_root = ROOT
    demo_cfg = repo_root / "demo" / "config.yaml"
    if not demo_cfg.exists():
        raise HTTPException(status_code=500, detail="demo/config.yaml not found")

    original_yaml = demo_cfg.read_text(encoding="utf-8")
    try:
        cfg = yaml.safe_load(original_yaml) or {}
        ref_cfg = cfg.get("refinement", {}) or {}
        ref_cfg["reflection_enabled"] = flags["reflection_enabled"]
        ref_cfg["reflexion_enabled"] = flags["reflexion_enabled"]
        ref_cfg["introspection_enabled"] = flags["introspection_enabled"]
        cfg["refinement"] = ref_cfg
        demo_cfg.write_text(yaml.safe_dump(cfg, allow_unicode=True), encoding="utf-8")

        # Decidir el flux segons si tenim sha1sum+version o bé un SRT pla
        if payload.sha1sum and payload.version:
            refined = execute_refinement_for_video(
                payload.sha1sum,
                payload.version,
                config_path=demo_cfg,
            )
        else:
            if not payload.srt_content:
                raise HTTPException(
                    status_code=400,
                    detail=(
                        "Cal proporcionar o bé sha1sum+version, o bé srt_content "
                        "per poder aplicar el refinament."
                    ),
                )
            refined = execute_refinement(payload.srt_content, config_path=demo_cfg)
    finally:
        # Restaurar el YAML original para no afectar a otras llamadas
        demo_cfg.write_text(original_yaml, encoding="utf-8")

    return ApplyRefinementResponse(refined_srt=refined)


@router.post("/train_multiagent_refinement", tags=["Refinement Process"], response_model=TrainMultiagentRefinementResponse)
def train_multiagent_refinement(payload: TrainMultiagentRefinementRequest) -> TrainMultiagentRefinementResponse:
    """Endpoint placeholder para entrenar els sistemes de reflexion / introspection.

    De moment no implementa cap lògica; simplement valida la càrrega i retorna
    un missatge indicant que és un stub.
    """

    # Aquí en el futur es podrà afegir la lògica d'entrenament que utilitzi
    # les bases de dades proporcionades i el flag system_to_train.

    return TrainMultiagentRefinementResponse(
        ok=True,
        detail=(
            "train_multiagent_refinement està definit com a stub; encara no s'ha "
            "implementat la lògica d'entrenament per als sistemes 'reflexion' o 'introspection'."
        ),
    )