Blog Azure AI/ML Foundry IQ Azure AI Search Knowledge Agent RAG Python demo multi-agente query planning observabilidad

Foundry IQ en Vivo: Demo Completa del Knowledge Agent de Azure AI Search

Demo técnica de una capa de conocimiento sobre Azure AI Search con planificación de consultas y trazabilidad

Demo técnica — Este post documenta la sesión de demostración del 28 de febrero de 2026. El objetivo no es presentar un benchmark ni un contrato de API, sino enseñar cómo estructurar una capa de conocimiento compartida sobre Azure AI Search para que varios agentes puedan recuperar contexto de forma consistente, observable y gobernable.

Antes de empezar: qué es y qué no es esta demo

En esta serie hemos usado el nombre Foundry IQ para referirnos a una capa de conocimiento construida alrededor de Azure AI Search, Azure AI Foundry y modelos de lenguaje. En la práctica, el patrón combina:

  • índices de Azure AI Search preparados para búsqueda léxica, semántica y/o vectorial;
  • un componente de recuperación agéntica o planificada;
  • agentes de aplicación que reutilizan la misma base documental;
  • trazabilidad de consultas, fuentes y decisiones de recuperación.

Conviene separar dos cosas:

  1. Capacidades de plataforma: Azure AI Search, Azure AI Foundry, modelos desplegados y mecanismos de recuperación soportados por la versión de API vigente.
  2. Código de aplicación: perfiles de consulta, logs normalizados, reglas de negocio, selección de fuentes y políticas internas.

En este artículo, los ejemplos de código representan principalmente la capa de aplicación. Evitamos fijar nombres concretos de clases de SDK, endpoints o versiones de API preview porque esas superficies pueden cambiar y deben verificarse siempre contra la documentación oficial vigente en el momento de implementación.

¿Qué vamos a demostrar?

La demo se organiza en cinco bloques:

  1. Infraestructura: qué componentes intervienen y cómo se separan responsabilidades.
  2. Primera consulta: una pregunta sencilla y cómo leer la traza de recuperación.
  3. Consulta compleja: descomposición de la pregunta, varias pasadas de recuperación y evaluación de cobertura.
  4. Integración multi-agente: cómo varios agentes consumen la misma capa de conocimiento.
  5. Comparativa operativa: perfiles rápido, equilibrado y profundo, con sus implicaciones de latencia, coste y calidad.

1. Infraestructura: qué tenemos montado

El entorno de demo parte de una idea sencilla: los agentes no deberían mantener cada uno su propio índice, sus propios documentos y sus propias reglas de recuperación. En su lugar, comparten una capa común de conocimiento.

┌─────────────────────────────────────────────────────────────────────┐
│                         Azure AI Foundry                            │
│                                                                     │
│  ┌─────────────────┐        ┌──────────────────────────────────┐    │
│  │  Modelos LLM    │        │       Capa de recuperación        │    │
│  │                 │        │                                  │    │
│  │  Planificación  │───────▶│  Azure AI Search                 │    │
│  │  Síntesis       │        │  Índices:                        │    │
│  │  Evaluación     │        │  • idx-posts                     │    │
│  └─────────────────┘        │  • idx-news                      │    │
│                             │                                  │    │
│                             │  Recuperación híbrida, semántica │    │
│                             │  y/o vectorial según el índice   │    │
│                             └──────────────────────────────────┘    │
│                                           ▲                         │
│                                           │                         │
│        ┌──────────────────────────────────┼─────────────────┐      │
│        │                                  │                 │      │
│  ┌─────┴─────┐  ┌──────────────┐  ┌──────┴──────┐  ┌───────┴──┐   │
│  │Discoverer │  │  Analyzer    │  │   Writer    │  │ Improver │   │
│  │Agent      │  │  Agent       │  │   Agent     │  │ Agent    │   │
│  └───────────┘  └──────────────┘  └─────────────┘  └──────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Responsabilidades por capa

