Crie um agente SQL com o LangGraph e Mistral Medium 3 usando o watsonx.ai

Autora

Anna Gutowska

AI Engineer, Developer Advocate

IBM

Neste tutorial, você criará um agente de IA que pode executar e gerar consulta de SQL e Python para seu SQLite personalizado. Seu agente será criado a partir do zero com o LangGraph e o grande modelo de linguagem (LLM) Mistral Medium 3. Esse LLM é otimizado para casos de uso profissionais, como programação e compreensão multimodal, o que o torna uma excelente escolha para essa tarefa.1 Também detectaremos e bloquearemos mensagens prejudiciais usando o modelo IBM Granite Guardian.

O que é o LangGraph?

O LangGraph, criado pela LangChain, é um framework de Agente de IA em código aberto projetado para criar, implementar e gerenciar complexos fluxos de trabalho de agentes de IA generativa. Ele fornece um conjunto de ferramentas e bibliotecas que permitem aos usuários criar, executar e otimizar LLMs de maneira escalável e eficiente. Em sua essência, o LangGraph usa o poder das arquiteturas baseadas em gráficos para modelar e gerenciar as intrincadas relações entre vários componentes de um fluxo de trabalho de Agente de IA, incluindo os componentes encontrados em sistemas multiagentes.

IBM watsonx.ai

Insights de dados com o LangGraph e watsonx.ai

Um agente de IA pode pegar nossa consulta de linguagem natural e fazer o processamento para nós para nos dar essa saída significativa? Usamos várias tecnologias de código aberto e o poder do watsonx.ai para colocar isso à prova.

Pré-requisitos

  1. Você precisa de uma conta do IBM Cloud para criar um projeto do watsonx.ai .

  2. Várias versões do Python podem funcionar para este tutorial. No momento da publicação, recomendamos baixar o Python 3.13, a versão mais recente.

Etapas

Etapa 1. Configure seu ambiente

Embora você possa escolher entre várias ferramentas, este tutorial explica como configurar uma conta da IBM para usar um Jupyter Notebook.

  1. Faça login no watsonx.ai usando sua conta do IBM Cloud.

  2. Crie um projeto do watsonx.ai.

    Você pode obter a ID do projeto a partir de seu projeto. Clique na guia Gerenciar . Em seguida, copie a ID do projeto da seção Detalhes da página Geral . Você precisa dessa ID para este tutorial.

  3. Crie um Jupyter Notebook.

    Essa etapa abre um ambiente do Jupyter Notebook onde você pode copiar o código deste tutorial. Ou então, você pode baixar esse notebook em seu sistema local e carregá-lo como um ativo em seu projeto do watsonx.ai. Este tutorial também está disponível no GitHub.

Etapa 2. Configure uma instância do watsonx.ai Runtime e uma chave de API

  1. Crie uma instância do serviço do watsonx.ai Runtime (selecione a região apropriada e escolha o plano Lite, que é uma instância gratuita).

  2. Gere uma chave de interface de programação de aplicação (API).

  3. Associe a instância do serviço watsonx.ai Runtime ao projeto que você criou em watsonx.ai.

Etapa 3. Instale e importe as bibliotecas relevantes e configure suas credenciais

Precisamos de algumas bibliotecas e módulos para este tutorial. Certifique-se de importar as seguintes e, se não estiverem instaladas, uma instalação rápida de pip resolve o problema.

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

Reinicie o terminal e importe os seguintes pacotes.

# 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 definir nossas credenciais, precisaremos do WATSONX_APIKEY e do WATSONX_PROJECT_ID que você gerou na Etapa 1. Também definiremos a URL para servir como o endpoint da API.

WATSONX_APIKEY = getpass.getpass("Please insira sua chave de API do watsonx.ai Runtime (pressione Enter): ")

WATSONX_PROJECT_ID = getpass.getpass("Insira a ID do seu projeto (pressione Enter): ")

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

Antes de podermos inicializar nosso LLM, podemos usar a classe Credentials para encapsular nossas credenciais de API passadas.

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

Etapa 4. Instancie o modelo de chat

Para poder interagir com todos os recursos disponíveis no tempo de execução do watsonx.ai, você precisa configurar um APIClient. Aqui, passamos nossas credenciais e WATSONX_PROJECT_ID.

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

