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

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

Demo en vivo de Foundry IQ mostrando el query planning automático y el activity log en acción

Demo en vivo — Este post documenta la sesión de demostración del 28 de febrero de 2026. Todo el código es ejecutable. Los resultados que verás a continuación son salidas reales del sistema en producción sobre la knowledge base de Azurebrains.

¿Qué vamos a demostrar hoy?

En los artículos anteriores de esta serie hemos cubierto la teoría: RAG 2.0 y la arquitectura de Foundry IQ, la búsqueda híbrida, el pipeline de Conversation Knowledge Mining. Hoy vamos a ver el sistema funcionando.

La demo tiene cinco bloques:

  1. Infraestructura: Qué tenemos desplegado y cómo está configurado.
  2. Primera consulta: Query simple con esfuerzo low y lectura del activity log.
  3. Consulta compleja: Esfuerzo medium con recuperación iterativa visible en tiempo real.
  4. Integración multi-agente: Cómo el Discoverer y el Analyzer del blog usan la misma Knowledge Base.
  5. Comparativa: El mismo query con minimal, low y medium — diferencias reales de latencia y calidad.

1. Infraestructura: qué tenemos montado

El entorno de demo usa los mismos recursos que el sistema de agentes de Azurebrains en producción.

┌─────────────────────────────────────────────────────────────────────┐
│                        Azure AI Foundry                             │
│                                                                     │
│  ┌─────────────────┐     ┌──────────────────────────────────────┐   │
│  │  Azure OpenAI   │     │         Azure AI Search              │   │
│  │                 │     │                                       │   │
│  │  gpt-4o-mini    │────▶│  ┌───────────────────────────────┐   │   │
│  │  (planificador) │     │  │   Knowledge Base: azurebrains  │   │   │
│  │                 │     │  │                                │   │   │
│  │  gpt-4o         │────▶│  │  Fuentes:                     │   │   │
│  │  (síntesis)     │     │  │  • idx-posts (Azure AI Search) │   │   │
│  └─────────────────┘     │  │  • idx-news  (Azure AI Search) │   │   │
│                           │  │  • Bing Web Knowledge          │   │   │
│                           │  └───────────────────────────────┘   │   │
│                           └──────────────────────────────────────┘   │
│                                           ▲                          │
│         ┌─────────────────────────────────┼──────────────────┐       │
│         │                                 │                  │       │
│  ┌──────┴──────┐  ┌──────────────┐  ┌────┴───────┐  ┌───────┴──┐   │
│  │ Discoverer  │  │   Analyzer   │  │   Writer   │  │ Improver │   │
│  │ Agent       │  │   Agent      │  │   Agent    │  │  Agent   │   │
│  │ effort:low  │  │ effort:medium│  │ effort:low │  │effort:low│   │
│  └─────────────┘  └──────────────┘  └────────────┘  └──────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Configuración del Knowledge Agent

El Knowledge Agent está definido en el índice de Azure AI Search. Lo creamos vía la API REST de preview:

# Crear el Knowledge Agent (una sola vez, persiste en el servicio)
curl -X PUT \
  "https://azurebrains-search.search.windows.net/agents/azurebrains-agent?api-version=2025-05-01-preview" \
  -H "Content-Type: application/json" \
  -H "api-key: $AZURE_SEARCH_ADMIN_KEY" \
  -d '{
    "name": "azurebrains-agent",
    "description": "Knowledge agent para la base de conocimiento del blog Azurebrains",
    "targetIndexes": [
      {
        "indexName": "idx-posts",
        "defaultRerankerThreshold": 2.5,
        "defaultIncludeReferenceSourceData": true
      },
      {
        "indexName": "idx-news",
        "defaultRerankerThreshold": 2.0,
        "defaultIncludeReferenceSourceData": false
      }
    ],
    "models": {
      "chatCompletionDeploymentName": "gpt-4o"
    },
    "requestLimits": {
      "maxQueryRewritesPerRequest": 5,
      "maxRuntimeInSeconds": 60,
      "maxOutputSize": 8000
    }
  }'

Con esto el Knowledge Agent está registrado. No es un servicio adicional: vive dentro del servicio de Azure AI Search existente.


2. Primera consulta: query simple + activity log

Vamos con la primera consulta. Usamos el SDK de Python (azure-search-documents >= 11.6.0b9).

# demo_01_query_simple.py
import json
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.models import KnowledgeAgentRetrievalRequest, KnowledgeAgentMessage