Capa Responsabilidad
Índices de Azure AI Search Almacenar documentos, fragmentos, metadatos, campos filtrables y señales de ranking.
Recuperación Buscar documentos relevantes mediante búsqueda textual, semántica, vectorial o híbrida, según el diseño del índice.
Planificación Reformular o descomponer preguntas cuando la consulta lo requiere.
Síntesis Construir una respuesta usando únicamente el contexto recuperado.
Observabilidad Registrar qué se preguntó, qué fuentes se consultaron, cuántos resultados se usaron y qué señales de calidad se obtuvieron.
Agentes de aplicación Usar la capa de conocimiento para tareas concretas: deduplicar, analizar, redactar o revisar contenido.

Supuestos del entorno

Para que el patrón funcione de forma razonable, los índices deben estar diseñados antes de llegar al agente:

  • documentos fragmentados en unidades recuperables;
  • identificadores estables de documento y fragmento;
  • campos de metadatos para fecha, categoría, idioma, autor, origen y permisos;
  • configuración de búsqueda adecuada al caso de uso;
  • estrategia clara de actualización e invalidación de contenido;
  • trazabilidad entre respuesta generada y documentos usados.

La recuperación agéntica no compensa un índice mal modelado. Si los chunks son demasiado grandes, los metadatos son pobres o no hay filtros de seguridad, la calidad del sistema se degrada aunque el modelo sea muy bueno.


2. Primera consulta: pregunta sencilla y traza de recuperación

Empezamos con una consulta simple:

¿Qué es Foundry IQ y cuáles son sus principales ventajas frente a un RAG clásico?

En la aplicación de demo no exponemos directamente la llamada de bajo nivel. Usamos un cliente interno que encapsula el proveedor de recuperación. Esto permite cambiar de versión de API, SDK o estrategia sin modificar todos los agentes.

# knowledge_client.py
from dataclasses import dataclass
from typing import Literal, Protocol


RetrievalDepth = Literal["rapida", "equilibrada", "profunda"]


@dataclass(frozen=True)
class RetrievalProfile:
    name: str
    depth: RetrievalDepth
    max_documents: int
    allow_query_rewrite: bool
    allow_second_pass: bool
    timeout_seconds: int


BALANCED_PROFILE = RetrievalProfile(
    name="balanced",
    depth="equilibrada",
    max_documents=8,
    allow_query_rewrite=True,
    allow_second_pass=False,
    timeout_seconds=10,
)


class RetrievalProvider(Protocol):
    def retrieve(self, *, messages: list[dict], profile: RetrievalProfile) -> dict:
        ...


class KnowledgeClient:
    """
    Cliente de aplicación.

    No acopla los agentes al detalle de una versión concreta de SDK o API.
    El proveedor real puede usar Azure AI Search, una API preview vigente,
    un wrapper REST interno o una combinación de búsqueda + síntesis.
    """

    def __init__(self, provider: RetrievalProvider):
        self._provider = provider

    def ask(self, question: str, profile: RetrievalProfile = BALANCED_PROFILE) -> dict:
        messages = [
            {
                "role": "system",
                "content": (
                    "Responde en español, con precisión técnica y usando solo "
                    "el contexto recuperado. Si falta evidencia, indícalo."
                ),
            },
            {
                "role": "user",
                "content": question,
            },
        ]

        return self._provider.retrieve(messages=messages, profile=profile)

La llamada desde un agente queda así:

result = knowledge_client.ask(
    "¿Qué es Foundry IQ y cuáles son sus principales ventajas frente a un RAG clásico?"
)

print(result["answer"])

for source in result["sources"]:
    print(f"- {source['id']} ({source['index']}) score={source.get('score')}")

Respuesta observada en la demo

Foundry IQ, en el contexto de esta arquitectura, es una capa de conocimiento compartida
para agentes. Su objetivo es centralizar la recuperación de información sobre una base
documental común, en lugar de que cada agente mantenga su propio índice o sus propias
copias de contexto.

Frente a un RAG clásico punto a punto, aporta tres ventajas principales:

1. Reutilización: varios agentes consultan los mismos índices y aplican criterios
   coherentes de recuperación.

2. Gobernanza: los filtros, metadatos, umbrales y reglas de acceso pueden gestionarse
   en una capa común.

3. Observabilidad: la aplicación puede registrar consultas, documentos recuperados,
   cobertura, latencia y señales de calidad para mejorar el sistema de forma iterativa.

La diferencia importante no es solo tecnológica, sino arquitectónica: la recuperación
pasa de ser una función embebida en cada agente a convertirse en una capacidad compartida
de la plataforma.