Para este tutorial, usaremos o wrapper ChatWatsonx para configurar nosso modelo de chat. Esse wrapper simplifica a integração da chamada e do encadeamento de ferramentas. Incentivamos você a usar as referências de API nos documentos oficiais do ChatWatsonx para obter mais informações. Podemos passar nosso model_id para o Mistral Medium 3 e nosso cliente como parâmetros.

Observe que se você usar um provedor de API diferente, precisará alterar o wrapper devidamente. Por exemplo, para usar a API OpenAI para acessar modelos como o GPT-4, você precisará de uma openai_api_key, bem como do wrapper ChatOpenAI.

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

Etapa 5. Configure o banco de dados SQLite

Neste tutorial, seu agente usará as ferramentas disponíveis para interagir com um banco de dados SQLite. Se você já tem um conjunto de dados armazenado em um sistema de gerenciamento de banco de dados relacional, como PostgreSQL ou SQLite, poderá igorar esta etapa. Caso contrário, execute a célula a seguir para definir a variável sql_script como igual a um script que gera um banco de dados contendo dados sintéticos de vendas de concessionárias de automóveis. Esse banco de dados conterá várias tabelas para armazenar dados individuais de concessionárias, vendas e veí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 executar esse script e criar o banco de dados SQLite local, precisamos configurar um objeto de conexão com o banco de dados. Passando o nome do caminho ":memory:", podemos nos conectar a um banco de dados transitório na memória.

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

Em seguida, podemos usar o método executescript para criar um cursor de banco de dados e executar nosso script SQL. Esse objeto Cursor nos permite executar esses scripts.

connection.executescript(sql_script)

Saída:

<sqlite3.Cursor at 0x30c511240>

Por fim, execute o comando a seguir para criar uma instância do SQLAlechemy Engine com uma conexão com o arquivo de banco de dados no seu diretório de trabalho. O arquivo deve ter o mesmo nome que seu Jupyter Notebook com uma extensão de arquivo de banco de dados. Podemos manter a URL vazia para que a conexão seja feita com o banco de dados local SQLite. Podemos fornecer nosso objeto Connection no parâmetro criador. Esse parâmetro é responsável por criar a conexão com o banco de dados.

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

Se você preferir usar um banco de dados alternativo para este tutorial que não esteja armazenado localmente, basta substituir o caminho vazio por "sqlite:///Chinook.db" para acessar um banco de dados de mídia digital de amostra.

Etapa 6. Configurar as ferramentas

Este tutorial usa o SQLDatabaseToolkit criado previamente do LangChain. O toolkit requer um objeto SQLDatabase, que recebe o SQLAlchemy Engine como parâmetro e o LLM escolhido. Depois de instanciar o toolkit, podemos recuperar suas ferramentas. Vamos imprimir as ferramentas para ver mais detalhes.

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

Saída:

[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 há quatro ferramentas disponíveis como parte do toolkit. Cada ferramenta serve para seu próprio propósito, como pode ser visto nas descrições das ferramentas. As ferramentas são criadas para listar os bancos de dados, executar consultas, retornar esquemas de tabelas e Verify as SQL Query antes de executá-las.

Para equipar nosso agente com a capacidade de gerar e executar código Python, podemos aproveitar a classe PythonREPLTool do LangChain. Esse código configura uma ferramenta Python REPL (Read-Eval-print Loop), definindo sua funcionalidade e anexando-a a uma lista de ferramentas para uso posterior.

python_repl = PythonREPLTool()
tools.append(python_repl)

Para ajudar a garantir que nosso agente receba um prompt do sistema adaptado para ferramentas e bancos de dados SQL, podemos obter um prompt fornecido pelo LangChain Hub. Vamos imprimir system_message para ver o prompt em detalhes.

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

Saída:

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.

Etapa 7. Defina o estado do agente

Há uma função create_react_agent criada previamente disponível no LangGraph que gera um gráfico de agentes que segue a arquitetura ReAct (raciocínio e ação). Essa arquitetura permite que o agente chame as ferramentas de forma interativa em um loop até que uma condição de parada seja atendida.

Fluxograma do agente do React Fluxo do agente do ReAct

Para fornecer uma abordagem mais prática, criaremos um agente do ReAct a partir do zero neste tutorial. Como primeiro passo, podemos criar uma classe AgentState para armazenar o contexto das mensagens do usuário, das ferramentas e do próprio agente. A classe TypedDict do Python é usada aqui para ajudar a garantir que as mensagens estejam no formato de dicionário apropriado. Também podemos usar a função redutora add_messages do LangGraph para acrescentar qualquer nova mensagem à lista existente de mensagens.

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

Etapa 8. Defina a classe ReActAgent

Em seguida, podemos criar a classe ReActAgent. Essa classe fornece uma estrutura básica para criar agentes que possam reagir às mudanças em seu ambiente. As funções da classe ReActAgent permitem chamadas de ferramentas iterativas em resposta ao estado do gráfico.

A função __init__ inicializa os atributos da classe com o grande modelo de linguagem, ferramentas e mensagem do sistema como parâmetros. Esse construtor cria um gráfico de estado com nós para o modelo guardião, o LLM e as ferramentas. O gráfico começa no nó guardião, que chama o método guardian_moderation para detectar conteúdo prejudicial antes de chegar ao LLM e ao banco de dados. A edge condicional entre o nó guardião e o nó llm roteia o estado do gráfico para o nó llm ou para a extremidade. Isso é determinado pelo output da função guardian_moderation. As mensagens seguras são passadas para o nó llm, que executa o método call_llm. Também adicionamos uma edge condicional entre os nós llm e toos para rotear as mensagens adequadamente. Se o LLM retornar uma chamada de ferramenta, o método should_call_tools retornará um booleano True. Caso contrário, False será retornado, e o gráfico será encaminhado para o fim. Essa etapa faz parte da arquitetura do agente do ReAct. Queremos que o agente receba o output de tooçe reaja à mudança de estado para determinar sua próxima ação.

Em seguida, podemos compilar o gráfico, o que nos permite invocar o agente em uma etapa posterior. Para manter as mensagens, podemos usar o ponto de verificação MemorySaver. As duas linhas finais desse método inicializam atributos tools e llm da instância da classe. O atributo tools é um dicionário que mapeia nomes de ferramentas para objetos de ferramentas. O atributo llm é o LLM, que é vinculado às ferramentas usando o 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)

A próxima função na classe ReActAgent é call_llm. Essa função chama o LLM recuperando as mensagens do estado. Se houver uma mensagem do sistema, o método a adicionará ao início da lista de mensagens. O LLM é então invocado com as mensagens e um novo estado com uma resposta do LLM é retornado.

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]}

A função call_tools é a próxima na classe ReActAgent. Esse método recupera as chamadas de ferramenta da última mensagem no estado, itera sobre elas e invoca cada ferramenta com os argumentos fornecidos. Em seguida, os resultados de cada chamada da ferramenta são armazenados em uma lista chamada results. Por fim, esse novo estado é retornado na forma de um dicionário, onde a chave das mensagens é mapeada para a 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}

A função a seguir na classe ReActAgent é should_call_tools. Essa função determina se é preciso chamar as ferramentas com base no estado, recuperando a resposta do LLM anterior do estado e verificando se ela contém alguma chamada de ferramenta.

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

A função guardian_moderation executada no nó do guardrain foi projetada para moderar mensagens usando um sistema de guardião, com o objetivo de detectar e bloquear conteúdo indesejado ou sensível. Primeiro, a última mensagem é recuperada. Em seguida, é definido um dicionário chamado detectores, que contém as configurações do detector e seus valores-limite. Esses detectores identificam tipos específicos de conteúdo nas mensagens, como informações de identificação pessoal (PII), bem como discurso de ódio, linguagem abusiva e palavrões (HAP). Em seguida, uma instância da classe Guardian é criada, passando um objeto api_client chamado client e o dicionário de detectores. O método de detecção da instância do Guardian é chamado, passando o conteúdo da última mensagem e o dicionário de detectores. O método, então, retorna um dicionário no qual a chave moderation_verdict armazena um valor de "safe" ou "inappropriate", dependendo do output do 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"}

A função block_message serve como um mecanismo de notificação, informando ao usuário que sua consulta de input contém conteúdo inadequado e foi bloqueada.

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

Agora podemos reunir todo esse código e executar a célula a seguir.

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.")]}

Etapa 9. Crie e invoque o objeto ReActAgent

