Desarrollar un agente de canalización de consultas de LlamaIndex

En esta página se muestra cómo desarrollar un agente con la plantilla LlamaIndex Query Pipelines (la clase LlamaIndexQueryPipelineAgent del SDK de Vertex AI para Python). Este agente está diseñado para responder a preguntas mediante la generación aumentada de recuperación (RAG), como la siguiente consulta: "¿Cómo fue la vida de Paul Graham en la universidad?".

Sigue estos pasos para desarrollar un agente con las canalizaciones de consultas de LlamaIndex:

  1. Definir y configurar un modelo
  2. Definir y usar un retriever
  3. Definir y usar un sintetizador de respuestas
  4. (Opcional) Personalizar la plantilla de petición
  5. (Opcional) Personaliza la orquestación.

Antes de empezar

Para configurar tu entorno, sigue los pasos que se indican en el artículo Configurar el entorno.

Definir y configurar un modelo

Define y configura un modelo para que lo use tu agente de Query Pipeline de LlamaIndex.

  1. Define la versión del modelo:

    model = "gemini-2.0-flash"
    
  2. (Opcional) Especifica los parámetros del modelo:

    model_kwargs = {
        # vertexai_config (dict): By providing the region and project_id parameters,
        # you can enable model usage through Vertex AI.
        "vertexai_config": {
            "project": "PROJECT_ID",
            "location": "LOCATION"
        },
        # temperature (float): The sampling temperature controls the degree of
        # randomness in token selection.
        "temperature": 0.28,
        # context_window (int): The context window of the model.
        # If not provided, the default context window is 200000.
        "context_window": 200000,
        # max_tokens (int): Token limit determines the maximum
        # amount of text output from one prompt. If not provided,
        # the default max_tokens is 256.
        "max_tokens": 256,
    }
    
  3. Crea un LlamaIndexQueryPipelineAgent con las siguientes configuraciones de modelo:

    from vertexai.preview import reasoning_engines
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
        model=model,                # Required.
        model_kwargs=model_kwargs,  # Optional.
    )
    
  4. Si estás en un entorno interactivo (como el terminal o un cuaderno de Colab), puedes consultar al agente:

    response = agent.query(input="What is Paul Graham's life in college?")
    
    print(response)
    

    Deberías recibir una respuesta similar a la siguiente:

    {'message': {'role': 'assistant',
      'additional_kwargs': {},
      'blocks': [{'block_type': 'text',
        'text': "Unfortunately, there's not a lot of publicly available information about Paul Graham's personal life in college. ..."}]},
      'raw': {'content': {'parts': [{'video_metadata': None,
          'thought': None,
          'code_execution_result': None,
          'executable_code': None,
          'file_data': None,
          'function_call': None,
          'function_response': None,
          'inline_data': None,
          'text': "Unfortunately, there's not a lot of publicly available information about Paul Graham's personal life in college. ..."}],
        'role': 'model'},
        'citation_metadata': None,
        'finish_message': None,
        'token_count': None,
        'avg_logprobs': -0.1468650027438327,
        'finish_reason': 'STOP',
        'grounding_metadata': None,
        'index': None,
        'logprobs_result': None,
        'safety_ratings': [{'blocked': None,
          'category': 'HARM_CATEGORY_HATE_SPEECH',
          'probability': 'NEGLIGIBLE',
          'probability_score': 0.022949219,
          'severity': 'HARM_SEVERITY_NEGLIGIBLE',
          'severity_score': 0.014038086},
        {'blocked': None,
          'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
          'probability': 'NEGLIGIBLE',
          'probability_score': 0.056640625,
          'severity': 'HARM_SEVERITY_NEGLIGIBLE',
          'severity_score': 0.029296875},
        {'blocked': None,
          'category': 'HARM_CATEGORY_HARASSMENT',
          'probability': 'NEGLIGIBLE',
          'probability_score': 0.071777344,
          'severity': 'HARM_SEVERITY_NEGLIGIBLE',
          'severity_score': 0.024047852},
        {'blocked': None,
          'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
          'probability': 'NEGLIGIBLE',
          'probability_score': 0.103515625,
          'severity': 'HARM_SEVERITY_NEGLIGIBLE',
          'severity_score': 0.05102539}],
        'usage_metadata': {'cached_content_token_count': None,
        'candidates_token_count': 222,
        'prompt_token_count': 10,
        'total_token_count': 232}},
      'delta': None,
      'logprobs': None,
      'additional_kwargs': {}}
    