Traza normalizada

La siguiente traza no pretende representar un esquema oficial de Azure AI Search. Es el formato normalizado que usamos en la aplicación para observar qué ha ocurrido durante la recuperación.

{
  "query": "¿Qué es Foundry IQ y cuáles son sus principales ventajas frente a un RAG clásico?",
  "profile": "balanced",
  "query_rewrite_enabled": true,
  "rewritten_queries": [
    "Foundry IQ capa de conocimiento compartida",
    "ventajas de una capa RAG centralizada frente a RAG clásico",
    "arquitectura multi-agente con Azure AI Search"
  ],
  "indexes_queried": [
    "idx-posts",
    "idx-news"
  ],
  "documents_returned": 6,
  "documents_used_for_answer": 4,
  "second_pass_executed": false,
  "stop_reason": "sufficient_context",
  "latency_ms": 1840
}

Qué podemos leer en la traza:

  • La pregunta se reformuló en varias consultas más específicas.
  • Se consultaron dos índices internos.
  • No hizo falta una segunda pasada de recuperación.
  • El sistema encontró contexto suficiente para responder.
  • La latencia observada fue razonable para una consulta interactiva.

3. Consulta compleja: planificación y recuperación iterativa

Ahora lanzamos una pregunta más exigente:

Analiza la evolución del stack de RAG en Azurebrains desde noviembre de 2025 hasta ahora. ¿Qué cambios arquitectónicos se han producido? ¿Cómo ha evolucionado el uso de la capa de conocimiento? ¿Qué decisiones siguen abiertas?

Esta pregunta no es una búsqueda puntual. Mezcla cronología, arquitectura, comparación y análisis. Para estos casos usamos un perfil más profundo.

DEEP_PROFILE = RetrievalProfile(
    name="deep",
    depth="profunda",
    max_documents=16,
    allow_query_rewrite=True,
    allow_second_pass=True,
    timeout_seconds=30,
)

result = knowledge_client.ask(
    question=(
        "Analiza la evolución del stack de RAG en Azurebrains desde noviembre de 2025 "
        "hasta ahora. ¿Qué cambios arquitectónicos se han producido? ¿Cómo ha "
        "evolucionado el uso de la capa de conocimiento? ¿Qué decisiones siguen abiertas?"
    ),
    profile=DEEP_PROFILE,
)

Traza observada

{
  "query": "Analiza la evolución del stack de RAG en Azurebrains desde noviembre de 2025...",
  "profile": "deep",
  "query_rewrite_enabled": true,
  "rewritten_queries": [
    "RAG Azurebrains noviembre 2025 arquitectura inicial",
    "Azurebrains capa de conocimiento agentes recuperación",
    "evolución arquitectura RAG multi-agente",
    "decisiones pendientes gobernanza observabilidad RAG",
    "Azure AI Search búsqueda híbrida semantic reranking agentes"
  ],
  "indexes_queried": [
    "idx-posts",
    "idx-news"
  ],
  "first_pass": {
    "documents_returned": 12,
    "coverage": "partial",
    "missing_aspects": [
      "decisiones abiertas",
      "comparación entre arquitectura inicial y arquitectura actual"
    ]
  },
  "second_pass": {
    "executed": true,
    "reason": "partial_coverage",
    "additional_queries": [
      "decisiones pendientes arquitectura RAG Azurebrains",
      "observabilidad evaluación calidad recuperación agentes"
    ],
    "documents_returned": 5
  },
  "documents_used_for_answer": 9,
  "stop_reason": "sufficient_context",
  "latency_ms": 11240
}

Qué aporta la segunda pasada

En consultas complejas, una única búsqueda puede recuperar documentos relevantes pero incompletos. La segunda pasada permite:

  • buscar explícitamente aspectos que no aparecieron en la primera recuperación;
  • mejorar cobertura cronológica;
  • distinguir entre documentos centrales y documentos de apoyo;
  • reducir respuestas demasiado genéricas;
  • detectar que falta evidencia antes de sintetizar.

Esto no significa que todas las consultas deban usar recuperación profunda. En una aplicación real, el perfil debe elegirse según el tipo de operación, el SLA de latencia y el coste aceptable.


4. Integración multi-agente: el patrón del blog

El patrón más importante de la demo es que varios agentes consumen la misma capa de conocimiento.

