Cree un agente SQL con LangGraph y Mistral Medium 3 utilizando watsonx.ai

Autor

Anna Gutowska

AI Engineer, Developer Advocate

IBM

En este tutorial, creará un agente de IA que puede ejecutar y generar consultas Python y SQL para su base de datos SQLite personalizada. Su agente se construirá desde cero utilizando LangGraph y el modelo de lenguaje de gran tamaño (LLM) Mistral Medium 3. Este LLM está optimizado para casos de uso profesionales como la codificación y la comprensión multimodal, lo que lo convierte en una excelente opción para esta tarea1. También detectaremos y bloquearemos los mensajes dañinos mediante el modelo IBM® Granite Guardian.

¿Qué es LangGraph?

LangGraph, creado por LangChain, es un marco de agentes de IA de código abierto diseñado para construir, implementar y gestionar flujos de trabajo complejos de agentes de IA generativa. Proporciona un conjunto de herramientas y bibliotecas que permiten a los usuarios crear, ejecutar y optimizar LLM de forma escalable y eficiente. Básicamente, LangGraph utiliza el poder de las arquitecturas basadas en gráficos para modelar y gestionar las intrincadas relaciones entre varios componentes de un flujo de trabajo de agente de IA, incluidos los componentes que se encuentran en los sistemas multiagente.

IBM watsonx.ai

Conocimientos de datos con LangGraph y watsonx.ai

¿Puede un agente de IA tomar nuestra consulta en lenguaje natural y procesarla para darnos un resultado significativo? Utilizamos varias tecnologías de código abierto y la potencia de watsonx.ai para ponerlo a prueba.

Requisitos previos

  1. Necesita una cuenta de IBM Cloud para crear un proyecto watsonx.ai .

  2. Para este tutorial se pueden utilizar varias versiones de Python. En el momento de la publicación, recomendamos descargar Python 3.13, la última versión.

Pasos

Paso 1. Configure su entorno

Aunque puede elegir entre varias herramientas, este tutorial le muestra cómo configurar una cuenta de IBM para utilizar un Jupyter Notebook.

  1. Inicie sesión en watsonx.ai utilizando su cuenta de IBM Cloud.

  2. Cree un proyecto watsonx.ai.

    Puede obtener el ID de su proyecto desde su proyecto. Haga clic en la pestaña Administrar . A continuación, copie el ID del proyecto de la sección Detalles de la página General . Necesita este ID para este tutorial.

  3. Cree un Jupyter Notebook.

    Este paso abre un entorno de Jupyter Notebook donde puede copiar el código de este tutorial. También puede descargar este notebook en su sistema local y cargarlo en su proyecto watsonx.ai como activo. Este tutorial también está disponible en GitHub.

Paso 2. Configure una instancia de tiempo de ejecución de watsonx.ai y una clave de API

  1. Cree una instancia de servicio de watsonx.ai Runtime (seleccione la región adecuada y elija el plan Lite, que es una instancia gratuita).

  2. Genere una clave de interfaz de programación de aplicaciones (API).

  3. Asocie la instancia del servicio watsonx.ai Runtime con el proyecto que ha creado en watsonx.ai.

Paso 3. Instale e importe las bibliotecas relevantes y configure sus credenciales

Necesitamos unas pocas bibliotecas y módulos para este tutorial. Asegúrese de importar los siguientes y, si no están instalados, una instalación rápida de pip resuelve el problema.

#installations
%pip install -qU ibm-watsonx-ai \
    langchain-ibm \
    langgraph \
    langchain_experimental \
    tavily-python \
    langchain-community

Reinicie su terminal e importe los siguientes paquetes.

# imports
import sqlite3
import getpass

from ibm_watsonx_ai import APIClient, Credentials
from ibm_watsonx_ai.foundation_models.moderations import Guardian
from IPython.display import Image, display
from langchain import hub  
from langchain_ibm import ChatWatsonx
from langgraph.graph.message import add_messages
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_community.utilities.sql_database import SQLDatabase
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage, AIMessage
from langchain_core.tools import tool
from langchain_experimental.tools.python.tool import PythonREPLTool
from sqlalchemy import create_engine
from typing_extensions import TypedDict
from typing import Annotated

Para establecer nuestras credenciales, necesitaremos el WATSONX_APIKEY y WATSONX_PROJECT_ID que generó en el Paso 1. También estableceremos la URL que sirva como el endpoint de la API.

WATSONX_APIKEY = getpass.getpass("Please enter your watsonx.ai Runtime API key (hit enter): ")

WATSONX_PROJECT_ID = getpass.getpass("Please enter your project ID (hit enter): ")

URL = "https://us-south.ml.cloud.ibm.com"

Antes de que podamos inicializar nuestro LLM, podemos usar la clase Credentials para encapsular nuestras credenciales de API pasadas.

credentials = Credentials(url=URL, api_key=WATSONX_APIKEY)

Paso 4. Ejemplificar el modelo de chat

Para poder interactuar con todos los recursos disponibles en watsonx.ai Runtime, debe configurar un APIClient. Aquí, introducimos nuestras credenciales y WATSONX_PROJECT_ID.

client = APIClient(credentials=credentials, project_id=WATSONX_PROJECT_ID)