(Opcional) Personalizar el modelo

La plantilla LlamaIndexQueryPipelineAgent usa Google GenAI de forma predeterminada para proporcionar acceso a todos los modelos básicos disponibles en Google Cloud. Para usar un modelo que no esté disponible a través de Google GenAI, define model_builder= de la siguiente manera:

from typing import Optional

def model_builder(
    *,
    model_name: str,                      # Required. The name of the model
    model_kwargs: Optional[dict] = None,  # Optional. The model keyword arguments.
    **kwargs,                             # Optional. The remaining keyword arguments to be ignored.
):

Para ver una lista de los modelos de chat admitidos en LlamaIndexQueryPipeline y sus funciones, consulta Integraciones de LLMs disponibles. Cada modelo de chat usa su propio conjunto de valores admitidos para model= y model_kwargs=.

IA generativa de Google

La IA generativa de Google se instala de forma predeterminada cuando configuras tu entorno y se usa automáticamente en la plantilla LlamaIndexQueryPipelineAgent cuando omites model_builder.

from vertexai.preview import reasoning_engines

agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
    model=model,                # Required.
    model_kwargs=model_kwargs,  # Optional.
)

Anthropic

  1. Sigue la documentación de Anthropic para configurar una cuenta e instalar el paquete llama-index-llms-anthropic.

  2. Define model_builder para devolver el modelo Anthropic:

    def model_builder(*, model_name: str, model_kwargs = None, **kwargs):
        from llama_index.llms.anthropic import Anthropic
    
        return Anthropic(model=model_name, **model_kwargs)
    
  3. Usa el modelo de Anthropic en la plantilla LlamaIndexQueryPipelineAgent:

    from vertexai.preview import reasoning_engines
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
        model="claude-3-opus-20240229",           # Required.
        model_builder=model_builder,              # Required.
        model_kwargs={
            "api_key": "ANTHROPIC_API_KEY",    # Required.
            "temperature": 0.28,                  # Optional.
        },
    )
    

OpenAILike

Puedes usar OpenAILike con la ChatCompletions API de Gemini.

  1. Sigue la documentación de OpenAILike para instalar el paquete:

    pip install llama-index-llms-openai-like
    
  2. Define un model_builder que devuelva el modelo OpenAILike:

    def model_builder(
        *,
        model_name: str,
        model_kwargs = None,
        project: str,   # Specified via vertexai.init
        location: str,  # Specified via vertexai.init
        **kwargs,
    ):
        import google.auth
        from llama_index.llms.openai_like import OpenAILike
    
        # Note: the credential lives for 1 hour by default.
        # After expiration, it must be refreshed.
        creds, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"])
        auth_req = google.auth.transport.requests.Request()
        creds.refresh(auth_req)
    
        if model_kwargs is None:
            model_kwargs = {}
    
        endpoint = f"https://{location}-aiplatform.googleapis.com"
        api_base = f'{endpoint}/v1beta1/projects/{project}/locations/{location}/endpoints/openapi'
    
        return OpenAILike(
            model=model_name,
            api_base=api_base,
            api_key=creds.token,
            **model_kwargs,
        )
    
  3. Usa el modelo en la plantilla LlamaIndexQueryPipelineAgent:

    from vertexai.preview import reasoning_engines
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
        model="google/gemini-2.0-flash",  # Or "meta/llama3-405b-instruct-maas"
        model_builder=model_builder,        # Required.
        model_kwargs={
            "temperature": 0,               # Optional.
            "max_retries": 2,               # Optional.
        },
    )
    

Definir y usar un retriever