# agent_profiles.py
from dataclasses import dataclass
from typing import Literal


AgentName = Literal["discoverer", "analyzer", "writer", "improver"]


@dataclass(frozen=True)
class AgentProfile:
    name: AgentName
    default_retrieval_profile: RetrievalProfile
    system_prompt: str


AGENT_PROFILES: dict[AgentName, AgentProfile] = {
    "discoverer": AgentProfile(
        name="discoverer",
        default_retrieval_profile=RetrievalProfile(
            name="discovery-fast",
            depth="rapida",
            max_documents=5,
            allow_query_rewrite=True,
            allow_second_pass=False,
            timeout_seconds=8,
        ),
        system_prompt=(
            "Eres un agente de descubrimiento. Evalúa si una noticia o tema "
            "ya está cubierto en la base de conocimiento."
        ),
    ),
    "analyzer": AgentProfile(
        name="analyzer",
        default_retrieval_profile=DEEP_PROFILE,
        system_prompt=(
            "Eres un analista técnico. Evalúa novedad, impacto, profundidad "
            "y relación con contenido existente."
        ),
    ),
    "writer": AgentProfile(
        name="writer",
        default_retrieval_profile=BALANCED_PROFILE,
        system_prompt=(
            "Eres un redactor técnico. Usa las fuentes recuperadas como grounding "
            "y evita afirmaciones no respaldadas."
        ),
    ),
    "improver": AgentProfile(
        name="improver",
        default_retrieval_profile=BALANCED_PROFILE,
        system_prompt=(
            "Eres un revisor técnico. Detecta afirmaciones ambiguas, obsoletas "
            "o sin evidencia suficiente."
        ),
    ),
}

Y un cliente común para todos:

class AgentKnowledgeService:
    def __init__(self, knowledge_client: KnowledgeClient):
        self._knowledge_client = knowledge_client

    def query(
        self,
        *,
        agent: AgentName,
        user_message: str,
        conversation_history: list[dict] | None = None,
        override_profile: RetrievalProfile | None = None,
    ) -> dict:
        agent_profile = AGENT_PROFILES[agent]
        retrieval_profile = override_profile or agent_profile.default_retrieval_profile

        messages = [
            {
                "role": "system",
                "content": agent_profile.system_prompt,
            }
        ]

        if conversation_history:
            messages.extend(conversation_history)

        messages.append(
            {
                "role": "user",
                "content": user_message,
            }
        )

        return self._knowledge_client._provider.retrieve(
            messages=messages,
            profile=retrieval_profile,
        )

Ejemplo de uso desde el agente de análisis:

result = agent_knowledge_service.query(
    agent="analyzer",
    user_message=(
        "Evalúa si este tema aporta novedad suficiente para un artículo técnico: "
        "'uso de recuperación agéntica sobre Azure AI Search para agentes editoriales'"
    ),
)

print(result["answer"])

Ventajas del patrón

Este diseño tiene varias ventajas prácticas:

  • Menos duplicación: los agentes no implementan su propia recuperación desde cero.
  • Mejor control de calidad: todos pasan por la misma política de fuentes y trazabilidad.
  • Cambio más seguro: si cambia la API subyacente, se modifica el proveedor, no todos los agentes.
  • Auditoría más sencilla: las consultas quedan registradas con un formato común.
  • Evolución incremental: se pueden ajustar perfiles sin reescribir la lógica de negocio.

5. Comparativa operativa: rápido, equilibrado y profundo

En lugar de hablar de niveles como si fueran propiedades universales del servicio, en la aplicación usamos tres perfiles operativos.

Perfil Objetivo Reformulación Segunda pasada Documentos máximos Latencia observada Uso recomendado
rapida Responder o decidir rápido Opcional No 3–5 < 1 s a pocos segundos deduplicación, checks simples, tool calls
equilibrada Buen balance calidad/latencia No normalmente 6–10 pocos segundos grounding general, redacción asistida, preguntas normales
profunda Maximizar cobertura 12–20 varios segundos o más análisis, investigación, comparación, revisión compleja

Ejemplo de ejecución comparativa:

import time


PROFILES = [
    RetrievalProfile(
        name="fast",
        depth="rapida",
        max_documents=5,
        allow_query_rewrite=True,
        allow_second_pass=False,
        timeout_seconds=8,
    ),
    BALANCED_PROFILE,
    DEEP_PROFILE,
]


