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

Autor

Anna Gutowska

AI Engineer, Developer Advocate

IBM

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

¿Qué es LangGraph?

LangGraph, creado por LangChain, es una infraestructura de agente de IA de código abierto diseñada para construir, desplegar 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 los LLM de forma escalable y eficiente. En esencia, LangGraph utiliza el poder de las arquitecturas basadas en grafos para modelar y gestionar las intrincadas relaciones entre varios componentes de un flujo de trabajo de agentes de IA, incluidos los componentes que se encuentran en sistemas multiagente.

IBM watsonx.ai

Insights de datos con LangGraph y watsonx.ai

¿Puede un agente de IA tomar nuestra consulta en lenguaje natural y hacer el procesamiento por nosotros para darnos ese resultado significativo? Utilizamos varias piezas de tecnología de código abierto y el poder de watsonx.ai para poner esto a prueba.

Requisitos previos

  1. Se necesita una cuenta de IBM Cloud para crear un proyecto en watsonx.ai .

  2. Varias versiones de Python pueden funcionar para este tutorial. En el momento de la publicación, recomendamos descargar la última versión de Python 3.13.

Pasos

Paso 1: Configurar el entorno

Si bien puede elegir entre varias herramientas, este tutorial lo guiará a través de cómo configurar una cuenta de IBM para usar un Jupyter Notebook.

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

  2. Cree un proyecto watsonx.ai.

    Puede obtener su ID de proyecto desde dentro de su proyecto. Haz clic en la pestaña Manage . Luego, copie el ID del proyecto de la sección Details 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. Configurar una instancia de watsonx.ai Runtime y una clave de API

  1. Cree una instancia de servicio 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 creó en watsonx.ai.

Paso 3. Instalar e importar las bibliotecas relevantes y configurar las credenciales

Necesitamos algunas 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 configurar nuestras credenciales, necesitaremos la WATSONX_APIKEY y WATSONX_PROJECT_ID que generamos en el paso 1. También estableceremos la URL para que sirva como endpoint de la API.

WATSONX_APIKEY = getpass.getpass("Enter your watsonx.ai Runtime API key (press enter): ")
WATSONX_PROJECT_ID = getpass.getpass("Enter your project ID (press 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.

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

Paso 4. Crear una instancia del modelo de chat

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

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

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

Tengamos en cuenta que, si usamos otro proveedor de API, tendremos que cambiar el wrapper en consecuencia. Por ejemplo, para utilizar la API de OpenAI para acceder a modelos como GPT-4, necesitarás una openai_api_key, así como el wrapper ChatOpenAI.

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

Paso 5. Configurar la base de datos SQLite

En este tutorial, el agente utilizará sus herramientas disponibles para interactuar con una base de datos SQLite. Si ya se tiene un conjunto de datos almacenado en un sistema de gestión de bases de datos relacionales, como PostgreSQL o SQLite, se puede omitir este paso. De lo contrario, ejecutamos 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 SQLite local, necesitamos configurar un objeto Connection de la base de datos. Al pasar el nombre de ruta ":memory:", podemos conectarnos a una base de datos transitoria en memoria.

conexión = sqlite3.connect(":memory:")

A continuación, podemos usar 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>

Finalmente, ejecutamos el siguiente comando para crear una instancia de SQLAlechemy Engine con una conexión al archivo de base de datos en 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 de SQLite. Podemos proporcionar nuestro objeto Connection en el parámetro creator. Este parámetro es responsable de crear la conexión a la base de datos.

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

Si se prefiere usar una base de datos alternativa para este tutorial que no esté almacenada localmente, simplemente se reemplaza la ruta vacía con "sqlite:///Chinook.db" para acceder a una muestra de base de datos de tienda de medios digitales.

Paso 6. Configurar las herramientas

Este tutorial utiliza el SQLDatabaseToolkit predefinido de LangChain. El kit de herramientas requiere un objeto SQLDatabase, que toma el SQLAlchemy Engine como parámetro y el LLM de elección. Una vez que creamos la instancia del kit de herramientas, podemos recuperar sus herramientas. Imprimamos las herramientas para ver más detalles.

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

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 4 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 equipar a nuestro agente con la capacidad de generar y ejecutar código Python, podemos aprovechar la clase PythonREPLTool de LangChain. Este código configura una herramienta Python REPL (Read-Eval-Print Loop), definiendo su funcionalidad y agregándola a una lista de herramientas para su uso posterior.

python_repl = PythonREPLTool()
tools.append(python_repl)

Para 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 LangChain Hub. Imprimamos 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:

Sistema: es un agente diseñado para interactuar con una SQL database.
Dada una pregunta de entrada, cree una consulta SQLite sintácticamente correcta para ejecutar, luego observe los resultados de la consulta y devuelva la respuesta.
A menos que el usuario especifique un número específico de ejemplos que desea obtener, limite siempre su consulta a un máximo de 5 resultados.
Puede ordenar los resultados por una columna relevante para obtener los ejemplos más interesantes de la base de datos.
Nunca consulte todas las columnas de una tabla específica, solo pregunte por las columnas relevantes dada la pregunta.
Tiene acceso a herramientas para interactuar con la base de datos.
Solo usa las herramientas de abajo. Utilice solo la información devuelta por las siguientes herramientas para construir su respuesta final.
DEBE revisar su consulta antes de ejecutarla. Si obtiene un error al ejecutar una consulta, reescriba la consulta y vuelve a intentarlo.
    
NO haga ninguna declaración DML (INSERT, UPDATE, DELETE, DROP, etc.) en la base de datos.
    
Para empezar, SIEMPRE debe mirar las tablas de la base de datos para ver qué puede consultar.
NO omita este paso.
Luego, debe consultar el esquema de las tablas más relevantes.

Paso 7. Definir el estado del agente

LangGraph dispone de una función prediseñada create_react_agent que genera un grafo de agentes que sigue la arquitectura ReAct (razonamiento y acción). Esta arquitectura permite al agente llamar iterativamente a las herramientas en un 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 garantizar que los mensajes estén en el formato de diccionario adecuado. También podemos utilizar la función reductora add_messages de LangGraph para agregar cualquier mensaje nuevo a la lista de mensajes existente.

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

Paso 8. Definir 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 en su entorno. Las funciones de la clase ReActAgent permiten llamadas iterativas a herramientas en respuesta al estado del grafo.

La función __init__ inicializa los atributos de la clase con el modelo de lenguaje grande, las herramientas y el mensaje del sistema como parámetros. Este constructor crea un grafo de estado con nodos para el modelo guardian, LLM y herramientas. El grafo comienza en el nodo guardian, que llama al método guardian_moderation para detectar contenido dañino antes de que llegue al LLM y a la base de datos. La arista condicional entre los nodos guardian y llm enruta el estado del grafo hacia el nodo llm o al final. Esto viene determinado por el resultado de la función guardian_moderation. Los mensajes seguros se pasan al nodo llm, que ejecuta el método call_llm. También agregamos una arista condicional entre los nodos llm y tools para enrutar los mensajes adecuadamente. Si LLM devuelve una llamada a una herramienta, el método should_call_tools devuelve un valor booleano True. De lo contrario, se devuelveFalse y el grafo se enruta hasta el final. Este paso forma parte de la arquitectura del agente ReAct: queremos que el agente reciba el resultado de la herramienta y luego reaccione al cambio de estado para determinar su siguiente acción.

A continuación, podemos compilar el grafo, lo que nos permite invocar al agente en un paso posterior. Para conservar los mensajes, podemos utilizar el verificador MemorySaver. Las dos últimas líneas de este método inicializan los atributos tools y llm de la instancia de clase. El atributo tools es un diccionario que asigna nombres de herramientas a objetos de herramientas. El atributo llm es el LLM, que está vinculado a las herramientas con 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 agrega al principio de la lista de mensajes. Luego se invoca el LLM con los mensajes y se devuelve un nuevo estado con una respuesta del 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. Finalmente, este nuevo estado se devuelve en forma de diccionario, donde la clave de mensajes se asigna 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 del LLM anterior del estado y comprobando si contiene alguna llamada a la 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 utilizando un sistema guardian, con el propósito de detectar y bloquear contenido no deseado o sensible. Primero, se recupera el último mensaje. A continuación, se define un diccionario denominado detectors, que contiene las configuraciones del detector 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 lenguaje 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 detectors. Se llama al método detect de la instancia Guardian, pasando el contenido del último mensaje y el diccionario detectors. Luego, el método devuelve un diccionario en el que la clave moderation_verdict almacena un valor de "seguro" o "inapropiado", dependiendo de 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 que su consulta de entrada posee 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. Crear e invocar 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 grafo en la memoria. Pensemos en cada thread_id como una nueva ventana de chat. También podemos definir la entrada del usuario a cualquier cadena de elección. A continuación, podemos pasar una lista que consta de 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 bloquear con éxito al usuario para que no 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 un hilo diferente. Por ejemplo, "¿Cuáles son los ingresos totales por ventas de los 5 mejores concesionarios en el año 2022?" como 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 Ai Message  [0m==================================
    Tool Calls:
      sql_db_query (cTzJFfvTl)
     Call ID: cTzJFfvTl
      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
    
    [('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 Ai Message  [0m==================================
    Tool Calls:
      sql_db_query (cTzJFfvTl)
     Call ID: cTzJFfvTl
      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
    
    [('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

¡Excelente! El agente ha ejecutado con éxito una serie de tareas, incluida la llamada a varias herramientas para extraer el esquema de la tabla SQL, generar nuevas consultas SQL y verificar su corrección antes de ejecutarlas. Como resultado, el agente devuelve los ingresos totales por ventas correctos para los 5 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 consulta SQL generada. En la respuesta, también vemos que el modelo Granite Guardian determinó que la consulta del usuario es adecuada.

Hagamos una pregunta de seguimiento. Esta vez pidamos al agente que genere y ejecute código Python para mostrar una representación con un gráfico de barras de los resultados. El agente debe recuperar los mensajes anteriores ya que estamos usando 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 Ai Message  [0m==================================
    Tool Calls:
      sql_db_query (cTzJFfvTl)
     Call ID: cTzJFfvTl
      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
    
    [('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. Luego, debe ejecutar el código y mostrar el gráfico de barras.
================================== [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. El código se ejecutó en repl.
    El gráfico de barras se ha trazado y mostrado en el REPL de python.
    El gráfico también se puede ver a continuación:
Ingresos totales por ventas de los 5 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. Observemos que el agente también pudo determinar los valores adecuados de los ejes x e y, las etiquetas y el título. Esto destaca el razonamiento agéntico que diferencia a los agentes de IA de los chatbots tradicionales de LLM.

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 usar la función de ejecutor de agente predefinida, 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 la generación, validación y ejecución de consultas SQL, demostrando habilidades tanto en SQL como en Python. Dada la clase LangChain PythonREPLTool, el agente está equipado con la capacidad no solo de generar sino también de ejecutar código Python. Vimos la ejecución exitosa 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 la herramienta habilita la arquitectura del agente ReAct. Con este tutorial, hemos exhibido las capacidades de programación y razonamiento multimodal de Mistral Medium 3. Como siguiente paso, consideremos experimentar con nodos y aristas adicionales en el grafo.

Soluciones relacionadas
Desarrollo de agentes de IA de IBM 

Permita a los desarrolladores crear, desplegar y monitorear 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 en IA líder en la industria y la cartera de soluciones de IBM a su lado.

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

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

Conozca los servicios de IA
Dé el siguiente paso

Ya sea que elija personalizar aplicaciones y habilidades predefinidas o crear y desplegar servicios agénticos personalizados utilizando un estudio de IA, la plataforma IBM watsonx responde a sus necesidades.

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

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