Una vez que hayas definido el modelo, define el buscador que usará para razonar. Un extractor se puede crear a partir de índices, pero también se puede definir de forma completa. Debes probar tu retriever de forma local.

  1. Define un buscador que devuelva documentos relevantes y puntuaciones de similitud:

    def retriever_builder(model, retriever_kwargs=None):
        import os
        import requests
        from llama_index.core import (
            StorageContext,
            VectorStoreIndex,
            load_index_from_storage,
        )
        from llama_index.core import SimpleDirectoryReader
        from llama_index.embeddings.vertex import VertexTextEmbedding
        import google.auth
    
        credentials, _ = google.auth.default()
        embed_model = VertexTextEmbedding(
            model_name="text-embedding-005", project="PROJECT_ID", credentials=credentials
        )
    
        data_dir = "data/paul_graham"
        essay_file = os.path.join(data_dir, "paul_graham_essay.txt")
        storage_dir = "storage"
    
        # --- Simple Download (if needed) ---
        if not os.path.exists(essay_file):
            os.makedirs(data_dir, exist_ok=True)  # Make sure the directory exists
            essay_url = "https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt"
            try:
                response = requests.get(essay_url)
                response.raise_for_status()  # Check for download errors
                with open(essay_file, "wb") as f:
                    f.write(response.content)
                print("Essay downloaded.")
            except requests.exceptions.RequestException as e:
                print(f"Download failed: {e}")
    
        # --- Build/Load Index ---
        if not os.path.exists(storage_dir):
            print("Creating new index...")
            # --- Load Data ---
            reader = SimpleDirectoryReader(data_dir)
            docs = reader.load_data()
    
            index = VectorStoreIndex.from_documents(docs, model=model, embed_model=embed_model)
            index.storage_context.persist(persist_dir=storage_dir)
        else:
            print("Loading existing index...")
            storage_context = StorageContext.from_defaults(persist_dir=storage_dir)
            index = load_index_from_storage(storage_context, embed_model=embed_model)
    
        return index.as_retriever()
    
  2. Prueba el retriever:

    from llama_index.llms.google_genai import GoogleGenAI
    
    model = GoogleGenAI(
        model=model,
        **model_kwargs
    )
    retriever = retriever_builder(model)
    retrieved_response = retriever.retrieve("What is Paul Graham's life in College?")
    

    La respuesta obtenida debería ser similar a la siguiente:

    [
      NodeWithScore(
        node=TextNode(
          id_='692a5d5c-cd56-4ed0-8e29-ecadf6eb9933',
          embedding=None,
          metadata={'file_path': '/content/data/paul_graham/paul_graham_essay.txt', 'file_name': 'paul_graham_essay.txt', 'file_type': 'text/plain', 'file_size': 75042, 'creation_date': '2025-03-24', 'last_modified_date': '2025-03-24'},
          excluded_embed_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'],
          excluded_llm_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'],
          relationships={
            <NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='3e1c4d73-1e1d-4e83-bd16-2dae24abb231', node_type='4', metadata={'file_path': '/content/data/paul_graham/paul_graham_essay.txt', 'file_name': 'paul_graham_essay.txt', 'file_type': 'text/plain', 'file_size': 75042, 'creation_date': '2025-03-24', 'last_modified_date': '2025-03-24'}, hash='0c3c3f46cac874b495d944dfc4b920f6b68817dbbb1699ecc955d1fafb2bf87b'), <NodeRelationship.PREVIOUS: '2'>: RelatedNodeInfo(node_id='782c5787-8753-4f65-85ed-c2833ea6d4d8', node_type='1', metadata={'file_path': '/content/data/paul_graham/paul_graham_essay.txt', 'file_name': 'paul_graham_essay.txt', 'file_type': 'text/plain', 'file_size': 75042, 'creation_date': '2025-03-24', 'last_modified_date': '2025-03-24'}, hash='b8e6463833887a8a2b13f1b5a623672819faedc1b725d9565ba003223628db0e'), <NodeRelationship.NEXT: '3'>: RelatedNodeInfo(node_id='f7d2cb7e-fa0c-40bf-b8e7-b888e36b87f9', node_type='1', metadata={}, hash='db7cc1a67fa3afd1e5f24c8c61583781ce6a00c444da8f25a5374468c17b7de0')
          },
          metadata_template='{key}: {value}',
          metadata_separator='\n',
          text='So I looked around to see what I could salvage from the wreckage of my plans, and there was Lisp...',
          mimetype='text/plain',
          start_char_idx=7166,
          end_char_idx=11549,
          metadata_separator='\n',
          text_template='{metadata_str}\n\n{content}'
        ),
        score=0.7403571819090398
      )
    ]
    
  3. Para usar el retriever en la plantilla LlamaIndexQueryPipelineAgent, añádelo al argumento retriever_builder=:

    from vertexai.preview import reasoning_engines
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
        model=model,                          # Required.
        model_kwargs=model_kwargs,            # Optional.
        retriever_builder=retriever_builder,  # Optional.
    )
    
  4. Prueba el agente de forma local haciendo consultas de prueba:

    response = agent.query(
        input="What is Paul Graham's life in College?"
    )
    

    La respuesta es una lista serializable en JSON de nodos con puntuaciones.

    [{'node': {'id_': '692a5d5c-cd56-4ed0-8e29-ecadf6eb9933',
      'embedding': None,
      'metadata': {'file_path': '/content/data/paul_graham/paul_graham_essay.txt',
        'file_name': 'paul_graham_essay.txt',
        'file_type': 'text/plain',
        'file_size': 75042,
        'creation_date': '2025-03-12',
        'last_modified_date': '2025-03-12'},
      'excluded_embed_metadata_keys': ['file_name',
        'file_type',
        'file_size',
        'creation_date',
        'last_modified_date',
        'last_accessed_date'],
      'excluded_llm_metadata_keys': ['file_name',
        'file_type',
        'file_size',
        'creation_date',
        'last_modified_date',
        'last_accessed_date'],
      'relationships': {'1': {'node_id': '07ee9574-04c8-46c7-b023-b22ba9558a1f',
        'node_type': '1',
        'metadata': {},
        'hash': '44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a',
        'class_name': 'RelatedNodeInfo'},
        '2': {'node_id': 'ac7e54aa-6fff-40b5-a15e-89c5eb234936',
        'node_type': '1',
        'metadata': {'file_path': '/content/data/paul_graham/paul_graham_essay.txt',
          'file_name': 'paul_graham_essay.txt',
          'file_type': 'text/plain',
          'file_size': 75042,
          'creation_date': '2025-03-12',
          'last_modified_date': '2025-03-12'},
        'hash': '755327a01efe7104db771e4e6f9683417884ea6895d878da882d2b21a6b66442',
        'class_name': 'RelatedNodeInfo'},
        '3': {'node_id': '3a04be27-ac46-4acd-a8c6-031689508982',
        'node_type': '1',
        'metadata': {},
        'hash': 'db7cc1a67fa3afd1e5f24c8c61583781ce6a00c444da8f25a5374468c17b7de0',
        'class_name': 'RelatedNodeInfo'}},
      'metadata_template': '{key}: {value}',
      'metadata_separator': '\n',
      'text': 'So I looked around to see what I could salvage from the wreckage of my plans, and there was Lisp...',
      'mimetype': 'text/plain',
      'start_char_idx': 7164,
      'end_char_idx': 11547,
      'metadata_separator': '\n',
      'text_template': '{metadata_str}\n\n{content}',
      'class_name': 'TextNode'},
      'score': 0.25325886336265013,
      'class_name': 'NodeWithScore'}
    ]
    