query = "Explica cómo mejora la planificación de consultas la calidad de recuperación en un sistema RAG."

for profile in PROFILES:
    start = time.perf_counter()

    result = knowledge_client.ask(query, profile=profile)

    elapsed_ms = int((time.perf_counter() - start) * 1000)

    print(f"\nPerfil: {profile.name}")
    print(f"Latencia cliente: {elapsed_ms} ms")
    print(f"Documentos usados: {result['trace']['documents_used_for_answer']}")
    print(f"Motivo de parada: {result['trace']['stop_reason']}")
    print(result["answer"][:500])

Resultado resumido

Perfil: fast
Latencia cliente: 740 ms
Documentos usados: 3
Motivo de parada: sufficient_context
Respuesta breve, útil para una explicación rápida, pero con menos matices.

Perfil: balanced
Latencia cliente: 1910 ms
Documentos usados: 5
Motivo de parada: sufficient_context
Respuesta más estructurada, con mejor separación entre planificación, recuperación
y síntesis.

Perfil: deep
Latencia cliente: 10480 ms
Documentos usados: 9
Motivo de parada: sufficient_context
Respuesta más completa, con ejemplos, límites del patrón y consideraciones de
producción.

La conclusión práctica es clara: no existe un perfil óptimo para todo. La recuperación profunda mejora cobertura, pero no debería usarse para cada interacción si el usuario espera una respuesta inmediata.


Patrones de producción aprendidos

Patrón 1: elegir profundidad por operación, no por agente

Un error habitual es asignar un perfil fijo a cada agente. En producción suele funcionar mejor decidir por tipo de operación.

def profile_for_operation(operation: str) -> RetrievalProfile:
    if operation in {
        "deduplication_check",
        "quick_grounding",
        "simple_fact_lookup",
    }:
        return RetrievalProfile(
            name="operation-fast",
            depth="rapida",
            max_documents=5,
            allow_query_rewrite=True,
            allow_second_pass=False,
            timeout_seconds=8,
        )

    if operation in {
        "article_grounding",
        "technical_review",
        "source_validation",
    }:
        return BALANCED_PROFILE

    if operation in {
        "novelty_analysis",
        "comparative_research",
        "architecture_review",
    }:
        return DEEP_PROFILE

    return BALANCED_PROFILE

El mismo agente puede necesitar una recuperación rápida para una comprobación puntual y una recuperación profunda para un análisis de novedad.

Patrón 2: tratar la traza como señal de calidad

La observabilidad no debería limitarse a latencia y errores HTTP. Para sistemas RAG o agentic retrieval conviene registrar señales de calidad.

def summarize_retrieval_quality(result: dict, query_id: str) -> dict:
    trace = result["trace"]
    sources = result.get("sources", [])

    scores = [
        source["score"]
        for source in sources
        if isinstance(source.get("score"), int | float)
    ]

    avg_score = sum(scores) / len(scores) if scores else None

    return {
        "query_id": query_id,
        "profile": trace.get("profile"),
        "documents_returned": trace.get("documents_returned"),
        "documents_used_for_answer": trace.get("documents_used_for_answer"),
        "second_pass_executed": trace.get("second_pass", {}).get("executed", False),
        "stop_reason": trace.get("stop_reason"),
        "latency_ms": trace.get("latency_ms"),
        "avg_score": avg_score,
    }

Señales útiles para alertas o revisión:

  • demasiadas consultas terminan por timeout;
  • muchas respuestas usan pocos documentos;
  • baja puntuación media de recuperación;
  • ausencia de fuentes en respuestas que deberían estar fundamentadas;
  • incremento brusco de latencia;
  • aumento de consultas sin cobertura suficiente.

Patrón 3: historial conversacional con límites

Para agentes como writer, pasar historial puede mejorar la coherencia. Pero no conviene enviar todo sin control.

def compact_history(history: list[dict], max_turns: int = 6) -> list[dict]:
    """
    Mantiene los últimos turnos relevantes.
    En producción puede combinarse con resumen conversacional y filtros de seguridad.
    """
    return history[-max_turns:]


conversation: list[dict] = []