A primeira linha no bloco de código a seguir cria uma instância da classe ReActAgent , passando o LLM, as ferramentas SQL e a mensagem do sistema como parâmetros. Em seguida, especificamos um encadeamento para armazenar os estados do gráfico na memória. Pense em cada thread_id como representando uma nova janela de bate-papo. Também podemos definir o input do usuário para qualquer string de sua escolha. Em seguida, podemos transmitir uma lista que consiste no input do usuário no tipo HumanMessage para invocar o agente.

Primeiro, vamos tentar um prompt que deve ser bloqueado pelo 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()

Saída:

    ================================ [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.

O modelo Granite Guardian foi capaz de bloquear com sucesso o usuário de solicitar informações confidenciais do cliente. Podemos ver que o gráfico não alcançou o nó LLM antes de encerrar a conversa. Em seguida, vamos fazer uma pergunta apropriada em um encadeamento diferente. Por exemplo, "Qual é a receita total de vendas das cinco concessionárias com melhor desempenho no ano de 2022?" conforme a entrada do usuário.

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()

Saída:

    ================================ [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

Ótimo! O agente executou com sucesso uma série de tarefas, incluindo a chamada de várias ferramentas para extrair o esquema da tabela SQL, a geração de novas SQL Queries e a verificação de sua exatidão antes de executá-las. Como resultado, o agente retorna a receita total correta de vendas para as cinco concessionárias de carros com melhor desempenho em nosso banco de dados em 2022. Podemos ver o raciocínio de várias etapas do agente à medida que ele imprime cada SQL query gerada. Na resposta, vemos também que o modelo Granite Guardian determinou que a consulta do usuário é apropriada.

Vamos fazer uma pergunta de acompanhamento. Desta vez, vamos pedir ao agente que gere e execute o código Python para exibir uma representação gráfica dos resultados em um gráfico de barras. O agente deve recuperar as mensagens anteriores, pois estamos usando o mesmo 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()

Saída:

================================ [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. Em seguida, você deve executar o código e exibir o 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. 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:
Receita total de vendas das cinco concessionárias com melhor desempenho em 2022

Como esperado, o agente chamou com sucesso a ferramenta python_repl para gerar e executar código Python, resultando em uma representação gráfica dos resultados. Observe que o agente também foi capaz de determinar os valores, rótulos e título apropriados dos eixos x e y. Isso destaca o raciocínio agêntico que diferencia os agentes de IA dos chatbots.

Para obter uma representação visual do gráfico do agente, podemos exibir o fluxo do gráfico.

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

Saída:

Diagrama de visualização do gráfico do agente Visualização de gráfico do agente do LangGraph

Resumo

Neste tutorial, exploramos como criar um agente ReAct de texto para SQL com o LangGraph. Em vez de usar a função de executor do agente criada previamente, create_react_agent, criamos nosso agente a partir do zero. Depois de analisar a pergunta de um usuário em linguagem natural, nosso agente de inteligência artificial usa suas ferramentas para geração, validação e execução da SQL query, demonstrando habilidade em SQL e Python. Dada a classe LangChain PythonREPLTool, o agente está equipado com a capacidade de não apenas gerar, mas também executar código Python. Vimos a execução bem-sucedida dessa ferramenta ao solicitar ao agente uma representação gráfica de sua resposta. O loop condicional entre o LLM e os nós da ferramenta habilita a arquitetura do agente ReAct. Neste tutorial, exibimos os recursos de programação e raciocínio multimodal do Mistral Medium 3. Como próxima etapa, considere experimentar com nós adicionais e edges no gráfico.

Soluções relacionadas
Desenvolvimento de agentes de IA da IBM 

Permita que desenvolvedores criem, implementem e monitorem agentes de IA com o IBM watsonx.ai studio.

 

Explore o watsonx.ai
Soluções de inteligência artificial

Use a IA a serviço de sua empresa com a experiência e o portfólio de soluções líder do setor da IBM à sua disposição.

Explore as soluções de IA
Consultoria e serviços em IA

Reinvente os fluxos de trabalho e operações críticos adicionando IA para maximizar experiências, tomadas de decisão em tempo real e valor de negócios.

Explore os serviços de IA
Dê o próximo passo

Se você optar por personalizar aplicativos e habilidades criados previamente ou criar e implementar serviços agênticos personalizados usando um estúdio de IA, a plataforma IBM watsonx tem aquilo de que você precisa.

Explore o watsonx Orchestrate Explore o watsonx.ai
Notas de rodapé

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