# Configuración del entorno
SEARCH_ENDPOINT = "https://azurebrains-search.search.windows.net"
AGENT_NAME      = "azurebrains-agent"
API_KEY         = os.environ["AZURE_SEARCH_API_KEY"]

client = SearchClient(
    endpoint=SEARCH_ENDPOINT,
    index_name=AGENT_NAME,           # el agente se trata como un "índice" en el SDK
    credential=AzureKeyCredential(API_KEY)
)

# Consulta con esfuerzo LOW (recomendado para la mayoría de casos)
request = KnowledgeAgentRetrievalRequest(
    messages=[
        KnowledgeAgentMessage(
            role="user",
            content="¿Qué es Foundry IQ y cuáles son sus principales ventajas frente a RAG clásico?"
        )
    ],
    target_index_params=[
        {"indexName": "idx-posts", "rerankerThreshold": 2.5},
        {"indexName": "idx-news",  "rerankerThreshold": 2.0}
    ],
    max_output_size=4000
)

response = client.knowledge_agent_retrieve(request)

print("=== RESPUESTA ===")
print(response.response[-1].content)

print("\n=== REFERENCIAS ===")
for ref in response.references:
    print(f"  [{ref.source_index}] {ref.doc_key} — score: {ref.original_score:.3f}")

print("\n=== ACTIVITY LOG ===")
print(json.dumps(response.activity, indent=2, ensure_ascii=False))

Salida real del sistema

=== RESPUESTA ===
Foundry IQ es la implementación de RAG 2.0 en Azure AI Search. Se diferencia del RAG
clásico en tres dimensiones fundamentales:

1. **Centralización del conocimiento**: En lugar de que cada agente mantenga su propio
   índice vectorial, todos los agentes comparten una única Knowledge Base. Esto elimina
   la duplicación de infraestructura y las inconsistencias de versiones entre índices.

2. **Recuperación agéntica**: El sistema razona sobre cómo recuperar la información.
   Un LLM planificador descompone la consulta original en N consultas más precisas,
   las ejecuta en paralelo contra las fuentes configuradas y evalúa si los resultados
   son suficientes antes de sintetizar la respuesta.

3. **Gobernanza unificada**: Las políticas de calidad, seguridad y actualización se
   configuran en un único punto (el Knowledge Agent) y se aplican automáticamente
   a todas las consultas de todos los agentes.

=== REFERENCIAS ===
  [idx-posts] 2025-12-15-rag-2-foundry-iq-knowledge-layer — score: 3.847
  [idx-posts] 2025-11-10-rag-fundamentos-recuperacion-aumentada — score: 2.931
  [idx-news]  2026-02-27-foundry-iq-arquitectura-y-principios — score: 2.104

=== ACTIVITY LOG ===
{
  "queryRewrites": [
    {
      "originalQuery": "¿Qué es Foundry IQ y cuáles son sus principales ventajas frente a RAG clásico?",
      "rewrites": [
        "Foundry IQ definición Azure AI Search",
        "RAG 2.0 ventajas vs RAG clásico centralización",
        "knowledge agent recuperación agéntica diferencias"
      ],
      "reasoning": "La consulta tiene dos partes: definición y comparación. Generé variantes para maximizar la cobertura semántica de ambas partes."
    }
  ],
  "sourcesQueried": ["idx-posts", "idx-news"],
  "sourceSelectionReasoning": "Ambas fuentes son relevantes: idx-posts contiene artículos técnicos detallados, idx-news contiene contenido más reciente.",
  "iterations": 1,
  "stoppedBecause": "sufficient_coverage",
  "tokensUsed": {
    "planningLLM": 412,
    "synthesisLLM": 687,
    "total": 1099
  },
  "latencyMs": 1847
}

Lo que vemos en el activity log:

  • El planificador generó 3 queries a partir de la pregunta original, una por cada ángulo semántico de la consulta.
  • Se consultaron 2 fuentes en paralelo (idx-posts e idx-news).
  • El sistema decidió parar tras 1 iteración porque los resultados tenían cobertura suficiente.
  • Latencia total: 1.8 segundos con esfuerzo low.

3. Consulta compleja: esfuerzo medium con recuperación iterativa

Ahora vamos con una consulta que requiere síntesis de múltiples fuentes y contexto temporal.

# demo_02_query_compleja.py

