feat(api): ejecución de tools y contrato de retorno estructurado - #101
Merged
Conversation
…er error handling
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.
#100 ·
POST /tools/{name}/executey el contrato de retornoCloses #100
El objetivo era el endpoint:
/toolsya publicaba elinputSchemade cadaherramienta, así que faltaba lo que ejecutara el formulario construido con él.
Al medirlo antes de escribir código apareció que no podía existir tal cual, y
de ahí salió la mitad de esta PR.
El bloqueo: éxito y fallo eran indistinguibles
isErrorcontent[0].textFalse{"score": 3, "is_clickbait": true, …}FalseEl titular está vacío o no es válidoTrueError executing tool …: validation errorisErrorsólo se activaba cuando el fallo ocurría en la capa MCP. Si laherramienta devolvía un mensaje de error —lo que hacían las once— para el
protocolo era un éxito. Y «parsear como JSON» no valía de heurística:
get_forecastdevuelve prosa siendo una ejecución correcta.Se descartó
-> ToolResult, la opción aparentemente obvia por existir ya. Suesquema describe el sobre, no la carta —
dataqueda comoanyOf: [{}, null]— y duplica el eje que MCP ya tiene: comoToolResult.failsedevuelve y no se lanza, daría respuestas con
isError: falseysuccess: falsea la vez.
ToolResultsigue intacto donde estaba, en losclient.pyybase_api.py; no aparecía ni aparece en ningúntool.py.Un fallo que llevaba desde la Épica 1 escondido
Al hacer que las tools lanzaran, el mensaje seguía perdiéndose. La causa
estaba en
log_tool_invocation:El decorador de observabilidad capturaba toda excepción y devolvía una
cadena. Consecuencia anterior a este issue: un
KeyErroro un fallo de red nocapturado dentro de una tool llegaba al cliente como texto normal con
isErroraFalse — indistinguible de un análisis correcto.
Y explica por qué el contrato parecía coherente: no era que unas tools
devolvieran errores por decisión y otras no, es que el decorador aplanaba todo
a texto, lo devuelto a propósito y lo lanzado por accidente. El arreglo es un
raiseen vez de unreturn, con el principio escrito en el código: eldecorador es para observar, no para decidir qué se responde.
Salida estructurada: qué cuesta
outputSchemastructuredContent-> strconjson.dumps(lo anterior){"result": string}-> dicta secasNoneNoneTypedDictpropioAnotar
-> dictno sirve: MCP necesita un tipo declarado. Se usanTypedDicty no modelos Pydantic porque las capas de cliente ya devuelvendiccionarios — así no hay conversión, sólo se declara la forma que ya tienen. El
esquema publicado incluye además el docstring del tipo como
description.Nueve herramientas declaran el suyo.
get_alertsyget_forecastse quedan en-> str: producen prosa para leer, y forzarles estructura sería inventarcampos que la salida no tiene.
El endpoint
Valida los argumentos contra el
inputSchemaantes de invocar (R4.5), coniter_errorsen vez devalidatepara acumular todos los problemas — quienrellena un formulario prefiere corregirlo de una vez. Que la validación sea
previa es lo que permite distinguir un campo mal escrito de un análisis que
salió mal.
status: errorLa última no es un error HTTP: la petición era correcta y el servidor la atendió.
Mismo criterio que
/analyze.Y un timeout propio (
mcp_execute_timeout, 60 s):mcp_timeoutson 5 s, desobra para un
list_toolsde 0,036 s, perodetect_clickbait_incoherencetarda~20 s en frío cargando embeddings. Con el margen del descubrimiento moriría
siempre.
Un fallo que habría llegado a producción
Las excepciones lanzadas dentro de una sesión MCP salen envueltas en
ExceptionGroup: la sesión abre un task group de anyio. Elexcept InvalidArgumentsde la ruta no la habría reconocido y un argumento malescrito habría dado 500 en vez de 422. La lógica ahora devuelve lo
ocurrido y decide fuera de la sesión.
Es la segunda vez que anyio aparece en el proyecto — la primera fueron los
cancel scopes en los tests del catálogo.
Un hueco de cobertura
Rehacer el contrato de las once herramientas no rompió un solo test. No por
estar bien cubierto: porque nadie probaba las tools MCP. Todos los tests
atacan la capa cliente, que devuelve
ToolResulty no ha cambiado.test_tool_contract.pycubre ahora esa frontera.Un dato para cuando llegue el agente (R13)
Se intentó revalidar la selección de herramientas con los scripts del spike #82,
porque los docstrings son la interfaz que lee el LLM. Dos cosas aparecieron, y
ninguna bloquea esta PR:
El spike #82 no medía lo que decía medir. Afirma usar «las descripciones
REALES (docstrings de tool.py)» pero las lleva copiadas a mano, resumidas a
unos 150 caracteres. Nunca probó los textos que recibe el agente.
Y las descripciones reales no caben en el contexto que asumía aquel spike:
num_ctxdel spikeLas definiciones por sí solas desbordan la ventana, antes de añadir la
consulta. Con esa configuración el modelo llegó a inventarse una herramienta
(
detect_clicks_incoherence) y a pedir alertas meteorológicas para analizar untitular — síntomas de una lista truncada, no de unos docstrings peores.
La revalidación del agente se aplaza al hito de R13, cuando haya infra
adecuada: el agente no forma parte de H2 y el hardware local es una restricción
del entorno de desarrollo, no del diseño.
Notas
Seis commits, ordenados para poder leerse por partes: el arreglo del decorador va
solo y primero, porque es un bug preexistente e independiente y no debería
quedar enterrado entre el resto.
jsonschemapasa a dependencia directa: ya entraba como transitiva de MCP, peroahora el código la importa.
135 tests en verde.