feat(analysis): la orquestación sale de api/ y se expone como tool MCP (#107) - #112
Merged
Conversation
…analysis orchestration
… potential hanging issue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#107 · La orquestación sale de
api/: las dos fachadas comparten veredictoCloses #107
La orquestación del análisis —contrastar señales, agruparlas por dimensión y
derivar el veredicto— vivía en
backend/api/analyze.pycon un soloconsumidor. 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
ambiguocon la discrepancia declarada — que es la tesis del proyecto, no undetalle 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.pycon el vocabulario del análisis,orchestrator.pycon la orquestación movida tal cual, ytool.pyque laregistra 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 detest existentes.
Los criterios decidieron dónde iba
Primera vez que
docs/estructura.mdse usa para decidir en lugar de paradescribir. Las tres carpetas existentes rechazaron la pieza por su propio
criterio:
api/core/integrations/Ninguna la admitía, así que pidieron un paquete nuevo.
La separación que hubo que hacer es entre dominio y contrato:
Dimension,OverallVerdictoSignalResultdescriben qué es el clickbait y sefueron;
ServerInfo,ExecuteResponseoHistoryEntrydescriben el sistema quelo sirve y se quedaron.
Y la dependencia va en un solo sentido:
api/importa deanalysis/, nuncaal revés. Es comprobable, así que es una alarma y no una opinión.
Qué garantiza el arreglo
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
/analyzey 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
outputSchemadesde un modelo PydanticMCP sólo publica
outputSchemasi el retorno está declarado — con-> dictnopublica nada (#100). Las once tools existentes usan
TypedDict; ésta devuelve unmodelo Pydantic, que no se había probado nunca aquí.
outputSchema-> dictTypedDictAnalyzeResponse(Pydantic)$defsde los 6 tipos anidadosPydantic resuelve los tipos anidados y arrastra los docstrings como
description: el LLM ve los valores admitidos de cada enum, no sólo los nombresde campo. Mejor que un
TypedDictplano — 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=2048frente a 17/20 a 8192), esto sube el catálogo de golpe. Se dejala 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.pyhabría creado un ciclo:analysis/ya importa las señales denlp/. Se registra desde su propio paquetey
main.pyla llama explícitamente, igual quehealth.register(mcp).Eso convierte la tensión 4 de
docs/estructura.md—quehealthconocieraMCP desde
core/— de excepción incómoda en patrón declarado: el descubrimientoencuentra las integraciones; lo que no es una integración se registra a mano.
Dos casos ya no son una excepción.
Notas
un movimiento más una tool nueva. 180 tests en verde.
docs/estructura.md(history.pyen
api/, ydiscovery/metadataenintegrations/), documentadas sinresolver.
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.
mover
history.pyymetadata.pyantes de dibujarlos.