Para este tutorial, utilizaremos el contenedor ChatWatsonx para configurar nuestro modelo de chat. Este contenedor simplifica la integración de la llamada y el encadenamiento de herramientas. Le recomendamos que utilice las referencias de la API en los documentos oficiales de ChatWatsonx para obtener más información. Podemos introducir nuestro model_id para Mistral Medium 3 y nuestro cliente como parámetros.

Tenga en cuenta que, si usa otro proveedor de API, tendrá que cambiar el contenedor en consecuencia. Por ejemplo, para usar la API de OpenAI y acceder a modelos como GPT-4, necesitará un openai_api_key además del contenedor ChatOpenAI.

model_id = "mistralai/mistral-medium-2505" 
llm = ChatWatsonx(model_id=model_id, watsonx_client=client)

Paso 5. Configure la base de datos SQLite

En este tutorial, su agente utilizará sus herramientas disponibles para interactuar con una base de datos SQLite. Si ya tiene un conjunto de datos almacenado en un sistema de gestión de bases de datos relacionales como PostgreSQL o SQLite, puede omitir este paso. De lo contrario, ejecute la siguiente celda para establecer la variable sql_script igual a un script que genera una base de datos que contiene datos sintéticos de ventas de concesionarios de automóviles. Esta base de datos contendrá varias tablas para almacenar datos individuales de concesionarios, ventas y vehículos.

sql_script = """
DROP TABLE IF EXISTS [Dealerships];

DROP TABLE IF EXISTS [Cars];

DROP TABLE IF EXISTS [Sales];

CREATE TABLE [Dealerships] (
    [id] INTEGER PRIMARY KEY,
    [name] TEXT NOT NULL,
    [location] TEXT NOT NULL
);

CREATE TABLE [Cars] (
    [id] INTEGER PRIMARY KEY,
    [make] TEXT NOT NULL,
    [model] TEXT NOT NULL,
    [year] INTEGER NOT NULL,
    [color] TEXT NOT NULL
);

CREATE TABLE [Sales] (
    [id] INTEGER PRIMARY KEY,
    [car_id] INTEGER NOT NULL,
    [dealership_id] INTEGER NOT NULL,
    [sale_date] DATE NOT NULL,
    [sale_price] REAL NOT NULL,
    FOREIGN KEY ([car_id]) REFERENCES [Cars] ([id]),
    FOREIGN KEY ([dealership_id]) REFERENCES [Dealerships] ([id])
);


INSERT INTO [Dealerships] ([id], [name], [location]) VALUES
    (1, 'Dealership A', 'New York'),
    (2, 'Dealership B', 'Los Angeles'),
    (3, 'Dealership C', 'Chicago'),
    (4, 'Dealership D', 'Houston'),
    (5, 'Dealership E', 'Phoenix'),
    (6, 'Dealership F', 'Philadelphia'),
    (7, 'Dealership G', 'San Antonio'),
    (8, 'Dealership H', 'San Diego'),
    (9, 'Dealership I', 'Dallas'),
    (10, 'Dealership J', 'San Jose');

INSERT INTO [Cars] ([id], [make], [model], [year], [color]) VALUES
    (1, 'Toyota', 'Camry', 2020, 'Blue'),
    (2, 'Honda', 'Civic', 2019, 'Red'),
    (3, 'Ford', 'Mustang', 2021, 'Black'),
    (4, 'Chevrolet', 'Silverado', 2018, 'White'),
    (5, 'Nissan', 'Altima', 2020, 'Gray'),
    (6, 'Kia', 'Optima', 2020, 'Silver'),
    (7, 'Hyundai', 'Elantra', 2019, 'Black'),
    (8, 'Volkswagen', 'Golf', 2021, 'Red'),
    (9, 'BMW', '3 Series', 2018, 'White'),
    (10, 'Mercedes-Benz', 'C-Class', 2020, 'Gray'),
    (11, 'Audi', 'A4', 2019, 'Blue'),
    (12, 'Lexus', 'ES', 2021, 'Black'),
    (13, 'Toyota', 'Corolla', 2018, 'White'),
    (14, 'Honda', 'Accord', 2020, 'Gray'),
    (15, 'Ford', 'Fusion', 2019, 'Red');

INSERT INTO [Sales] ([id], [car_id], [dealership_id], [sale_date], [sale_price]) VALUES
    (1, 1, 1, '2022-01-01', 25000.0),
    (2, 2, 2, '2022-02-01', 20000.0),
    (3, 3, 3, '2022-03-01', 30000.0),
    (4, 4, 1, '2022-04-01', 40000.0),
    (5, 5, 2, '2022-05-01', 28000.0),
    (6, 6, 4, '2022-06-01', 22000.0),
    (7, 7, 5, '2022-07-01', 20000.0),
    (8, 8, 6, '2022-08-01', 28000.0),
    (9, 9, 7, '2022-09-01', 35000.0),
    (10, 10, 8, '2022-10-01', 32000.0),
    (11, 11, 9, '2022-11-01', 30000.0),
    (12, 12, 10, '2022-12-01', 38000.0),
    (13, 13, 1, '2023-01-01', 25000.0),
    (14, 14, 2, '2023-02-01', 28000.0),
    (15, 15, 3, '2023-03-01', 22000.0);
    
"""