Definir y usar un sintetizador de respuestas

Después de definir el modelo y el retriever, define el response-synthesizer que genera una respuesta a partir de un LLM usando una consulta de usuario y un conjunto de fragmentos de texto. Puedes usar el get_response_synthesizer predeterminado o configurar el modo de respuesta.

  1. Define un sintetizador de respuestas que devuelva la respuesta:

    def response_synthesizer_builder(model, response_synthesizer_kwargs=None):
        from llama_index.core.response_synthesizers import SimpleSummarize
    
        return SimpleSummarize(llm=model)
    
  2. Prueba la función:

    response_synthesizer = response_synthesizer_builder(model=model)
    response = response_synthesizer.get_response(
        "What is Paul Graham's life in College?",
        [node.model_dump_json() for node in retrieved_response],
    )
    

    La respuesta debería ser similar a la siguiente:

    "While in a PhD program for computer science, he took art classes and worked on a book about Lisp hacking. He applied to art schools, got accepted to RISD, and later got an invitation to take the entrance exam at the Accademia di Belli Arti in Florence. He was accepted to both. He attended the Accademia, but was disappointed by the lack of instruction."
    
  3. Para usar el sintetizador de respuestas en la plantilla LlamaIndexQueryPipeline, añádelo al argumento response_synthesizer_builder=:

    from vertexai.preview import reasoning_engines
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
        model=model,                                                    # Required.
        model_kwargs=model_kwargs,                                      # Optional.
        retriever_builder=retriever_builder,                            # Optional.
        response_synthesizer_builder=response_synthesizer_builder,      # Optional.
    )
    
  4. Prueba todo el flujo de procesamiento de consultas de RAG de forma local ejecutando consultas de prueba:

    response = agent.query(
        input="What is Paul Graham's life in College?"
    )
    

    La respuesta es un diccionario similar al siguiente:

    {
      'response': "While in college, he was drawn to McCarthy's 1960 Lisp, although he didn't fully grasp the reasons for his interest at the time. He also had a brief encounter with surplus Xerox Dandelions in the computer lab but found them too slow for his liking. \n",
      'source_nodes': [
        '{"node":{"id_":"95889c30-53c7-43d0-bf91-930dbb23bde6"...,"score":0.7077213268404997,"class_name":"NodeWithScore"}'
      ],
      'metadata': {
        '95889c30-53c7-43d0-bf91-930dbb23bde6': {
          'file_path': '/content/data/paul_graham/paul_graham_essay.txt',
          'file_name': 'paul_graham_essay.txt',
          'file_type': 'text/plain',
          'file_size': 75042,
          'creation_date': '2025-03-25',
          'last_modified_date': '2025-03-25'
        }
      }
    }
    

