Sebastian Gomez
Respuestas estructuradas con esquemas y validación
Notebook de la lección: https://github.com/seagomezar/ADK Blog Posts/blob/main/Lesson_5.ipynb
En esta quinta lección llevamos tu agente a un nivel "enterprise": estandarizamos la salida con esquemas, validamos datos y mantenemos la trazabilidad que iniciamos con los callbacks de la Lección 4. El objetivo es lograr respuestas predecibles, fáciles de consumir por otras apps y listas para producción.
Panorama general
- Diseñar un esquema de salida para noticias de IA con contexto financiero.
- Forzar formato estructurado (JSON) desde el agente y validar con Pydantic.
- Convertir la salida validada en Markdown y guardarla como artefacto.
- Reusar callbacks para auditar fuentes y enriquecer el reporte.
- Pruebas de punta a punta y cierre limpio del servicio.
5.1 ¿Por qué salidas estructuradas?
Las respuestas libres son útiles para conversación, pero difíciles de integrar. Con esquemas:
- Estandarizas el formato (contrato claro entre agentes y servicios).
- Validas datos (tipos, rangos, obligatoriedad).
- Automatizas transformaciones (JSON a Markdown, dashboards, APIs).
- Reduces retrabajo por respuestas ambiguas.
5.2 Define el esquema de salida
Usaremos Pydantic para modelar cada ítem de noticia y el reporte completo.
from typing import List, Optional
from pydantic import BaseModel, AnyHttpUrl, conlist, Field
class NewsItem(BaseModel):
headline: str = Field(..., min_length=8)
company: str
ticker: Optional[str] = None
market_data: str # p.ej. "$123.45 (+1.23%)"
summary: str
sources: conlist(AnyHttpUrl, min_length=1)
class ResearchReport(BaseModel):
title: str
items: conlist(NewsItem, min_length=3, max_length=10)
process_log: List[str] = []Dos detalles importantes de Pydantic v2 que conviene tener presentes:
- En
conlistlos parámetros se llamanmin_lengthymax_length. Los antiguosmin_itemsymax_itemsestán obsoletos y emiten avisos de deprecación. Optional[str]ya no implica un valor por defecto deNone. El campo sigue siendo obligatorio (solo aceptaNone), así que para quetickersea realmente opcional escribimosOptional[str] = None.
5.3 Haz al agente "schema aware"
Tienes dos caminos complementarios:
- Prompting guiado: instruir al LLM para que devuelva un JSON que siga el esquema.
- Validación programática: parsear y validar el JSON con Pydantic; si falla, reintentar o degradar.
5.3.1 Opción nativa ADK: output_schema
ADK permite forzar una salida estructurada declarando un esquema Pydantic en LlmAgent.output_schema.
from google.adk.agents import LlmAgent
class ReportOut(ResearchReport):
pass
formatter = LlmAgent(
name="report_formatter",
model="gemini-2.5-flash",
instruction="Devuelve EXCLUSIVAMENTE un JSON que cumpla el esquema.",
input_schema=None,
output_schema=ReportOut, # fuerza JSON
output_key="final_report_json", # guarda el resultado en session.state
) Los identificadores de modelo de Gemini y los pines de versión de ADK cambian con frecuencia. gemini-2.5-flash es la línea recomendada al momento de esta revisión, pero confirma el id vigente y la versión de ADK en la documentación oficial antes de publicar.
Importante: cuando output_schema está definido, el agente no puede usar herramientas. Usa este agente solo para la etapa de formateo y estandarización.
5.3.2 Patrón de dos agentes (herramientas a formateo)
- Agente A (con herramientas): busca, agrega contexto financiero y prepara un borrador JSON o Markdown.
- Agente B (con
output_schemay sin herramientas): recibe el borrador y devuelve JSON válido que cumple el esquema. Usaoutput_keypara dejar el resultado ensession.statey facilitar su consumo posterior.
Plantilla de instrucción (extracto). El siguiente bloque es un esqueema de forma, no un payload literal: los valores como str o str|null indican el tipo esperado de cada campo, no texto que el modelo deba copiar tal cual.
Devuelve EXCLUSIVAMENTE un JSON válido con esta forma:
{
"title": "AI Industry News Report",
"items": [
{
"headline": str,
"company": str,
"ticker": str|null,
"market_data": str,
"summary": str,
"sources": [url, ...]
}
],
"process_log": [str, ...]
}
No agregues comentarios ni texto antes o después del JSON.Validación en tiempo de ejecución:
import json
from pydantic import ValidationError
raw = llm_response_text # salida del agente (JSON puro)
try:
data = json.loads(raw)
report = ResearchReport.model_validate(data)
except (json.JSONDecodeError, ValidationError) as e:
# Estrategia de recuperación: pedir al modelo que repare el JSON
# o degradar a una plantilla Markdown simple.
raise5.4 Integra callbacks para trazabilidad
Reutiliza el after tool callback de la Lección 4 para llenar process_log con dominios fuente y acciones de control. Incluye ese log dentro del JSON validado y también en el Markdown final.
5.5 Del JSON validado a Markdown
Genera un artefacto legible a partir del esquema:
def report_to_markdown(report: ResearchReport) -> str:
parts = [f"# {report.title}", "\n## Top Headlines\n"]
for i, item in enumerate(report.items, start=1):
parts.append(
f"### {i}. {item.headline}\n"
f"- **Compañía:** {item.company} ({item.ticker or 'N/A'})\n"
f"- **Mercado:** {item.market_data}\n"
f"- **Resumen:** {item.summary}\n"
f"- **Fuentes:** {', '.join(map(str, item.sources))}\n"
)
if report.process_log:
parts.append(
"\n## Process Log\n"
+ "\n".join(f"- {e}" for e in report.process_log)
)
return "\n".join(parts)Guarda ambos artefactos. Necesitamos Path de la librería estándar, y save_news_to_markdown es el helper que definimos en la Lección 4 para escribir el archivo Markdown en disco.
from pathlib import Path
# save_news_to_markdown(...) viene de la Lección 4.
json_path = "ai_research_report.json"
md_path = "ai_research_report.md"
Path(json_path).write_text(report.model_dump_json(indent=2), encoding="utf-8")
save_news_to_markdown(md_path, report_to_markdown(report))5.6 Pruebas de punta a punta
Ejecuta la app:
- Desde la carpeta padre:
adk weby selecciona el agente. - O directo:
adk web --port 8000 app5.
Solicita: "Prepara un reporte de 5 noticias de IA con tickers".
Verifica:
- El agente devuelve JSON válido (sin texto extra).
- El JSON pasa validación Pydantic sin errores.
ai_research_report.mdincluye titulares, tickers, mercado yprocess_log.
Cierra procesos al terminar: pkill -f "adk web".
5.7 Recuperación ante errores
- JSON inválido: pedir al LLM que "repare el JSON" con una función de auto fix, o degradar a una plantilla Markdown mínima.
- Campos faltantes: asignar por defecto
N/Ao reintentar con un prompt que pida solo los campos faltantes. - Fuentes vacías: exigir al modelo que incluya al menos una URL por ítem, o marcar el ítem como incompleto.
Ejercicios propuestos
- Extiende
NewsItemcon un campo nuevo (por ejemplosentiment) usando los validadores de Pydantic v2 y comprueba que un JSON inválido falla la validación. - Implementa la estrategia de auto fix: cuando
model_validatelanceValidationError, vuelve a pedir al modelo que corrija solo los campos problemáticos. - Conecta el patrón de dos agentes de punta a punta y confirma que el JSON validado se guarda en
session.statebajooutput_key.
Resumen en 3 puntos
- Los esquemas Pydantic y
output_schemate dan un contrato de salida claro, validable y fácil de consumir por otras apps. - Cuando
output_schemaestá activo el agente no puede usar herramientas, así que el patrón de dos agentes (herramientas a formateo) es la forma recomendada de combinar búsqueda y salida estructurada. - Del JSON validado puedes derivar Markdown y otros artefactos, y los callbacks te dan trazabilidad de extremo a extremo en
process_log.
Recursos
- Output schema (LlmAgent): https://google.github.io/adk docs/api reference/python/google adk.html#google.adk.agents.LlmAgent.output_schema
- Evaluación: https://google.github.io/adk docs/evaluate/
- Pydantic: https://docs.pydantic.dev/
- Lección anterior: Callbacks y guardrails para agentes confiables
- Siguiente lección: De desarrollo a producción: streaming, memoria, evaluación, despliegue y observabilidad
Este contenido se basa en el curso "Building Live Voice Agents with Google's ADK!" de DeepLearning.AI (https://learn.deeplearning.ai/courses/building live voice agents with googles adk/). Este blog busca acercar material de ADK al español.
Eso es todo, espero que esta lección te sea de utilidad y la puedas aplicar a algún proyecto que tengas en mente. Déjame un comentario si te sirvió, si quieres añadir alguna opinión o si tienes alguna duda, y recuerda que si te gustó también puedes compartirla usando los links a las redes sociales aquí abajo. ¡Buena suerte!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.