Para ejecutar este script y crear la base de datos local SQLite, necesitamos configurar un objeto de base de datos Connection. Al pasar el nombre de la ruta ":memory:", podemos conectarnos a una base de datos transitoria en memoria.

connection = sqlite3.connect(":memory:")

A continuación, podemos utilizar el método executescript para crear un cursor de base de datos y ejecutar nuestro script SQL. Este objeto Cursor nos permite ejecutar dichos scripts.

connection.executescript(sql_script)

Resultado:

<sqlite3.Cursor at 0x30c511240>

Por último, ejecute el siguiente comando para crear una instancia del motor SQLAlechemy con una conexión al archivo de base de datos de su directorio de trabajo. El archivo debe tener el mismo nombre que su Jupyter Notebook con una extensión de archivo de base de datos. Podemos mantener la URL vacía para que la conexión se realice a la base de datos local SQLite. Podemos proporcionar nuestro objeto Connection en el parámetro creator. Este parámetro es responsable de crear la conexión con la base de datos.

engine = create_engine(
    "sqlite://",
    creator=lambda: connection
)

Si prefiere usar una base de datos alternativa para este tutorial que no se almacene localmente, simplemente reemplace la ruta vacía por "sqlite:///Chinook.db" para acceder a una base de datos de ejemplo de una tienda de medios digitales.

Paso 6. Configure las herramientas

Este tutorial utiliza el SQLDatabaseToolkit prediseñado de LangChain. El kit de herramientas requiere un objeto SQLDatabase, que toma el motor SQLAlchemy como parámetro, y el LLM elegido. Una vez instanciado el kit de herramientas, podemos recuperar sus herramientas. Escribamos las herramientas para ver más detalles.

db = SQLDatabase(engine)
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()
tools

Resultado:

[QuerySQLDatabaseTool(description="Input to this tool is a detailed and correct SQL query, output is a result from the database. If the query is not correct, an error message will be returned. If an error is returned, rewrite the query, check the query, and try again. If you encounter an issue with Unknown column 'xxxx' in 'field list', use sql_db_schema to query the correct table fields.", db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x30c3ecd10>),
InfoSQLDatabaseTool(description='Input to this tool is a comma-separated list of tables, output is the schema and sample rows for those tables. Be sure that the tables actually exist by calling sql_db_list_tables first! Example Input: table1, table2, table3', db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x30c3ecd10>),
ListSQLDatabaseTool(db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x30c3ecd10>),
QuerySQLCheckerTool(description='Use this tool to double check if your query is correct before executing it. Always use this tool before executing a query with sql_db_query!', db=<langchain_community.utilities.sql_database.SQLDatabase object at 0x30c3ecd10>, llm=ChatWatsonx(model_id='mistralai/mistral-medium-2505', apikey=SecretStr('**********'), params={}, watsonx_model=<ibm_watsonx_ai.foundation_models.inference.model_inference.ModelInference object at 0x309c34690>, watsonx_client=<ibm_watsonx_ai.client.APIClient object at 0x30c3e3250>), llm_chain=LLMChain(verbose=False, prompt=PromptTemplate(input_variables=['dialect', 'query'], input_types={}, partial_variables={}, template='\n{query}\nDouble check the {dialect} query above for common mistakes, including:\n- Using NOT IN with NULL values\n- Using UNION when UNION ALL should have been used\n- Using BETWEEN for exclusive ranges\n- Data type mismatch in predicates\n- Properly quoting identifiers\n- Using the correct number of arguments for functions\n- Casting to the correct data type\n- Using the proper columns for joins\n\nIf there are any of the above mistakes, rewrite the query. If there are no mistakes, just reproduce the original query.\n\nOutput the final SQL query only.\n\nSQL Query: '), llm=ChatWatsonx(model_id='mistralai/mistral-medium-2505', apikey=SecretStr('**********'), params={}, watsonx_model=<ibm_watsonx_ai.foundation_models.inference.model_inference.ModelInference object at 0x309c34690>, watsonx_client=<ibm_watsonx_ai.client.APIClient object at 0x30c3e3250>), output_parser=StrOutputParser(), llm_kwargs={}))]

Podemos ver que hay cuatro herramientas disponibles como parte del kit de herramientas. Cada herramienta tiene su propio propósito, como se ve en las descripciones de las herramientas. Las herramientas están diseñadas para listar las bases de datos, ejecutar consultas, devolver esquemas de tablas y verificar las consultas SQL antes de ejecutarlas.

Para dotar a nuestro agente de la capacidad de generar y ejecutar código Python, podemos aprovechar la clase PythonREPLTool de LangChain. Este código configura una herramienta REPL (bucle de lectura-evaluación-impresión) de Python, define su funcionalidad y la añade a una lista de herramientas para su uso posterior.

python_repl = PythonREPLTool()
tools.append(python_repl)

Para ayudar a garantizar que nuestro agente reciba una instrucción del sistema adaptada a las herramientas y bases de datos SQL, podemos extraer una instrucción proporcionada por el LangChain Hub. Escribamos el system_message para ver la instrucción en detalle.

chatprompttemplate = hub.pull("langchain-ai/sql-agent-system-prompt")  
system_message = chatprompttemplate.format(dialect="SQLite", top_k=5)  
print(system_message)

Resultado:

System: You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct SQLite query to run, then look at the results of the query and return the answer.
Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most 5 results.
You can order the results by a relevant column to return the most interesting examples in the database.
Never query for all the columns from a specific table, only ask for the relevant columns given the question.
You have access to tools for interacting with the database.
Only use the below tools. Only use the information returned by the below tools to construct your final answer.
You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.
    
DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.
    
To start you should ALWAYS look at the tables in the database to see what you can query.
Do NOT skip this step.
Then you should query the schema of the most relevant tables.

Paso 7. Defina el estado del agente

Hay una función prediseñada create_react_agent disponible en LangGraph que genera un gráfico de agentes que sigue la arquitectura ReAct (razonamiento y acción). Esta arquitectura permite al agente llamar a las herramientas iterativamente en bucle hasta que se cumpla una condición de parada.

Diagrama de flujo del agente React Flujo de agente ReAct

Para proporcionar un enfoque más práctico, crearemos un agente ReAct desde cero en este tutorial. Como primer paso, podemos crear una clase AgentState para almacenar el contexto de los mensajes del usuario, las herramientas y el propio agente. La clase TypedDict de Python se utiliza aquí para ayudar a garantizar que los mensajes estén en el formato de diccionario adecuado. También podemos usar la función reductora de add_messages de LangGraph para añadir cualquier mensaje nuevo a la lista existente de mensajes.

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

Paso 8. Defina la clase ReActAgent

A continuación, podemos crear la clase ReActAgent. Esta clase proporciona una estructura básica para crear agentes que puedan reaccionar a los cambios de su entorno. Las funciones de la clase ReActAgent permiten la llamada iterativa a herramientas en respuesta al estado del grafo.

La función __init__ inicializa los atributos de la clase con el modelo de lenguaje de gran tamaño, las herramientas y el mensaje del sistema como parámetros. Este constructor crea un gráfico de estado con nodos para el modelo de guardián, el LLM y las herramientas. El gráfico comienza en el nodo guardian, que llama al método guardian_moderation para detectar contenidos nocivos antes de que lleguen al LLM y a la base de datos. El borde condicional entre los nodos guardian y llm dirige el estado del gráfico al nodo llm o al final. Esto lo determina la salida de la función guardian_moderation. Los mensajes seguros se pasan al nodo llm, que ejecuta el método call_llm. También añadimos un borde condicional entre los nodos llm y tools para encaminar los mensajes adecuadamente. Si el LLM devuelve una llamada a herramienta, el método should_call_tools devuelve un booleano True. De lo contrario, se devuelve False y el gráfico se dirige al final. Este paso forma parte de la arquitectura del agente ReAct, queremos que el agente reciba la salida de la herramienta y luego reaccione al cambio de estado para determinar su próxima acción.

A continuación, podemos compilar el gráfico, que nos permitirá invocar al agente en un paso posterior. Para conservar los mensajes, podemos utilizar el checkpointer MemorySaver. Las dos últimas líneas de este método inicializan los atributos tools y llm de la instancia de la clase. El atributo herramientas es un diccionario que asigna nombres de herramientas a objetos de herramientas. El atributo llm es el LLM, que está vinculado a las herramientas mediante el método bind_tools.

class ReActAgent:
    
    def __init__(self, llm, tools, system_message=""):
        memory = MemorySaver()
        graph = StateGraph(AgentState)
        graph.add_node("guardian", self.guardian_moderation)
        graph.add_node("llm", self.call_llm)
        graph.add_node("tools", self.call_tools)
        graph.add_node("block_message", self.block_message)
        graph.add_conditional_edges(
            "guardian",
            lambda state: state["moderation_verdict"],  
            {
                "inappropriate": "block_message",  
                "safe": "llm"           
            }
        )
        graph.add_edge("block_message", END)
        graph.add_conditional_edges(
            "llm",
            self.should_call_tools,
            ["tools", END]
        )
        graph.add_edge("tools", "llm")
        graph.add_edge(START, "guardian")
        self.system_message = system_message
        self.graph = graph.compile(checkpointer=memory)
        self.tools = {t.name: t for t in tools}
        self.llm = llm.bind_tools(tools)

La siguiente función de la clase ReActAgent es call_llm. Esta función llama al LLM recuperando los mensajes del estado. Si hay un mensaje del sistema, el método lo añade al principio de la lista de mensajes. A continuación, se invoca el LLM con los mensajes y se devuelve un nuevo estado con una respuesta LLM.

def call_llm(self, state: AgentState):
    messages = state['messages']
    if self.system_message:
        messages = [SystemMessage(content=self.system_message)] + messages
    message = self.llm.invoke(messages)
    return {'messages': [message]}

La función call_tools es la siguiente en la clase ReActAgent. Este método recupera las llamadas a herramientas del último mensaje en el estado, las itera e invoca cada herramienta con los argumentos dados. A continuación, los resultados de cada llamada a la herramienta se almacenan en una lista llamada results. Por último, este nuevo estado se devuelve en forma de diccionario, donde la clave de los mensajes corresponde a la lista de resultados.

def call_tools(self, state: AgentState):
    tool_calls = state['messages'][-1].tool_calls
    results = []
    for t in tool_calls:
        result = self.tools[t['name']].invoke(t['args'])
        results.append(ToolMessage(tool_call_id=t['id'], 
                                    name=t['name'], 
                                    content=str(result)))
    return {'messages': results}

La siguiente función de la clase ReActAgent es should_call_tools. Esta función determina si se debe llamar a las herramientas en función del estado recuperando la respuesta LLM anterior del estado y comprobando si contiene alguna llamada a herramienta.

def should_call_tools(self, state: AgentState):
    result = state['messages'][-1]
    return "tools" if len(result.tool_calls) > 0 else END

La función guardian_moderation ejecutada en el nodo guardrain está diseñada para moderar mensajes mediante un sistema guardian, con el propósito de detectar y bloquear contenido no deseado o sensible. En primer lugar, se recupera el último mensaje. A continuación, se define un diccionario llamado detectors, que contiene las configuraciones de los detectores y sus valores umbral. Estos detectores identifican tipos específicos de contenido en los mensajes, como información de identificación personal (PII), así como discurso de odio, lenguaje abusivo y blasfemia (HAP). Luego, se crea una instancia de la clase Guardian, pasando un objeto api_client llamado client y el diccionario de detectors. Se llama al método detect de la instancia de Guardian, pasando el contenido del último mensaje y el diccionario de detectors. El método devuelve un diccionario en el que la clave moderation_verdict almacena un valor de "seguro" o "inapropiado", según la salida del modelo Granite Guardian.

def guardian_moderation(self, state: AgentState):
    message = state['messages'][-1]  
    detectors = {
        "granite_guardian": {"threshold": 0.4},
        "hap": {"threshold": 0.4},
        "pii": {},
    }
    guardian = Guardian(
        api_client=client,  
        detectors=detectors 
    )
    response = guardian.detect(
        text=message.content,
        detectors=detectors
    )
    if len(response['detections']) != 0 and response['detections'][0]['detection'] == "Yes":
        return {"moderation_verdict": "inappropriate"}
    else:
        return {"moderation_verdict": "safe"}

La función block_message sirve como mecanismo de notificación, informando al usuario de que su consulta de entrada contiene contenido inapropiado y ha sido bloqueada.

def block_message(self, state: AgentState):
    return {"messages": [AIMessage(content="This message has been blocked due to inappropriate content.")]}

Ahora podemos juntar todo este código y ejecutar la siguiente celda.

class ReActAgent:
    
    def __init__(self, llm, tools, system_message=""):
        memory = MemorySaver()
        graph = StateGraph(AgentState)
        graph.add_node("guardian", self.guardian_moderation)
        graph.add_node("llm", self.call_llm)
        graph.add_node("tools", self.call_tools)
        graph.add_node("block_message", self.block_message)
        graph.add_conditional_edges(
            "guardian",
            lambda state: state["moderation_verdict"],  
            {
                "inappropriate": "block_message",  
                "safe": "llm"           
            }
        )
        graph.add_edge("block_message", END)
        graph.add_conditional_edges(
            "llm",
            self.should_call_tools,
            ["tools", END]
        )
        graph.add_edge("tools", "llm")
        graph.add_edge(START, "guardian")
        self.system_message = system_message
        self.graph = graph.compile(checkpointer=memory)
        self.tools = {t.name: t for t in tools}
        self.llm = llm.bind_tools(tools)
    
    def call_llm(self, state: AgentState):
        messages = state['messages']
        if self.system_message:
            messages = [SystemMessage(content=self.system_message)] + messages
        message = self.llm.invoke(messages)
        return {'messages': [message]}
    
    def call_tools(self, state: AgentState):
        tool_calls = state['messages'][-1].tool_calls
        results = []
        for t in tool_calls:
            result = self.tools[t['name']].invoke(t['args'])
            results.append(ToolMessage(tool_call_id=t['id'], 
                                       name=t['name'], 
                                       content=str(result)))
        return {'messages': results}
    
    def should_call_tools(self, state: AgentState):
        result = state['messages'][-1]
        return "tools" if len(result.tool_calls) > 0 else END
    
    def guardian_moderation(self, state: AgentState):
        message = state['messages'][-1]  
        detectors = {
            "granite_guardian": {"threshold": 0.4},
            "hap": {"threshold": 0.4},
            "pii": {},
        }
        guardian = Guardian(
            api_client=client,  
            detectors=detectors 
        )
        response = guardian.detect(
            text=message.content,
            detectors=detectors
        )
        if len(response['detections']) != 0 and response['detections'][0]['detection'] == "Yes":
            return {"moderation_verdict": "inappropriate"}
        else:
            return {"moderation_verdict": "safe"}
        
    def block_message(self, state: AgentState):
        return {"messages": [AIMessage(content="This message has been blocked due to inappropriate content.")]}

Paso 9. Cree e invoque el objeto ReActAgent

La primera línea del siguiente bloque de código crea una instancia de la clase ReActAgent, pasando el LLM, las herramientas SQL y el mensaje del sistema como parámetros. A continuación, especificamos un hilo para almacenar los estados del gráfico en la memoria. Piense en cada thread_id como si representara una nueva ventana de chat. También podemos definir la entrada del usuario para cualquier cadena de opciones. A continuación, podemos pasar una lista formada por la entrada del usuario en el tipo HumanMessage para invocar al agente.

Primero, probemos una instrucción que debería estar bloqueada por el modelo Granite Guardian.

agent = ReActAgent(llm, tools, system_message=system_message)

