Casos de Prueba
Formato esperado (CSV o Excel):
| audiencia | pregunta |
|---|---|
| optico | ¿Cuánto cuesta el ALTEA? |
| cliente | ¿Dónde puedo comprar? |
Generar casos con IA
Carga desde la BD, o sube
un CSV/Excel con tus preguntas Sin resultados para el filtro actual
un CSV/Excel con tus preguntas Sin resultados para el filtro actual
Configuración Bot
Sesiones anteriores
Resumen de conversación
turno(s)
slot(s)
contexto activo
Identidad óptico:
·
·
Turno /
Sesión finalizada · turnos
Configura y haz clic en "Iniciar Sesión Bot"
Resumen de la sesión
Turnos
Promedio
Min–Max
:
Outcomes
Turnos con puntuación baja:
Último mensaje
Bot:
MAS:
esperando respuesta…
Pipeline Debug
Branch:
Decision:
Código:
Nombre:
Candidatos:
Mejor prob:
Umbral:
Email usuario:
Email candidato:
Fuentes:
Catálogo BD ()
QA indexado ()
Web indexado ()
ninguna
Confianza:
≥70%
40–69%
<40%
Branch:
Decision:
Código:
Nombre:
Candidatos:
Mejor prob:
Umbral:
Email usuario:
Email candidato:
Fuentes:
Catálogo BD ()
QA indexado ()
Web indexado ()
ninguna
Confianza:
≥70%
40–69%
<40%
Variaciones de la pregunta
Pregunta original:
Haz clic en una variación para cargarla:
Revisar conversación
Persona
Conversation ID
Estado
Turnos
Bot (usuario)
enmendada
enmendada
MAS (asistente)
Pipeline Debug
Verificación
Paquete diagnóstico
AG3 — Scores y Confianza (explicación detallada)
Pipeline Logs
No warning or error logs for this turn.
Diagrama del Turno
Rendering...
Exportar enmiendas
Cargando enmiendas...
No hay enmiendas registradas
Usa los iconos de edición en el panel Revisar para enmendar preguntas o respuestas
pregunta
respuesta
Pregunta
Respuesta
Eliminar sesión
¿Estás seguro de que quieres eliminar esta sesión?
La sesión se marcará como eliminada y dejará de aparecer en las listas.
Estado del Servicio
Retrieval Agent
Haz clic en "Actualizar" para verificar el estado de los servicios
n8n Webhook Tester
Respuesta:
Arquitectura del Sistema — Fase 2d
%%{init: {"theme": "dark"}}%%
flowchart TB
subgraph ENTRADA["Canal de Entrada"]
U["Usuario Humano\nOptico o Cliente Final"]
BOT["Usuario Bot (MAS Test)\nLLM genera preguntas · Evalúa respuestas\nCatálogo MCP · MongoDB"]
API["POST /v1/mas/turn\nconversation_id · user_message · slots_previos · historial"]
end
subgraph MAS["Pipeline MAS — Fase 2d (AG1→Verif→AG2→AG4→AG3→AG5)"]
direction TB
A1["AG1 — Router y Safety\nLLM + historial_conversacion\nAudiencia · Intencion + intenciones_secundarias · Riesgo · next_step\nconsulta_contextualizada (reformulacion)"]
VERIF["Verificación Óptico\nExact match → Fuzzy + email tiebreaker\n_pending_query → Re-run AG1 post-verificacion"]
A2["AG2 — Policy y Governance\nLLM\npermitir_respuesta · fuente_permitida\npermitir_accion · requiere_confirmacion"]
A4["AG4 — State y Slots\nLLM con fast-path sin accion + historial\nSlots conocidos · Preguntas siguientes\nEjecuta ANTES de AG3"]
A3["AG3 — Retrieval y Evidence\nPython + LLM Planner (3 fases paralelas)\nA: Milvus QA+Docs | Rerank opcional (Jina) | B: LLM plan + MCP Catalog | C: Score\nhits_qa · hits_evidencias · hits_catalogo · datos_en_vivo"]
A5["AG5 — Orquestador\nLLM\nOutcome A B C D · Mensaje final\nPreferencia: hits_catalogo > hits_qa > hits_evidencias"]
end
subgraph OUTCOMES["Outcomes del Orquestador (AG5)"]
OA["A: responder_ahora\nConfianza alta, sin accion pendiente"]
OB["B: hacer_preguntas\nConfianza baja o slots faltantes"]
OC["C: preparar_confirmacion\nResumen listo — esperar CONFIRMAR"]
OD["D: ejecutar_accion\nToken CONFIRMAR valido y vigente\n(crear_pedido · ver_estado_pedido · email · cita)"]
end
subgraph DATOS["Fuentes de Datos"]
MQ["Milvus — QA Opticos/Clientes\nrespuesta_corta · respuesta_larga · metadata"]
MD["Milvus — Page Chunks + Products\nFragmentos web · pagina_seccion"]
MCP["mcp-catalog :8003\nMCP Streamable HTTP\nHerramientas PostgreSQL en vivo\nPrecios · Specs · Repuestos · Modelos"]
PG["PostgreSQL Catalogo\nv_precios_plano · v_precios_graduado\nv_catalogo_completo · repuestos · dip_graduar"]
RD["Redis\nchat_model · ag3_catalog_model · qa_confidence_threshold\nreranker_enabled · reranker_model · reranker_top_n"]
JINA["Jina Reranker API (opcional)\njina-reranker-v2-base-multilingual\nRequiere JINA_API_KEY"]
YML["Config YAML\nmas-config.yaml · policies/rules.yaml"]
MONGO["MongoDB — Bot Sessions\nbot_sessions collection\nTurnos · Evaluaciones · Enmiendas"]
end
U --> API
BOT --> API
BOT -.->|"gather_catalog_knowledge"| MCP
BOT -.->|"save/load sessions"| MONGO
API --> A1
A1 -->|"next_step=aclarar_audiencia EARLY EXIT"| OB
A1 -->|"opticos sin verificar"| VERIF
A1 -->|"next_step=proceder (verificado o cliente)"| A2
VERIF -->|"verificado: re-run AG1 con _pending_query"| A2
VERIF -->|"pendiente / escalado EARLY EXIT"| OB
A2 -->|"permitir_respuesta=false EARLY EXIT"| OA
A2 --> A4
A4 -->|"merged_slots"| A3
A3 --> A5
A5 --> OA
A5 --> OB
A5 --> OC
A5 --> OD
A3 -.->|"Phase A: search_qa_* + search_docs"| MQ
A3 -.->|"Phase A: page_chunks + products"| MD
A3 -.->|"Phase B: list_tools → LLM plan → MCP call_tool"| MCP
MCP -.->|"SELECT … read-only"| PG
VERIF -.->|"verify_optico_tool + search_candidates"| MCP
A1 -.->|"get_mas_chat_model"| RD
A3 -.->|"get_mas_ag3_catalog_model + reranker config"| RD
A3 -.->|"rerank QA + docs (si habilitado)"| JINA
A1 -.->|"intents · risk_levels"| YML
A2 -.->|"policy rules"| YML
style ENTRADA fill:#1e1040,stroke:#a855f7,color:#e4e8f0
style MAS fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
style OUTCOMES fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style DATOS fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style A3 fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style MCP fill:#1a0f2e,stroke:#a855f7,color:#e4e8f0
style VERIF fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style A5 fill:#1a2e1a,stroke:#22c55e,color:#e4e8f0
style OA fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style OB fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style OC fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style OD fill:#1a2e1a,stroke:#22c55e,color:#e4e8f0
style PG fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style BOT fill:#1a0f2e,stroke:#ec4899,color:#e4e8f0
style JINA fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style MONGO fill:#1a0f2e,stroke:#ec4899,color:#e4e8f0
Flujo de Decisión por Turno
%%{init: {"theme": "dark"}}%%
flowchart TD
BOT_SOURCE{"Fuente del\nmensaje?"}
HUMAN_USER["Usuario humano\nEscribe pregunta"]
BOT_USER["Usuario Bot\nLLM genera pregunta\nCatálogo MCP · Persona"]
START["Mensaje entrante\nPOST /v1/mas/turn\n+ historial_conversacion + slots_previos"]
BOT_SOURCE -->|"humano"| HUMAN_USER
BOT_SOURCE -->|"bot"| BOT_USER
HUMAN_USER --> START
BOT_USER --> START
BOT_EVAL["Bot: Evalúa respuesta\nScore 0-100 · next_action\nMongoDB persistence"]
AG1_NODE["AG1: Router y Safety\nLLM + historial — Clasifica audiencia · intencion + intenciones_secundarias · riesgo\nProduce consulta_contextualizada para AG3"]
AUD_OVERRIDE{"Audience override\n_optico_verificado o\n_audiencia en slots?"}
NEXT_STEP{siguiente_paso}
CLARIFY["EARLY EXIT\nOutcome B: hacer_preguntas\nPreguntar audiencia al usuario"]
OPTICO_GATE{"optico sin\nverificar?"}
VERIFY_PENDING{"verificacion\npendiente?"}
ASK_CREDS["EARLY EXIT\nOutcome B: hacer_preguntas\nPedir nombre_comercial + codigo\nGuarda _pending_query en slots"]
AG4_VERIFY["AG4: Extrae\n_optico_codigo +\n_optico_nombre_comercial"]
MCP_VERIFY["MCP: verify_optico_tool\nExact match en DB"]
VERIFY_OK{"match?"}
RERUN_AG1["Re-run AG1\ncon _pending_query\n(pregunta original guardada)\nForza audiencia=opticos\nsiguiente_paso=proceder"]
EMAIL_PENDING{"email\npendiente?"}
AG4_EMAIL["AG4: Extrae\n_optico_email"]
EMAIL_EXTRACTED{"email\nextraído?"}
EMAIL_CHECK{"email\nmatch?"}
CRED_REENTRY{"credenciales\nextraídas?"}
MCP_VERIFY_REENTRY["MCP: verify_optico_tool\n(re-entry con nuevas creds)"]
REENTRY_OK{"match?"}
FUZZY_CHECK{"candidato\nfuzzy >= umbral?"}
ASK_EMAIL["EARLY EXIT\nOutcome B: hacer_preguntas\nPedir email para verificar"]
FALLBACK_EXTRACT["Fallback: regex en\nconsulta_contextualizada\nExtraer codigo + nombre"]
FALLBACK_FOUND{"extrajo\ncredenciales?"}
MCP_VERIFY_FALLBACK["MCP: verify_optico_tool\n(fallback creds)"]
FALLBACK_OK{"match?"}
ATTEMPT_CHECK{"intentos\n< max?"}
RETRY_CREDS["EARLY EXIT\nOutcome B: hacer_preguntas\nPedir datos de nuevo"]
ESCALATE["EARLY EXIT\nescalar a humano"]
AG2_NODE["AG2: Policy y Governance\nLLM + validacion Python — Evalua politica · define fuente · gates"]
POLICY_GATE{permitir_respuesta}
BLOCKED_NODE["EARLY EXIT\nOutcome A: responder_ahora\nRespuesta de bloqueo sin informacion"]
AG4_NODE["AG4: State y Slots\nLLM + historial — fast-path si accion=ninguna\nEjecuta ANTES de AG3 — produce merged_slots"]
AG3_NODE["AG3: Retrieval y Evidence\nPython + LLM Planner\nFase A y B: paralelo via asyncio.gather\nA: Milvus QA + Docs | Rerank opcional (Jina) | B: LLM plan + MCP catalog\nFase C: merge + confianza intent-aware (multi-intent)\nhits_qa · hits_evidencias · hits_catalogo · datos_en_vivo"]
AG5_NODE["AG5: Orquestador\nLLM — Decide outcome · compone mensaje final\nPreferencia: hits_catalogo > hits_qa > docs"]
OA_N["Outcome A\nresponder_ahora\nConfianza alta, sin accion"]
OB_N["Outcome B\nhacer_preguntas\nFaltan slots o confianza baja"]
OC_N["Outcome C\npreparar_confirmacion\nResumen lista — pedir CONFIRMAR\n(crear_pedido · ver_estado_pedido · email · cita)"]
OD_N["Outcome D\nejecutar_accion\nToken CONFIRMAR valido\n(crear_pedido · ver_estado_pedido · email · cita)"]
RESP["MASTurnResponse\nreply_text · outcome · datos_en_vivo\nslots_actualizados · debug"]
START --> AG1_NODE
AG1_NODE --> AUD_OVERRIDE
AUD_OVERRIDE -->|"si: forzar audiencia"| NEXT_STEP
AUD_OVERRIDE -->|"no"| NEXT_STEP
NEXT_STEP -->|"aclarar_audiencia"| CLARIFY
NEXT_STEP -->|"proceder"| OPTICO_GATE
CLARIFY --> RESP
OPTICO_GATE -->|"si"| EMAIL_PENDING
OPTICO_GATE -->|"no"| AG2_NODE
EMAIL_PENDING -->|"si"| AG4_EMAIL
EMAIL_PENDING -->|"no"| VERIFY_PENDING
AG4_EMAIL --> EMAIL_EXTRACTED
EMAIL_EXTRACTED -->|"si"| EMAIL_CHECK
EMAIL_EXTRACTED -->|"no"| CRED_REENTRY
EMAIL_CHECK -->|"si"| RERUN_AG1
EMAIL_CHECK -->|"no"| ATTEMPT_CHECK
CRED_REENTRY -->|"si: usuario reintrodujo creds"| MCP_VERIFY_REENTRY
CRED_REENTRY -->|"no: pedir email de nuevo"| RESP
MCP_VERIFY_REENTRY --> REENTRY_OK
REENTRY_OK -->|"si"| RERUN_AG1
REENTRY_OK -->|"no"| ATTEMPT_CHECK
VERIFY_PENDING -->|"no: primera vez"| ASK_CREDS
VERIFY_PENDING -->|"si"| AG4_VERIFY
ASK_CREDS --> RESP
AG4_VERIFY --> MCP_VERIFY
MCP_VERIFY --> VERIFY_OK
VERIFY_OK -->|"si"| RERUN_AG1
VERIFY_OK -->|"no"| FUZZY_CHECK
AG4_VERIFY -.->|"creds incompletas"| FALLBACK_EXTRACT
FALLBACK_EXTRACT --> FALLBACK_FOUND
FALLBACK_FOUND -->|"si"| MCP_VERIFY_FALLBACK
FALLBACK_FOUND -->|"no: pedir de nuevo"| RESP
MCP_VERIFY_FALLBACK --> FALLBACK_OK
FALLBACK_OK -->|"si"| RERUN_AG1
FALLBACK_OK -->|"no"| FUZZY_CHECK
FUZZY_CHECK -->|"si: prob >= umbral"| ASK_EMAIL
FUZZY_CHECK -->|"no"| ATTEMPT_CHECK
ASK_EMAIL --> RESP
ATTEMPT_CHECK -->|"si"| RETRY_CREDS
ATTEMPT_CHECK -->|"no: max alcanzado"| ESCALATE
RETRY_CREDS --> RESP
ESCALATE --> RESP
RERUN_AG1 --> AG2_NODE
AG2_NODE --> POLICY_GATE
POLICY_GATE -->|"false"| BLOCKED_NODE
POLICY_GATE -->|"true"| AG4_NODE
BLOCKED_NODE --> RESP
AG4_NODE -->|"merged_slots"| AG3_NODE
AG3_NODE --> AG5_NODE
AG5_NODE --> OA_N
AG5_NODE --> OB_N
AG5_NODE --> OC_N
AG5_NODE --> OD_N
OA_N --> RESP
OB_N --> RESP
OC_N --> RESP
OD_N --> RESP
RESP -.->|"si fuente=bot"| BOT_EVAL
style BOT_SOURCE fill:#1e1040,stroke:#ec4899,color:#e4e8f0
style HUMAN_USER fill:#1e1040,stroke:#a855f7,color:#e4e8f0
style BOT_USER fill:#1a0f2e,stroke:#ec4899,color:#e4e8f0
style BOT_EVAL fill:#1a0f2e,stroke:#ec4899,color:#e4e8f0
style START fill:#1e1040,stroke:#a855f7,color:#e4e8f0
style RESP fill:#1e1040,stroke:#a855f7,color:#e4e8f0
style CLARIFY fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style BLOCKED_NODE fill:#2e1414,stroke:#ef4444,color:#e4e8f0
style AG4_NODE fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style AG3_NODE fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style AG5_NODE fill:#1a2e1a,stroke:#22c55e,color:#e4e8f0
style OA_N fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style OB_N fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style OC_N fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style OD_N fill:#1a2e1a,stroke:#22c55e,color:#e4e8f0
style AUD_OVERRIDE fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style OPTICO_GATE fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style VERIFY_PENDING fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style ASK_CREDS fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style AG4_VERIFY fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style MCP_VERIFY fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style VERIFY_OK fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style RERUN_AG1 fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
style ATTEMPT_CHECK fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style RETRY_CREDS fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style EMAIL_PENDING fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style AG4_EMAIL fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style EMAIL_EXTRACTED fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style EMAIL_CHECK fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style CRED_REENTRY fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style MCP_VERIFY_REENTRY fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style REENTRY_OK fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style FUZZY_CHECK fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style ASK_EMAIL fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style FALLBACK_EXTRACT fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style FALLBACK_FOUND fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style MCP_VERIFY_FALLBACK fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style FALLBACK_OK fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style ESCALATE fill:#2e1414,stroke:#ef4444,color:#e4e8f0
style AG1_NODE fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
style AG2_NODE fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
Secuencia de un Turno Completo
%%{init: {"theme": "dark"}}%%
sequenceDiagram
participant U as Usuario (Optico)
participant API as runner.py
participant AG1 as AG1 Router
participant AG2 as AG2 Policy
participant AG4 as AG4 Slots
participant AG3 as AG3 Evidence
participant MIL as Milvus
participant JINA as Jina Reranker
participant MCP as mcp-catalog :8003
participant AG5 as AG5 Orquestador
U->>API: POST user_message="¿Cuánto cuesta el ALTEA en C1?"
API->>AG1: run_ag1(user_message, chat_model, historial)
Note over AG1: LLM clasifica:
audiencia=opticos
intencion=precio
intenciones_secundarias=[envio]
next_step=proceder
consulta_contextualizada=null (autoexplicativo)
AG1-->>API: RouterOutput
Note over API: Verification gate:
audiencia=opticos, not verified?
alt first time opticos — ask credentials
Note over API: _verificacion_pendiente = true
_pending_query = user_message
(guarda pregunta original)
API-->>U: "¿Tu nombre comercial y código?"
U->>API: "Soy OPTICA TYNDALL, código 00354"
API->>AG1: run_ag1(credential message, chat_model, historial)
AG1-->>API: RouterOutput (intencion=otros, audiencia=opticos)
API->>AG4: extract _optico_codigo + _optico_nombre_comercial
AG4-->>API: SlotOutput {codigo: "00354", nombre: "OPTICA TYNDALL"}
API->>MCP: verify_optico_tool(codigo="00354", nombre="OPTICA TYNDALL")
alt exact match verified
MCP-->>API: {verified: true, optico_id: 42}
Note over API: _optico_verificado = true
Pop _pending_query → re-run AG1
API->>AG1: run_ag1(_pending_query="¿Cuánto cuesta el ALTEA en C1?", chat_model, [])
Note over AG1: LLM clasifica correctamente:
audiencia=opticos, intencion=precio
consulta_contextualizada incluye ALTEA C1
AG1-->>API: RouterOutput (forzado: audiencia=opticos, proceder)
Note over API: Fall through to normal pipeline
else no exact match — fuzzy
MCP-->>API: {verified: false}
API->>MCP: search_optico_candidates_tool(codigo, nombre)
MCP-->>API: candidates[]
Note over API: fuzzy scoring:
best_prob vs threshold
alt fuzzy candidate found (prob >= threshold)
API-->>U: "¿Me puedes indicar tu email?"
U->>API: "mi@email.com"
API->>AG4: extract _optico_email
AG4-->>API: SlotOutput {email: "mi@email.com"}
Note over API: compare UPPER(email)
vs candidate mail
alt email matches
Note over API: _optico_verificado = true
Pop _pending_query → re-run AG1
API->>AG1: run_ag1(_pending_query, chat_model, [])
AG1-->>API: RouterOutput (forzado: audiencia=opticos, proceder)
else email mismatch
API-->>U: retry or escalate
end
else no fuzzy candidate
API-->>U: retry or escalate
end
end
else already verified or cliente
Note over API: skip gate
end
API->>AG2: run_ag2(RouterOutput, chat_model)
Note over AG2: LLM evalua politica:
permitir=true
fuente=qa_y_evidencias
requiere_confirmacion=false
AG2-->>API: PolicyOutput
API->>AG4: run_ag4(mensaje, RouterOutput, slots_previos, chat_model, historial)
Note over AG4: fast-path: accion=ninguna
Extrae slots del mensaje
modelo_nombre="ALTEA", color="C1"
AG4-->>API: SlotOutput (merged_slots={modelo_nombre: ALTEA, color: C1})
API->>AG3: run_ag3(search_query, audiencia, fuente, chat_model, slots=merged_slots, intent=precio, secondary=[envio])
Note over AG3: Fase A y B: paralelo via asyncio.gather
par Fase A — Milvus QA search
AG3->>MIL: search_qa_opticos("precio ALTEA")
MIL-->>AG3: hits_qa (score L2 ~0.3)
opt reranker_enabled=true
AG3->>JINA: rerank_hits(query, hits_qa+chunks+products)
JINA-->>AG3: reranked hits (top_n por fuente)
end
and Fase B — LLM plan + MCP catalog
AG3->>MCP: list_tools() → tool discovery
MCP-->>AG3: 8 herramientas disponibles
Note over AG3: LLM planner (ag3_catalog_model):
CatalogPlan → get_product_price + get_graduation_price
AG3->>MCP: call_tool("get_product_price", {modelo: "ALTEA"})
MCP-->>AG3: [{modelo:ALTEA, pvo:45.0, pvp:89.0, colores:[C1,C3]}]
end
Note over AG3: Fase C: merge + _compute_confidence
hits_catalogo=[CatalogHit(...)]
confianza=0.95, datos_en_vivo=true
AG3-->>API: EvidenceOutput (hits_catalogo con precio real)
API->>AG5: run_ag5(mensaje, AG1, AG2, AG3, AG4, chat_model)
Note over AG5: LLM decide:
hits_catalogo con datos_en_vivo
= Outcome A, usar pvo=45€ exacto
AG5-->>API: OrchestratorOutput (outcome=responder_ahora)
API-->>U: MASTurnResponse
reply_text="El ALTEA en C1 tiene un precio de 45,00€ para ópticos."
outcome="responder_ahora"
datos_en_vivo=true
Máquina de Estados — Outcomes
%%{init: {"theme": "dark"}}%%
stateDiagram-v2
[*] --> Evaluando : Nuevo turno recibido
Evaluando --> EarlyExit_Audiencia : AG1 next_step=aclarar_audiencia
Evaluando --> EarlyExit_Verificacion : optico sin verificar
Evaluando --> EarlyExit_Politica : AG2 permitir_respuesta=false
Evaluando --> Orquestando : AG3 + AG4 completados
EarlyExit_Audiencia --> OutcomeB : Audiencia desconocida
EarlyExit_Verificacion --> OutcomeB : Pedir credenciales (primera vez)\no pedir email (fuzzy match)\no reintentar (datos incorrectos)
EarlyExit_Verificacion --> OutcomeD : Escalar a humano (max intentos)
EarlyExit_Verificacion --> PostVerificacion : Verificacion exitosa\n(exact match / email tiebreaker / re-entry / fallback)
PostVerificacion --> Orquestando : Re-run AG1 con _pending_query\n→ AG2 → AG4 → AG3 → AG5
EarlyExit_Politica --> OutcomeA : Bloqueo de politica
Orquestando --> OutcomeA : confianza alta AND sin accion solicitada
Orquestando --> OutcomeB : confianza baja OR slots faltantes
Orquestando --> OutcomeC : accion permitida AND slots completos AND sin token previo\n(crear_pedido · ver_estado_pedido · email · cita)
Orquestando --> OutcomeD : token CONFIRMAR valido AND TTL vigente\n(crear_pedido · ver_estado_pedido · email · cita)
OutcomeA --> [*] : responder_ahora
OutcomeB --> [*] : hacer_preguntas
OutcomeC --> [*] : preparar_confirmacion
OutcomeD --> [*] : ejecutar_accion
Retrieval y Evidencia (AG3 + Catálogo)
%%{init: {"theme": "dark"}}%%
flowchart TD
subgraph INPUT["Entradas a AG3 (LLM Planner + Python Executor)"]
I1["consulta_contextualizada o user_message\n(AG1 reformula respuestas cortas)"]
I2["fuente_permitida\nde AG2 Policy\nsolo_qa | qa_y_evidencias | solo_evidencias"]
I3["audiencia\nde AG1 Router"]
I4["merged_slots\nde AG4 (slots_previos + slots_conocidos)\nmodelo_nombre · color · tipo_graduacion …"]
I5["intent + secondary_intents\nde AG1 Router\nprimaria + hasta 2 secundarias"]
I6["ag3_catalog_model\nde Redis/Config\n(independiente de chat_model)"]
end
GATHER["asyncio.gather\nFase A y B en paralelo"]
subgraph PHASE_A["Fase A — Milvus (Python asyncio.gather)"]
direction LR
QA["search_qa_opticos / search_qa_clientes\nBase Q&A vectorial"]
PC["search_page_chunks + search_products\nFragmentos web"]
end
subgraph PHASE_B["Fase B — Catálogo MCP (LLM Planner + Python Executor)"]
direction TB
DISCOVER["_discover_tools(mcp_server)\nMCP list_tools() — obtiene esquemas\nAG3 customer-agnostic"]
LLM_PLAN["_plan_catalog_calls_llm()\nSingle LLM structured-output call\nModelo: ag3_catalog_model\nOutput: CatalogPlan (list of tool calls)"]
FALLBACK["_plan_catalog_calls_deterministic()\nFallback si LLM falla\n_INTENT_TOOL_MAP[intent] + slots"]
subgraph MCP_TOOLS["MCP call_tool() — mcp-catalog:8003\nasyncio.gather concurrente"]
CP["get_product_price\nget_graduation_price"]
CS["get_product_specs\nlist_model_colors"]
CR["get_spare_parts\nget_minimum_diopter"]
CD["search_models\nlist_sports"]
end
end
subgraph RERANK["Reranking opcional — Jina Reranker (Python asyncio.gather)"]
direction LR
RR_GATE{"mas.reranker_enabled?"}
RR_QA["rerank_hits(qa)\nJina API concurrente"]
RR_CHUNKS["rerank_hits(chunks)"]
RR_PRODUCTS["rerank_hits(products)"]
RR_SKIP["Sin rerank\nhits originales pasan directo"]
end
subgraph PHASE_C["Fase C — Merge y Scoring intent-aware (Python)"]
SC1["hits_catalogo con datos → 0.95\n(boost a 0.97 si QA corrobora)"]
SC2["QA hits: score + count + corroboracion\n(cap 0.75 solo si intent PRIMARIO es catalogo sin datos)"]
SC3["Doc hits: score + boost si ANY intent es doc-heavy\n(DOCUMENTACION · ENVIO · DISTRIBUCION)"]
SC4["sin resultados → 0.0"]
end
subgraph OUTPUT["EvidenceOutput al pipeline"]
O1["hits_qa: EvidenceHitQA list"]
O2["hits_evidencias: EvidenceHitDoc list"]
O3["hits_catalogo: CatalogHit list\n(tool_name · params · resultado · columnas)\nPrioridad maxima para AG5"]
O4["confianza: float 0–1"]
O5["conflictos: list str"]
O6["evidencia_faltante: bool"]
O7["datos_en_vivo: bool\ntrue si hits_catalogo tiene filas"]
end
I1 --> GATHER
I2 -->|"habilita fuentes"| GATHER
GATHER --> PHASE_A
GATHER --> PHASE_B
I3 -->|"filtra coleccion QA"| QA
I4 -->|"slots para LLM planner"| LLM_PLAN
I5 -->|"intent para LLM planner"| LLM_PLAN
I6 -->|"modelo LLM"| LLM_PLAN
DISCOVER --> LLM_PLAN
LLM_PLAN -->|"exito"| MCP_TOOLS
LLM_PLAN -->|"fallo"| FALLBACK
FALLBACK --> MCP_TOOLS
QA --> RR_GATE
PC --> RR_GATE
RR_GATE -->|"true"| RR_QA
RR_GATE -->|"true"| RR_CHUNKS
RR_GATE -->|"true"| RR_PRODUCTS
RR_GATE -->|"false"| RR_SKIP
RR_QA --> O1
RR_CHUNKS --> O2
RR_PRODUCTS --> O2
RR_SKIP --> O1
RR_SKIP --> O2
CP --> O3
CS --> O3
CR --> O3
CD --> O3
O1 --> PHASE_C
O2 --> PHASE_C
O3 --> PHASE_C
PHASE_C --> SC1
PHASE_C --> SC2
PHASE_C --> SC3
PHASE_C --> SC4
SC1 --> O4
SC2 --> O4
SC3 --> O4
SC4 --> O4
O3 --> O7
style INPUT fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style GATHER fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style PHASE_A fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style PHASE_B fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
style MCP_TOOLS fill:#1a0f2e,stroke:#a855f7,color:#e4e8f0
style RERANK fill:#1a2e1a,stroke:#22c55e,color:#e4e8f0
style RR_QA fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style RR_CHUNKS fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style RR_PRODUCTS fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style RR_SKIP fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style PHASE_C fill:#1c1040,stroke:#a855f7,color:#e4e8f0
style OUTPUT fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
style O3 fill:#1a0f2e,stroke:#a855f7,color:#e4e8f0
style O7 fill:#0f2418,stroke:#22c55e,color:#e4e8f0
style CP fill:#1a1a0f,stroke:#f59e0b,color:#e4e8f0
style DISCOVER fill:#0d1f30,stroke:#06b6d4,color:#e4e8f0
style LLM_PLAN fill:#0f1a2e,stroke:#3b82f6,color:#e4e8f0
style FALLBACK fill:#2e1414,stroke:#ef4444,color:#e4e8f0
Cargando ayuda…