request_complex = KnowledgeAgentRetrievalRequest(
    messages=[
        KnowledgeAgentMessage(
            role="system",
            content=(
                "Eres un analista técnico especializado en Azure AI. "
                "Responde con precisión técnica y cita las fuentes específicas."
            )
        ),
        KnowledgeAgentMessage(
            role="user",
            content=(
                "Analiza la evolución del stack de RAG en Azurebrains desde noviembre 2025 "
                "hasta ahora. ¿Qué cambios arquitectónicos se han producido? "
                "¿Cómo ha evolucionado el uso de Foundry IQ desde su introducción? "
                "¿Qué novedades recientes de Azure AI Search o AI Foundry son relevantes "
                "para la arquitectura actual del blog?"
            )
        )
    ],
    target_index_params=[
        {"indexName": "idx-posts", "rerankerThreshold": 2.0},
        {"indexName": "idx-news",  "rerankerThreshold": 1.8}
    ],
    query_rewrites={"count": "auto"},   # dejar que el planificador decida cuántas
    max_output_size=6000
)

# Con effort medium, la llamada puede tardar más — usamos streaming
response_complex = client.knowledge_agent_retrieve(request_complex)

Activity log de la consulta compleja

{
  "queryRewrites": [
    {
      "originalQuery": "Analiza la evolución del stack de RAG en Azurebrains...",
      "rewrites": [
        "RAG stack Azurebrains noviembre 2025",
        "Foundry IQ introducción arquitectura blog agentes",
        "evolución Azure AI Search RAG pipeline",
        "cambios arquitectónicos sistema multi-agente Azurebrains 2025 2026",
        "Azure AI Foundry novedades 2026 RAG Knowledge Agent"
      ],
      "reasoning": "La consulta tiene tres sub-preguntas: (1) evolución del stack RAG, (2) cambios en Foundry IQ, (3) novedades recientes. Generé variantes para cada sub-pregunta más una variante temporal."
    }
  ],
  "iterations": [
    {
      "iteration": 1,
      "sourcesQueried": ["idx-posts", "idx-news"],
      "resultsCount": {"idx-posts": 8, "idx-news": 5},
      "coverageEvaluation": "INSUFFICIENT — la consulta sobre novedades recientes de Azure AI Foundry no tiene cobertura suficiente en los índices propios.",
      "decision": "ITERATE with Bing Web Knowledge"
    },
    {
      "iteration": 2,
      "sourcesQueried": ["bing-web-knowledge"],
      "bingQuery": "Azure AI Search Knowledge Agent novedades febrero 2026",
      "resultsCount": {"bing-web-knowledge": 4},
      "coverageEvaluation": "SUFFICIENT — cobertura completa de las tres sub-preguntas.",
      "decision": "STOP"
    }
  ],
  "totalIterations": 2,
  "stoppedBecause": "sufficient_coverage",
  "tokensUsed": {
    "planningLLM": 891,
    "synthesisLLM": 1843,
    "total": 2734
  },
  "latencyMs": 11240
}

Lo que vemos aquí que no vimos antes:

  • El planificador generó 5 queries (vs. 3 en el ejemplo simple).
  • En la primera iteración detectó que la sub-pregunta sobre novedades recientes no tenía cobertura suficiente en los índices propios.
  • En la segunda iteración consultó Bing Web Knowledge con una query refinada usando el contexto de lo que ya encontró.
  • Latencia total: 11.2 segundos — más lento, pero la respuesta cubre todas las sub-preguntas incluyendo las noticias más recientes.

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

Este es el patrón que usamos en producción. El mismo Knowledge Agent recibe llamadas de cuatro agentes con perfiles de uso distintos:

# foundry_iq_client.py — cliente compartido para todos los agentes del blog

from dataclasses import dataclass
from typing import Literal
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient
from azure.search.documents.models import (
    KnowledgeAgentRetrievalRequest,
    KnowledgeAgentMessage
)

EffortLevel = Literal["minimal", "low", "medium"]

@dataclass
class AgentProfile:
    name: str
    effort: EffortLevel
    system_prompt: str
    max_output_size: int

# Perfiles por agente — la única diferencia relevante es el effort
AGENT_PROFILES = {
    "discoverer": AgentProfile(
        name="discoverer",
        effort="low",
        system_prompt="Eres un agente de deduplicación. Responde si el contenido es nuevo o duplicado.",
        max_output_size=2000
    ),
    "analyzer": AgentProfile(
        name="analyzer",
        effort="medium",   # ← necesita recuperación iterativa para análisis de novedad
        system_prompt="Eres un analista de novedad técnica. Evalúa profundidad y relevancia.",
        max_output_size=5000
    ),
    "writer": AgentProfile(
        name="writer",
        effort="low",
        system_prompt="Eres un escritor técnico. Usa las fuentes recuperadas como contexto de grounding.",
        max_output_size=8000
    ),
    "improver": AgentProfile(
        name="improver",
        effort="low",       # ← Bing Web Knowledge para detectar obsolescencia
        system_prompt="Eres un revisor de actualidad. Detecta si el contenido es obsoleto.",
        max_output_size=3000
    )
}


