View in English
Respuestas estructuradas con esquemas y validación
Oct 16, 2025
Updated: Jun 25, 2026

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 conlist los parámetros se llaman min_length y max_length. Los antiguos min_items y max_items están obsoletos y emiten avisos de deprecación.
  • Optional[str] ya no implica un valor por defecto de None. El campo sigue siendo obligatorio (solo acepta None), así que para que ticker sea realmente opcional escribimos Optional[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_schema y sin herramientas): recibe el borrador y devuelve JSON válido que cumple el esquema. Usa output_key para dejar el resultado en session.state y 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.
    raise

5.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 web y 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.md incluye titulares, tickers, mercado y process_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/A o 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

  1. Extiende NewsItem con un campo nuevo (por ejemplo sentiment) usando los validadores de Pydantic v2 y comprueba que un JSON inválido falla la validación.
  2. Implementa la estrategia de auto fix: cuando model_validate lance ValidationError, vuelve a pedir al modelo que corrija solo los campos problemáticos.
  3. Conecta el patrón de dos agentes de punta a punta y confirma que el JSON validado se guarda en session.state bajo output_key.

Resumen en 3 puntos

  1. Los esquemas Pydantic y output_schema te dan un contrato de salida claro, validable y fácil de consumir por otras apps.
  2. Cuando output_schema está 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.
  3. Del JSON validado puedes derivar Markdown y otros artefactos, y los callbacks te dan trazabilidad de extremo a extremo en process_log.

Recursos

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

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias