Skip to content

feat(analysis): la orquestación sale de api/ y se expone como tool MCP (#107) - #112

Merged
g-garciac2022 merged 5 commits into
devfrom
feat/107-orquestacion-neutral
Aug 16, 2026
Merged

feat(analysis): la orquestación sale de api/ y se expone como tool MCP (#107)#112
g-garciac2022 merged 5 commits into
devfrom
feat/107-orquestacion-neutral

Conversation

@g-garciac2022

Copy link
Copy Markdown
Collaborator

#107 · La orquestación sale de api/: las dos fachadas comparten veredicto

Closes #107

La orquestación del análisis —contrastar señales, agruparlas por dimensión y
derivar el veredicto— vivía en backend/api/analyze.py con un solo
consumidor
. El servidor MCP exponía las cinco señales sueltas y nada que las
combinara.

Por qué importaba: se perdía el caso que demuestra el trabajo

Un agente que recibe cuatro resultados crudos y decide él hará una de dos cosas:
quedarse con la mayoría, o matizar en prosa. Lo que no hará es producir
ambiguo con la discrepancia declarada — que es la tesis del proyecto, no un
detalle de implementación.

El chat y el formulario habrían dado veredictos distintos al mismo titular, y
el que se perdía era el bueno.

Qué incluye

  • backend/analysis/ (nuevo): domain.py con el vocabulario del análisis,
    orchestrator.py con la orquestación movida tal cual, y tool.py que la
    registra como analyze_headline.
  • backend/api/analyze.py: eliminado.
  • backend/api/schemas.py: de 440 a 296 líneas, sólo con lo que es contrato.
  • backend/main.py: registra la tool explícitamente.
  • tests/analysis/test_tool.py (5 tests) y los imports de tres ficheros de
    test existentes.

Los criterios decidieron dónde iba

Primera vez que docs/estructura.md se usa para decidir en lugar de para
describir. Las tres carpetas existentes rechazaron la pieza por su propio
criterio:

Carpeta Criterio
api/ ¿existiría sin HTTP? sí existiría → fuera
core/ ¿ignora el dominio del clickbait? lo sabe todo → fuera
integrations/ ¿envuelve algo externo? no envuelve nada → fuera

Ninguna la admitía, así que pidieron un paquete nuevo.

La separación que hubo que hacer es entre dominio y contrato:
Dimension, OverallVerdict o SignalResult describen qué es el clickbait y se
fueron; ServerInfo, ExecuteResponse o HistoryEntry describen el sistema que
lo sirve y se quedaron.

Y la dependencia va en un solo sentido: api/ importa de analysis/, nunca
al revés. Es comprobable, así que es una alarma y no una opinión.

Qué garantiza el arreglo

assert analysis_tool.analyze is orchestrator.analyze

Ese test es el issue entero: no hay dos jerarquías de veredicto capaces de
divergir
. La tool no reimplementa nada — llama a la misma función que
/analyze y devuelve el mismo tipo.

Se descartaron dos alternativas. Que el agente llamara a POST /analyze:
consistencia trivial, pero el título del TFG es «agente basado en MCP» y que su
capacidad principal esquive MCP es una pregunta previsible en la defensa. Y meter
las reglas de agregación en el prompt: convertiría en no determinista y opaco
justo el paso diseñado para ser explícito — aunque un modelo perfecto siguiera la
jerarquía sin fallar, tendrías una agregación correcta pero no auditable.

La división que queda: el LLM elige qué preguntar; el código decide qué
significa la respuesta.

Medido: el outputSchema desde un modelo Pydantic

MCP sólo publica outputSchema si el retorno está declarado — con -> dict no
publica nada (#100). Las once tools existentes usan TypedDict; ésta devuelve un
modelo Pydantic, que no se había probado nunca aquí.

Retorno outputSchema
-> dict None
TypedDict 212 caracteres, 2 propiedades
AnalyzeResponse (Pydantic) 4.147 caracteres, con $defs de los 6 tipos anidados

Pydantic resuelve los tipos anidados y arrastra los docstrings como
description: el LLM ve los valores admitidos de cada enum, no sólo los nombres
de campo. Mejor que un TypedDict plano — y también mucho más grande.

Con las definiciones de tools ya en ~2.362 tokens (spike #82: 7/20 aciertos a
num_ctx=2048 frente a 17/20 a 8192), esto sube el catálogo de golpe. Se deja
la respuesta completa y se anota
: el límite es de memoria del modelo y se
alivia con la infraestructura de la universidad, con el matiz de que no
desaparece del todo — un catálogo grande también dificulta la selección aunque
quepa.

Una tensión que se resolvió sola

Registrar la tool desde integrations/nlp/tool.py habría creado un ciclo:
analysis/ ya importa las señales de nlp/. Se registra desde su propio paquete
y main.py la llama explícitamente, igual que health.register(mcp).

Eso convierte la tensión 4 de docs/estructura.md —que health conociera
MCP desde core/— de excepción incómoda en patrón declarado: el descubrimiento
encuentra las integraciones; lo que no es una integración se registra a mano.

Dos casos ya no son una excepción.

Notas

  • No cambia comportamiento. Ningún test existente cambió de expectativa: es
    un movimiento más una tool nueva. 180 tests en verde.
  • Quedan abiertas las tensiones 2 y 3 de docs/estructura.md (history.py
    en api/, y discovery/metadata en integrations/), documentadas sin
    resolver.
  • La categoría de la tool es «Análisis completo», no «Señales de análisis»:
    mezclarla con las cinco invitaría al modelo a elegir entre ellas como si fueran
    alternativas del mismo tipo, cuando es la que las contrasta.
  • [docs] Diagramas del flujo de peticiones y actualización de arquitectura.md #106 (los diagramas) se desbloquea, aunque las tensiones 2 y 3 podrían
    mover history.py y metadata.py antes de dibujarlos.

@g-garciac2022
g-garciac2022 merged commit c7195b0 into dev Aug 16, 2026
1 check passed
@g-garciac2022
g-garciac2022 deleted the feat/107-orquestacion-neutral branch August 16, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[arch] La orquestación del análisis sólo existe en la vía REST: el agente MCP no puede reproducir el veredicto

1 participant