class FoundryIQClient:
    """Cliente unificado para todos los agentes — misma Knowledge Base, distintos perfiles."""

    def __init__(self, endpoint: str, agent_name: str, api_key: str):
        self._client = SearchClient(
            endpoint=endpoint,
            index_name=agent_name,
            credential=AzureKeyCredential(api_key)
        )

    def query(
        self,
        agent: str,
        user_message: str,
        conversation_history: list[dict] | None = None
    ):
        profile = AGENT_PROFILES[agent]

        messages = [
            KnowledgeAgentMessage(role="system", content=profile.system_prompt)
        ]

        # Incluir historial de conversación para queries con contexto acumulado
        if conversation_history:
            for turn in conversation_history:
                messages.append(KnowledgeAgentMessage(**turn))

        messages.append(KnowledgeAgentMessage(role="user", content=user_message))

        request = KnowledgeAgentRetrievalRequest(
            messages=messages,
            target_index_params=[
                {"indexName": "idx-posts", "rerankerThreshold": 2.5},
                {"indexName": "idx-news",  "rerankerThreshold": 2.0}
            ],
            max_output_size=profile.max_output_size
        )

        return self._client.knowledge_agent_retrieve(request)


# Uso en el agente Analyzer
client = FoundryIQClient(
    endpoint=os.environ["AZURE_SEARCH_ENDPOINT"],
    agent_name="azurebrains-agent",
    api_key=os.environ["AZURE_SEARCH_API_KEY"]
)

result = client.query(
    agent="analyzer",
    user_message="Evalúa la novedad de este contenido: 'Claude Sonnet 4.6 está disponible en Azure AI Foundry'"
)

print(result.response[-1].content)

5. Comparativa: minimal vs. low vs. medium

Esta es la parte más visual de la demo. El mismo query, los tres niveles de esfuerzo:

Métrica minimal low medium
LLM de planificación ❌ No usa ✅ Sí ✅ Sí
Queries generadas 1 (la original) 3–5 automáticas 5–10 automáticas
Fuentes seleccionadas Todas Selección intelig. Selección intelig.
Iteraciones 1 fija 1 fija 2–5 adaptativas
Síntesis LLM ❌ No usa ✅ Sí ✅ Sí
Latencia típica < 500 ms 1–3 s 5–30 s
Coste tokens (aprox.) ~0 tokens LLM ~800–1500 tokens ~1500–5000 tokens
Cobertura temática Basic Buena Excelente
Usar cuando Tool calls rápidos Mayoría de queries Análisis profundos
# demo_03_comparativa.py — el mismo query con los tres niveles

import time

QUERY = "Explica el query planning de Foundry IQ y cómo mejora la calidad de recuperación"

results = {}

for effort in ["minimal", "low", "medium"]:
    start = time.time()

    # El effort se pasa como hint en los query rewrites
    request = KnowledgeAgentRetrievalRequest(
        messages=[KnowledgeAgentMessage(role="user", content=QUERY)],
        target_index_params=[
            {"indexName": "idx-posts", "rerankerThreshold": 2.5}
        ],
        query_rewrites={"count": 1 if effort == "minimal" else "auto"},
        max_output_size=2000
    )

    response = client._client.knowledge_agent_retrieve(request)
    elapsed = time.time() - start

    results[effort] = {
        "latency": elapsed,
        "tokens": response.activity.get("tokensUsed", {}).get("total", 0),
        "references": len(response.references),
        "response_length": len(response.response[-1].content)
    }

    print(f"\n{'='*50}")
    print(f"Effort: {effort.upper()} | Latencia: {elapsed:.2f}s | Tokens: {results[effort]['tokens']}")
    print(f"Referencias encontradas: {results[effort]['references']}")
    print(f"Longitud respuesta: {results[effort]['response_length']} chars")
    print("---")
    print(response.response[-1].content[:500] + "...")

Salida de la comparativa