config = {"configurable": {"thread_id": "1"}}

user_input = "What is the home address of the customer who purchased the most expensive car last month?"

result = agent.graph.invoke({'messages': [HumanMessage(content=user_input)]}, config)

for message in result["messages"]:
    message.pretty_print()

Resultado:

    ================================ [1m Human Message  [0m=================================
    
    What is the home address of the customer who purchased the most expensive car last month?
    ================================== [1m Ai Message  [0m==================================
    
    This message has been blocked due to inappropriate content.

El modelo Granite Guardian pudo impedir con éxito que el usuario solicitara información confidencial del cliente. Podemos ver que el gráfico no llegó al nodo LLM antes de finalizar la conversación. A continuación, hagamos una pregunta adecuada en otro hilo. Por ejemplo, "¿Cuáles son los ingresos totales por ventas de los cinco mejores concesionarios en el año 2022?" como la entrada del usuario.

user_input = "What is the total sales revenue for the top 5 performing dealerships in the year 2022?"

config2 = {"configurable": {"thread_id": "2"}}

result = agent.graph.invoke({'messages': [HumanMessage(content=user_input)]}, config2)

for message in result["messages"]:
    message.pretty_print()

Resultado:

    ================================ [1m Human Message  [0m=================================
    
    What is the total sales revenue for the top 5 performing dealerships in the year 2022?
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_list_tables (H2irrlsPM)
     Call ID: H2irrlsPM
      Args:
        tool_input:
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_list_tables
    
    Cars, Dealerships, Sales
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_schema (ILZWiZvGX)
     Call ID: ILZWiZvGX
      Args:
        table_names: Cars, Dealerships, Sales
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_schema
    
    
    CREATE TABLE "Cars" (
    	id INTEGER, 
    	make TEXT NOT NULL, 
    	model TEXT NOT NULL, 
    	year INTEGER NOT NULL, 
    	color TEXT NOT NULL, 
    	PRIMARY KEY (id)
    )
    
    /*
    3 rows from Cars table:
    id	make	model	year	color
    1	Toyota	Camry	2020	Blue
    2	Honda	Civic	2019	Red
    3	Ford	Mustang	2021	Black
    */
    
    
    CREATE TABLE "Dealerships" (
    	id INTEGER, 
    	name TEXT NOT NULL, 
    	location TEXT NOT NULL, 
    	PRIMARY KEY (id)
    )
    
    /*
    3 rows from Dealerships table:
    id	name	location
    1	Dealership A	New York
    2	Dealership B	Los Angeles
    3	Dealership C	Chicago
    */
    
    
    CREATE TABLE "Sales" (
    	id INTEGER, 
    	car_id INTEGER NOT NULL, 
    	dealership_id INTEGER NOT NULL, 
    	sale_date DATE NOT NULL, 
    	sale_price REAL NOT NULL, 
    	PRIMARY KEY (id), 
    	FOREIGN KEY(dealership_id) REFERENCES "Dealerships" (id), 
    	FOREIGN KEY(car_id) REFERENCES "Cars" (id)
    )
    
    /*
    3 rows from Sales table:
    id	car_id	dealership_id	sale_date	sale_price
    1	1	1	2022-01-01	25000.0
    2	2	2	2022-02-01	20000.0
    3	3	3	2022-03-01	30000.0
    */
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_query_checker (yIZ0tk4VP)
     Call ID: yIZ0tk4VP
      Args:
        query: SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5;
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_query_checker
    
    SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id
    WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5; ================================= [1m Mensaje de IA [0m========== ======================== Llamadas a la herramienta: sql_db_query (cTzJFfvTl) ID de llamada: cTzJFfvTl Args: consulta: SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5;
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_query
    
    [('Dealership A', 65000.0), ('Dealership B', 48000.0), ('Dealership J', 38000.0), ('Dealership G', 35000.0), ('Dealership H', 32000.0)]
    ================================== [1m Ai Message  [0m==================================
    
    The total sales revenue for the top 5 performing dealerships in the year 2022 are:
    
    1. Dealership A: $65,000
    2. Dealership B: $48,000
    3. Dealership J: $38,000
    4. Dealership G: $35,000
    5. Dealership H: $32,000

    ================================ [1m Human Message  [0m=================================
    
    What is the total sales revenue for the top 5 performing dealerships in the year 2022?
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_list_tables (H2irrlsPM)
     Call ID: H2irrlsPM
      Args:
        tool_input:
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_list_tables
    
    Cars, Dealerships, Sales
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_schema (ILZWiZvGX)
     Call ID: ILZWiZvGX
      Args:
        table_names: Cars, Dealerships, Sales
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_schema
    
    
    CREATE TABLE "Cars" (
    	id INTEGER, 
    	make TEXT NOT NULL, 
    	model TEXT NOT NULL, 
    	year INTEGER NOT NULL, 
    	color TEXT NOT NULL, 
    	PRIMARY KEY (id)
    )
    
    /*
    3 rows from Cars table:
    id	make	model	year	color
    1	Toyota	Camry	2020	Blue
    2	Honda	Civic	2019	Red
    3	Ford	Mustang	2021	Black
    */
    
    
    CREATE TABLE "Dealerships" (
    	id INTEGER, 
    	name TEXT NOT NULL, 
    	location TEXT NOT NULL, 
    	PRIMARY KEY (id)
    )
    
    /*
    3 rows from Dealerships table:
    id	name	location
    1	Dealership A	New York
    2	Dealership B	Los Angeles
    3	Dealership C	Chicago
    */
    
    
    CREATE TABLE "Sales" (
    	id INTEGER, 
    	car_id INTEGER NOT NULL, 
    	dealership_id INTEGER NOT NULL, 
    	sale_date DATE NOT NULL, 
    	sale_price REAL NOT NULL, 
    	PRIMARY KEY (id), 
    	FOREIGN KEY(dealership_id) REFERENCES "Dealerships" (id), 
    	FOREIGN KEY(car_id) REFERENCES "Cars" (id)
    )
    
    /*
    3 rows from Sales table:
    id	car_id	dealership_id	sale_date	sale_price
    1	1	1	2022-01-01	25000.0
    2	2	2	2022-02-01	20000.0
    3	3	3	2022-03-01	30000.0
    */
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_query_checker (yIZ0tk4VP)
     Call ID: yIZ0tk4VP
      Args:
        query: SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5;
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_query_checker
    
    SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id
    WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5; ================================= [1m Mensaje de IA [0m========== ======================== Llamadas a la herramienta: sql_db_query (cTzJFfvTl) ID de llamada: cTzJFfvTl Args: consulta: SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5;
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_query
    
    [('Dealership A', 65000.0), ('Dealership B', 48000.0), ('Dealership J', 38000.0), ('Dealership G', 35000.0), ('Dealership H', 32000.0)]
    ================================== [1m Ai Message  [0m==================================
    
    The total sales revenue for the top 5 performing dealerships in the year 2022 are:
    
    1. Dealership A: $65,000
    2. Dealership B: $48,000
    3. Dealership J: $38,000
    4. Dealership G: $35,000
    5. Dealership H: $32,000

¡Genial! El agente ha ejecutado con éxito una serie de tareas, incluida la llamada a varias herramientas para extraer el esquema de la tabla SQL, la generación de nuevas consultas SQL y la verificación de su corrección antes de ejecutarlas. Como resultado, el agente devuelve los ingresos totales de ventas correctos para los cinco concesionarios de automóviles con mejor rendimiento dentro de nuestra base de datos en 2022. Podemos ver el razonamiento de varios pasos del agente a medida que imprime cada SQL query generada. En la respuesta, también vemos que el modelo de Granite Guardian determinó que la consulta del usuario es apropiada.

Hagamos una pregunta de seguimiento. Esta vez vamos a pedir al agente que genere y ejecute código en Python para mostrar una representación en un gráfico de barras de los resultados. El agente debería recuperar los mensajes anteriores, ya que estamos utilizando el mismo thread_id.

user_input = "Write Python code to plot these results on a bar graph. Then, you must execute the code and display the bar graph."

result = agent.graph.invoke({'messages': [HumanMessage(content=user_input)]}, config2)

for message in result["messages"]:
    message.pretty_print()

Resultado:

================================ [1m Human Message  [0m=================================
    
    What is the total sales revenue for the top 5 performing dealerships in the year 2022?
================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_list_tables (H2irrlsPM)
     Call ID: H2irrlsPM
      Args:
        tool_input:
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_list_tables
    
    Cars, Dealerships, Sales
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_schema (ILZWiZvGX)
     Call ID: ILZWiZvGX
      Args:
        table_names: Cars, Dealerships, Sales
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_schema
    
    
    CREATE TABLE "Cars" (
    	id INTEGER, 
    	make TEXT NOT NULL, 
    	model TEXT NOT NULL, 
    	year INTEGER NOT NULL, 
    	color TEXT NOT NULL, 
    	PRIMARY KEY (id)
    )
    
    /*
    3 rows from Cars table:
    id	make	model	year	color
    1	Toyota	Camry	2020	Blue
    2	Honda	Civic	2019	Red
    3	Ford	Mustang	2021	Black
    */
    
    
    CREATE TABLE "Dealerships" (
    	id INTEGER, 
    	name TEXT NOT NULL, 
    	location TEXT NOT NULL, 
    	PRIMARY KEY (id)
    )
    
    /*
    3 rows from Dealerships table:
    id	name	location
    1	Dealership A	New York
    2	Dealership B	Los Angeles
    3	Dealership C	Chicago
    */
    
    
    CREATE TABLE "Sales" (
    	id INTEGER, 
    	car_id INTEGER NOT NULL, 
    	dealership_id INTEGER NOT NULL, 
    	sale_date DATE NOT NULL, 
    	sale_price REAL NOT NULL, 
    	PRIMARY KEY (id), 
    	FOREIGN KEY(dealership_id) REFERENCES "Dealerships" (id), 
    	FOREIGN KEY(car_id) REFERENCES "Cars" (id)
    )
    
    /*
    3 rows from Sales table:
    id	car_id	dealership_id	sale_date	sale_price
    1	1	1	2022-01-01	25000.0
    2	2	2	2022-02-01	20000.0
    3	3	3	2022-03-01	30000.0
    */
    ================================== [1m Ai Message  [0m==================================
    Tool Calls:
      sql_db_query_checker (yIZ0tk4VP)
     Call ID: yIZ0tk4VP
      Args:
        query: SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5;
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_query_checker
    
    SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id
    WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5; ================================= [1m Mensaje de IA [0m========== ======================== Llamadas a la herramienta: sql_db_query (cTzJFfvTl) ID de llamada: cTzJFfvTl Args: consulta: SELECT Dealerships.name, SUM(Sales.sale_price) AS total_sales_revenue FROM Dealerships INNER JOIN Sales ON Dealerships.id = Sales.dealership_id WHERE strftime('%Y', Sales.sale_date) = '2022' GROUP BY Dealerships.id ORDER BY total_sales_revenue DESC LIMIT 5;
    ================================= [1m Tool Message  [0m=================================
    Name: sql_db_query
    
    [('Dealership A', 65000.0), ('Dealership B', 48000.0), ('Dealership J', 38000.0), ('Dealership G', 35000.0), ('Dealership H', 32000.0)]
================================== [1m Ai Message  [0m==================================
    
    The total sales revenue for the top 5 performing dealerships in the year 2022 are:
    
    1. Dealership A: $65,000
    2. Dealership B: $48,000
    3. Dealership J: $38,000
    4. Dealership G: $35,000
    5. Dealership H: $32,000
    ================================ [1m Human Message  [0m=================================
    
    Write Python code to plot these results on a bar graph. Then, you must execute the code and display the bar graph.
================================== [1m Ai Message  [0m==================================
    Tool Calls:
      Python_REPL (5X1fPIODL)
     Call ID: 5X1fPIODL
      Args:
        query: import matplotlib.pyplot as plt
    
    # Data for total sales revenue
    dealerships = ['Dealership A', 'Dealership B', 'Dealership J', 'Dealership G', 'Dealership H']
    sales_revenue = [65000, 48000, 38000, 35000, 32000]
    
    # Create a bar graph
    plt.bar(dealerships, sales_revenue, color='blue')
    
    # Customize the plot
    plt.title('Total Sales Revenue for Top 5 Performing Dealerships in 2022')
    plt.xlabel('Dealerships')
    plt.ylabel('Total Sales Revenue ($)')
    plt.xticks(rotation=45)
    plt.grid(axis='y')
    
    # Display the plot
    plt.tight_layout()
    plt.show()
    ================================= [1m Tool Message  [0m=================================
    Name: Python_REPL
    
    
    ================================== [1m Ai Message  [0m==================================
    
    Your requirements have been fulfilled. Code has been executed in repl.
    The bar graph has been plotted and shown in the python REPL.
    The graph can also be seen below:
Ingresos totales por ventas de los cinco mejores concesionarios en 2022

Como era de esperar, el agente llamó con éxito a la herramienta python_repl para generar y ejecutar código Python, lo que resultó en una representación gráfica de los resultados. Observe que el agente también pudo determinar los valores adecuados de los ejes x e y, las etiquetas y el título. Esto pone de relieve el razonamiento agéntico que distingue a los agentes de IA de los chatbots LLM tradicionales.

Para obtener una representación visual del gráfico del agente, podemos mostrar el flujo del gráfico.

display(Image(agent.graph.get_graph().draw_mermaid_png()))

Resultado:

Diagrama de visualización de gráficos de agentes Visualización de gráficos de agentes LangGraph

Resumen

En este tutorial, exploramos cómo crear un agente ReAct de texto a SQL con LangGraph. En lugar de utilizar la función ejecutora de agente precompilada, create_react_agent, construimos nuestro agente desde cero. Después de analizar la pregunta de un usuario en lenguaje natural, nuestro agente de inteligencia artificial utiliza sus herramientas para generar, validar y ejecutar consultas SQL, demostrando su destreza tanto en SQL como en Python. Dada la clase PythonREPLTool de LangChain, el agente está equipado con la capacidad no solo de generar sino también de ejecutar código Python. Hemos comprobado el correcto funcionamiento de esta herramienta al solicitar al agente una representación gráfica de su respuesta. El bucle condicional entre el LLM y los nodos de herramientas permite la arquitectura de agente ReAct. Con este tutorial, hemos exhibido las capacidades de codificación y razonamiento multimodal de Mistral Medium 3. Como siguiente paso, considere experimentar con nodos y aristas adicionales en el grafo.

Soluciones relacionadas
Desarrollo de agentes de IA de IBM 

Permita a los desarrolladores crear, implementar y monitorizar agentes de IA con el estudio IBM watsonx.ai.

 

Explore watsonx.ai
Soluciones de inteligencia artificial

Ponga la IA a trabajar en su negocio con la experiencia líder en IA del sector de IBM y junto a su cartera de soluciones.

Explore las soluciones de IA
Consultoría y servicios de IA

Reinvente las operaciones y flujos de trabajo críticos añadiendo IA para maximizar las experiencias, la toma de decisiones en tiempo real y el valor empresarial.

Explore los servicios de IA
Dé el siguiente paso

Tanto si opta por personalizar las aplicaciones y habilidades prediseñadas como si prefiere crear e implementar servicios agentivos personalizados mediante un estudio de IA, la plataforma IBM watsonx le ofrece todo lo que necesita.

Explore watsonx Orchestrate Explore watsonx.ai
Notas a pie de página

1 "Medium Is the New Large". Mistral.ai. 7 de mayo de 2025. mistral.ai/news/mistral-medium-3.