(Opcional) Personalizar la plantilla de petición

Las plantillas de peticiones traducen las entradas de los usuarios en instrucciones para el modelo, lo que permite que las respuestas sean coherentes y relevantes en función del contexto. Consulta más información sobre las peticiones.

La plantilla de petición predeterminada se organiza secuencialmente en las siguientes secciones:

Sección Descripción
(Opcional) Instrucción del sistema Instrucciones para el agente que se aplicarán a todas las consultas.
Entrada del usuario La consulta del usuario a la que debe responder el agente.

La plantilla de petición predeterminada se genera si creas el agente sin especificar tu propia plantilla de petición y tiene el siguiente aspecto:

from llama_index.core import prompts
from llama_index.core.base.llms import types

message_templates = [
  types.ChatMessage(role=types.MessageRole.SYSTEM, content=system_instruction),
  types.ChatMessage(role=types.MessageRole.USER, content="{input}"),
]
prompts.ChatPromptTemplate(message_templates=message_templates)

Puedes usar la plantilla de petición completa cuando crees una instancia del agente en el siguiente ejemplo:

  from vertexai.preview import reasoning_engines

  system_instruction = "I help to find what is Paul Graham's life in College"

  agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
      model=model,
      system_instruction=system_instruction,
  )

Puedes sustituir la plantilla de petición predeterminada por la tuya propia y usarla al crear el agente:

prompt_str = "Please answer {question} about {name}"
prompt_tmpl = PromptTemplate(prompt_str)

from vertexai.preview import reasoning_engines
agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
    model = model,
    prompt = prompt_tmpl,
)

agent.query(
    input={
        "name": "Paul Graham",
        "question": "What is the life in college?",
    }
)

(Opcional) Personalizar la orquestación

Todos los componentes LlamaIndexQueryPipeline implementan la interfaz Query Component, que proporciona esquemas de entrada y salida para la orquestación. La LlamaIndexQueryPipelineAgent necesita que se cree un runnable para poder responder a las consultas. De forma predeterminada, LlamaIndexQueryPipelineAgent crea una cadena secuencial o un grafo acíclico dirigido (DAG) mediante Query Pipeline.

Puede personalizar la orquestación si tiene previsto hacer lo siguiente:

  • Implementa un agente que amplíe el flujo de RAG (por ejemplo, ampliando un módulo Prompt, Model, Retriever o Response Synthesizer a Query Engine, Query Transformer, Output Parsers, Postprocessor/Rerankers o Custom Query Component).

  • Pide al agente que use ReAct para ejecutar herramientas y que anote cada paso con comentarios sobre por qué ha realizado ese paso. Para ello, anula el ejecutable predeterminado al crear el LlamaIndexQueryPipelineAgent especificando el argumento runnable_builder=:

    from typing import Optional
    from llama_index.core.llms import function_calling
    
    def runnable_builder(
        model: function_calling.FunctionCallingLLM,
        *,
        system_instruction: Optional[str] = None,
        prompt: Optional[query.QUERY_COMPONENT_TYPE] = None,
        retriever: Optional[query.QUERY_COMPONENT_TYPE] = None,
        response_synthesizer: Optional[query.QUERY_COMPONENT_TYPE] = None,
        runnable_kwargs: Optional[Mapping[str, Any]] = None,
    ):
    

    Donde:

    • model corresponde al modelo de chat que devuelve model_builder (consulta Definir y configurar un modelo).
    • retriever y retriever_kwargs corresponden al buscador y a las configuraciones que se van a usar (consulta Definir un buscador).
    • response_synthesizer y response_synthesizer_kwargs corresponden al sintetizador de respuestas y a las configuraciones que se van a usar (consulta Definir un sintetizador de respuestas).
    • system_instruction y prompt corresponden a la configuración de la petición (consulte Personalizar la plantilla de petición).
    • agent_executor_kwargs y runnable_kwargs son los argumentos de palabras clave que puedes usar para personalizar el elemento ejecutable.

Puedes personalizar la lógica de orquestación mediante una canalización personalizada o ReAct:

Flujo de trabajo personalizado

Para proporcionar un módulo adicional (como un postprocesador) al agente, anula el runnable_builder de LlamaIndexQueryPipelineAgent.

  1. Define un postprocesador:

    def post_processor_builder():
      from llama_index.core.postprocessor import SimilarityPostprocessor
    
      # similarity postprocessor: filter nodes below 0.7 similarity score
      return SimilarityPostprocessor(similarity_cutoff=0.7)
    
    def runnable_with_postprocessor_builder(
        model, runnable_kwargs, **kwargs
    ):
      from llama_index.core.query_pipeline import QueryPipeline
    
      pipeline = QueryPipeline(**runnable_kwargs)
      pipeline_modules = {
          "retriever": retriever_builder(model),
          "postprocessor": post_processor_builder(),
      }
      pipeline.add_modules(pipeline_modules)
      pipeline.add_link("retriever", "postprocessor")
    
      return pipeline
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
      model=model,
      runnable_builder=runnable_with_postprocessor_builder,
    )
    
  2. Consulta el agente:

    result = agent.query(input="What is Paul Graham's life in College?")
    

    La salida debería ser similar a la siguiente:

    [
      {
        'node': {'id_': 'bb7d2942-213d-4fb3-a7cb-1a664642a7ff',
        'embedding': None,
        'metadata': {
          'file_path': '/content/data/paul_graham/paul_graham_essay.txt',
          'file_name': 'paul_graham_essay.txt',
          'file_type': 'text/plain',
          'file_size': 75042,
          'creation_date': '2025-03-25',
          'last_modified_date': '2025-03-25'
        },
        'excluded_embed_metadata_keys': [
          'file_name',
          'file_type',
          'file_size',
          'creation_date',
          'last_modified_date',
          'last_accessed_date'
        ],
        'excluded_llm_metadata_keys': [
          'file_name',
          'file_type',
          'file_size',
          'creation_date',
          'last_modified_date',
          'last_accessed_date'
        ],
        'relationships': {'1': {'node_id': 'c508cee5-5ef2-4fdf-a33d-0427dcb78b5c',
          'node_type': '4',
          'metadata': {'file_path': '/content/data/paul_graham/paul_graham_essay.txt',
            'file_name': 'paul_graham_essay.txt',
            'file_type': 'text/plain',
            'file_size': 75042,
            'creation_date': '2025-03-25',
            'last_modified_date': '2025-03-25'},
          'hash': '0c3c3f46cac874b495d944dfc4b920f6b68817dbbb1699ecc955d1fafb2bf87b',
          'class_name': 'RelatedNodeInfo'},
          '2': {'node_id': '97a84b41-62bf-4959-acae-cfd4bdfbd4d9',
          'node_type': '1',
          'metadata': {'file_path': '/content/data/paul_graham/paul_graham_essay.txt',
            'file_name': 'paul_graham_essay.txt',
            'file_type': 'text/plain',
            'file_size': 75042,
            'creation_date': '2025-03-25',
            'last_modified_date': '2025-03-25'},
          'hash': 'a7dd352be97e47e8e553ceda3d2d2c9e9d5c54adb298063c94da06167938d583',
          'class_name': 'RelatedNodeInfo'},
          '3': {'node_id': 'b984eea1-f0bc-4880-812e-3f49f1e304b8',
          'node_type': '1',
          'metadata': {},
          'hash': 'db7cc1a67fa3afd1e5f24c8c61583781ce6a00c444da8f25a5374468c17b7de0',
          'class_name': 'RelatedNodeInfo'}},
        'metadata_template': '{key}: {value}',
        'metadata_separator': '\n',
        'text': 'So I looked around to see what I could salvage from the wreckage of my plans, and there was Lisp...',
        'mimetype': 'text/plain',
        'start_char_idx': 7166,
        'end_char_idx': 11549,
        'metadata_separator': '\n',
        'text_template': '{metadata_str}\n\n{content}',
        'class_name': 'TextNode'},
        'score': 0.7403571819090398,
        'class_name': 'NodeWithScore'
      },
      {
        'node': {'id_': 'b984eea1-f0bc-4880-812e-3f49f1e304b8...'}
        'score': 0.7297395567513889,
        'class_name': 'NodeWithScore'
      }
    ]
    