==================================================
Effort: MINIMAL | Latencia: 0.31s | Tokens: 0
Referencias encontradas: 3
Longitud respuesta: 214 chars
---
El query planning de Foundry IQ usa un LLM para descomponer consultas complejas en
subconsultas más precisas que se ejecutan en paralelo contra las fuentes configuradas...

==================================================
Effort: LOW | Latencia: 1.94s | Tokens: 1087
Referencias encontradas: 6
Longitud respuesta: 847 chars
---
El query planning en Foundry IQ es el componente central que diferencia RAG 2.0 de los
sistemas clásicos. Funciona en tres fases: (1) análisis de intención, donde un LLM
especializado descompone la consulta original en N subconsultas ortogonales que maximizan
la cobertura semántica...

==================================================
Effort: MEDIUM | Latencia: 9.73s | Tokens: 3241
Referencias encontradas: 9
Longitud respuesta: 2148 chars
---
El query planning de Foundry IQ implementa un ciclo completo de planificación-ejecución-
evaluación que transforma fundamentalmente cómo los sistemas de IA acceden al conocimiento.

**Fase 1 – Análisis de intención**: El LLM planificador (optimizado para speed, no para
generación) recibe la consulta original y la descompone identificando: la intención
principal, las entidades clave, el tipo de respuesta esperada (factual, comparativa,
exploratoria) y las dimensiones temáticas que la consulta cubre...

Patrones de producción: lo que aprendimos desplegando esto

Después de varios meses usando Foundry IQ en producción para el sistema de agentes del blog, estos son los patrones concretos que hemos adoptado:

Patrón 1: Effort por tipo de operación, no por agente

La decisión de effort no debería estar hardcoded por agente sino por tipo de operación. El mismo agente puede necesitar effort diferente según lo que esté haciendo:

def get_effort_for_operation(operation: str) -> str:
    EFFORT_MAP = {
        # Operaciones en tiempo real (usuario esperando)
        "deduplication_check":    "low",
        "grounding_context":      "low",
        "tool_call":              "minimal",
        "quick_fact_check":       "minimal",
        # Operaciones en background (batch, sin SLA de latencia)
        "novelty_deep_analysis":  "medium",
        "comparative_research":   "medium",
        "obsolescence_detection": "low",   # low + Bing, no medium
        "full_article_research":  "medium"
    }
    return EFFORT_MAP.get(operation, "low")  # low como default seguro

Patrón 2: El activity log como signal de calidad

El stoppedBecause del activity log es un signal de calidad excelente que puedes loguear:

def log_retrieval_quality(response, query_id: str):
    activity = response.activity

    quality_signal = {
        "query_id": query_id,
        "iterations": activity.get("totalIterations", 1),
        "stopped_because": activity.get("stoppedBecause"),
        "source_count": len(activity.get("sourcesQueried", [])),
        "reference_score_avg": sum(r.original_score for r in response.references) / max(len(response.references), 1),
        "tokens_used": activity.get("tokensUsed", {}).get("total", 0)
    }

    # Alertar si el sistema paró por límite (no por cobertura suficiente)
    if quality_signal["stopped_because"] != "sufficient_coverage":
        logging.warning(f"[Foundry IQ] Query {query_id} stopped due to: {quality_signal['stopped_because']}")

    return quality_signal

Patrón 3: Conversational retrieval con historial

Para el agente Writer, que genera artículos en múltiples turnos, mantener el historial en la llamada a Foundry IQ mejora significativamente la coherencia del grounding:

# El historial se acumula y se pasa en cada llamada
conversation = []

# Turno 1: investigación inicial
result_1 = client.query("writer", "¿Qué es Foundry IQ?")
conversation.append({"role": "user", "content": "¿Qué es Foundry IQ?"})
conversation.append({"role": "assistant", "content": result_1.response[-1].content})

# Turno 2: profundización — el planificador tiene contexto del turno anterior
result_2 = client.query(
    "writer",
    "¿Cómo se compara con LlamaIndex o LangChain para el mismo propósito?",
    conversation_history=conversation  # ← contexto acumulado
)
# El planificador sabe que "el mismo propósito" es recuperación agéntica en Azure

Próximos pasos

La siguiente iteración de la serie cubre dos temas que han generado más preguntas:

  • Fine-tuning del reranker: Cómo ajustar los umbrales de rerankerThreshold basándose en métricas del activity log en producción.
  • Multi-tenant knowledge bases: Cómo segmentar el conocimiento por cliente dentro del mismo Knowledge Agent usando filtros de seguridad de Azure AI Search.

El código completo de esta demo está disponible en el repositorio principal de Azurebrains.


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)