first = agent_knowledge_service.query(
    agent="writer",
    user_message="Explica qué es una capa de conocimiento compartida para agentes.",
)

conversation.append(
    {
        "role": "user",
        "content": "Explica qué es una capa de conocimiento compartida para agentes.",
    }
)
conversation.append(
    {
        "role": "assistant",
        "content": first["answer"],
    }
)

second = agent_knowledge_service.query(
    agent="writer",
    user_message="Compárala ahora con un RAG clásico embebido en cada aplicación.",
    conversation_history=compact_history(conversation),
)

Recomendaciones:

  • limitar longitud de historial;
  • no reenviar datos sensibles innecesarios;
  • resumir turnos antiguos;
  • conservar referencias de fuentes usadas;
  • separar memoria conversacional de base documental.

Patrón 4: filtros y permisos desde el diseño del índice

Si la base de conocimiento es multi-equipo o multi-cliente, la seguridad no puede depender solo del prompt.

Buenas prácticas:

  • incluir campos filtrables de tenant, equipo, clasificación y visibilidad;
  • aplicar filtros antes de la síntesis;
  • no mostrar citas de documentos que el usuario no pueda ver;
  • registrar el contexto de identidad usado en cada consulta;
  • probar casos negativos: usuarios sin permiso, documentos mixtos y cambios de rol.

Patrón 5: degradación controlada

En sistemas con LLMs y recuperación, los fallos parciales son normales: timeouts, falta de cobertura, índices en actualización o límites de cuota.

La aplicación debería poder responder de forma controlada:

def safe_answer(result: dict) -> str:
    trace = result.get("trace", {})
    stop_reason = trace.get("stop_reason")

    if stop_reason == "insufficient_context":
        return (
            "No tengo contexto suficiente en la base de conocimiento para responder "
            "con precisión. Puedo darte una respuesta general, pero debería revisarse "
            "contra fuentes adicionales."
        )

    if stop_reason == "timeout":
        return (
            "La búsqueda no ha terminado dentro del tiempo esperado. Prueba con una "
            "pregunta más acotada o ejecuta un análisis profundo en segundo plano."
        )

    return result["answer"]

Checklist para llevar este patrón a producción

Antes de usar una arquitectura de este tipo en un flujo crítico, revisaría al menos estos puntos:

  • Los índices tienen metadatos suficientes para filtrar, auditar y ordenar.
  • Los documentos están fragmentados con una estrategia coherente.
  • La aplicación registra fuentes usadas por cada respuesta.
  • Existen perfiles de recuperación separados por operación.
  • Los timeouts están definidos y probados.
  • Hay pruebas con preguntas sin respuesta.
  • Hay pruebas con documentos contradictorios.
  • La seguridad se aplica antes de generar la respuesta.
  • Las respuestas indican incertidumbre cuando falta evidencia.
  • Las APIs preview están aisladas detrás de un adaptador interno.
  • Hay métricas de latencia, cobertura, errores y coste.
  • Existe un proceso de revisión para contenido generado automáticamente.

Próximos pasos

Las siguientes mejoras naturales de esta arquitectura son:

  • calibración de ranking y umbrales: medir qué documentos se usan realmente y ajustar la recuperación con datos;
  • evaluación automática: crear conjuntos de preguntas esperadas y comparar cobertura, citas y estabilidad;
  • segmentación multi-tenant: aplicar filtros de seguridad y visibilidad desde el índice;
  • observabilidad avanzada: correlacionar consulta, documentos recuperados, respuesta final y feedback humano;
  • degradación por coste y latencia: decidir cuándo responder rápido, cuándo profundizar y cuándo ejecutar en background.

El código de integración debe mantenerse deliberadamente desacoplado de la API concreta. En tecnologías preview, esa separación es lo que permite evolucionar sin reescribir todos los agentes.


Este artículo es parte de la serie Azurebrains RAG Series:

  1. RAG Fundamentos: Recuperación Aumentada
  2. Azure AI Search: Búsqueda Híbrida y Reranking
  3. RAG 2.0 y Foundry IQ: Knowledge Layer Centralizado
  4. GraphRAG: Relaciones entre Documentos con Apache AGE
  5. Conversation Knowledge Mining con Foundry IQ
  6. → Foundry IQ en Vivo: Demo Completa (este artículo)