Agente ReAct

Para proporcionar comportamiento de llamada a herramientas con tu propio agente ReAct, anula runnable_builder por LlamaIndexQueryPipelineAgent.

  1. Define una función de ejemplo que devuelva un tipo de cambio:

    def get_exchange_rate(
      currency_from: str = "USD",
      currency_to: str = "EUR",
      currency_date: str = "latest",
    ):
      """Retrieves the exchange rate between two currencies on a specified date.
    
      Uses the Frankfurter API (https://api.frankfurter.app/) to obtain
      exchange rate data.
    
      Args:
          currency_from: The base currency (3-letter currency code).
              Defaults to "USD" (US Dollar).
          currency_to: The target currency (3-letter currency code).
              Defaults to "EUR" (Euro).
          currency_date: The date for which to retrieve the exchange rate.
              Defaults to "latest" for the most recent exchange rate data.
              Can be specified in YYYY-MM-DD format for historical rates.
    
      Returns:
          dict: A dictionary containing the exchange rate information.
              Example: {"amount": 1.0, "base": "USD", "date": "2023-11-24",
                  "rates": {"EUR": 0.95534}}
      """
      import requests
      response = requests.get(
          f"https://api.frankfurter.app/{currency_date}",
          params={"from": currency_from, "to": currency_to},
      )
      return response.json()
    
  2. Crea un agente ReAct personalizado con herramientas:

    def runnable_with_tools_builder(model, runnable_kwargs=None, **kwargs):
      from llama_index.core.query_pipeline import QueryPipeline
      from llama_index.core.tools import FunctionTool
      from llama_index.core.agent import ReActAgent
    
      llama_index_tools = []
      for tool in runnable_kwargs.get("tools"):
          llama_index_tools.append(FunctionTool.from_defaults(tool))
      agent = ReActAgent.from_tools(llama_index_tools, llm=model, verbose=True)
      return QueryPipeline(modules = {"agent": agent})
    
    agent = reasoning_engines.LlamaIndexQueryPipelineAgent(
      model="gemini-2.0-flash",
      runnable_kwargs={"tools": [get_exchange_rate]},
      runnable_builder=runnable_with_tools_builder,
    )
    
  3. Consulta el agente:

    result = agent.query(input="What is the exchange rate between US and EURO today?")
    

    La salida debería tener este aspecto:

    {
      'response': 'The exchange rate between US and EURO today, 2025-03-19, is 1 USD to 0.91768 EUR.',
      'source_nodes': [],
      'metadata': None
    }
    

Siguientes pasos