In questo tutorial, costruirai un agente AI che può eseguire e generare query Python e SQL Query per il tuo database SQLite personalizzato. Il tuo agente sarà costruito da zero utilizzando LangGraph e il modello linguistico di grandi dimensioni (LLM) Mistral Medium 3. Questo LLM è ottimizzato per casi d'uso professionali come la codifica e la comprensione multimodale, rendendolo quindi un'ottima scelta per questo compito.1 Rileveremo e bloccheremo anche i messaggi dannosi utilizzando il modello IBM® Granite Guardian.
LangGraph, creato da LangChain , è un framework open source per agenti AI progettato per costruire, distribuire e gestire complessi workflow di agenti di AI generativa. Fornisce un insieme di strumenti e librerie che permettono agli utenti di creare, eseguire e ottimizzare gli LLM in modo scalabile ed efficiente. Fondamentalmente, LangGraph utilizza la potenza delle architetture basate su grafi per modellare e gestire le complesse relazioni tra i vari componenti di un workflow con agenti AI, inclusi quelli presenti nei sistemi multi-agente.
Per creare un progetto watsonx.ai è necessario un account IBM® Cloud .
per questo tutorial possono essere utilizzate diverse versioni di Python. Al momento della pubblicazione, consigliamo di scaricare Python 3.13, l'ultima versione.
Sebbene sia possibile scegliere tra diversi strumenti, questo tutorial illustra come configurare un account IBM per utilizzare un Jupyter Notebook.
Accedi a watsonx.ai utilizzando il tuo account IBM Cloud.
Crea un progetto watsonx.ai.
Puoi ottenere l'ID del tuo progetto dall'interno del tuo progetto. Clicca sulla scheda Gestisci . Quindi, copia l'ID del progetto dalla sezione Dettagli della pagina Generali . Per questo tutorial ti serve questo ID.
Crea un Jupyter Notebook.
Questo passaggio apre un ambiente Jupyter Notebook in cui è possibile copiare il codice da questo tutorial. In alternativa, puoi scaricare questo notebook sul tuo sistema locale e caricarlo nel tuo progetto watsonx.ai come asset. Questo tutorial è disponibile anche su GitHub.
Crea un'istanza di servizio watsonx.ai Runtime (seleziona l'area geografica appropriata e scegli il piano Lite, che è un'istanza gratuita).
Genera una chiave API.
Associa l'istanza del servizio watsonx.ai Runtime al progetto che hai creato in watsonx.ai.
Abbiamo bisogno di alcune librerie e moduli per questo tutorial. Assicurati di importare i seguenti elementi e, se non sono installati, una rapida installazione pip risolverà il problema.
#installations
%pip install -qU ibm-watsonx-ai \
langchain-ibm \
langgraph \
langchain_experimental \
tavily-python \
langchain-community
Riavvia il terminale e importa i seguenti pacchetti.
# importazioni
importa sqlite3
importa getpass
da ibm_watsonx_ai importa APIClient, Credentials
da ibm_watsonx_ai.foundation_models.moderations importa Guardian
da IPython.display importa Image, display
da langchain importa hub
da langchain_ibm importa ChatWatsonx
da langgraph.graph.message importa add_messages
da langgraph.graph importa StateGraph, START, END
da langgraph.checkpoint.memory importa MemorySaver
da langchain_community.utilities.sql_database importa SQLDatabase
da langchain_community.agent_toolkits.sql.toolkit importa SQLDatabaseToolkit
da langchain_core.messages importa AnyMessage, SystemMessage, HumanMessage, ToolMessage, AIMessage
da langchain_core.tools importa tool
da langchain_experimental.tools.python.tool importa PythonREPLTool
da sqlalchemy importa create_engine
da typing_extensions importa TypedDict
da typing importa Annotated
Per impostare le nostre credenziali, avremo bisogno del WATSONX_APIKEY e del WATSONX_PROJECT_ID generati nel Passaggio 1. Imposteremo anche l'URL che fungerà da endpoint dell'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"
Prima di poter inizializzare il nostro LLM, possiamo utilizzare la classe credenziali per incapsulare le credenziali API trasmesse.
credentials = Credentials(url=URL, api_key=WATSONX_APIKEY)
Per poter interagire con tutte le risorse disponibili in watsonx.ai Runtime, è necessario configurare un APIClient. Qui passiamo le nostre credenziali e WATSONX_PROJECT_ID.
client = APIClient(credentials=credentials, project_id=WATSONX_PROJECT_ID)
Per questo tutorial, utilizzeremo il wrapper ChatWatsonx per impostare il nostro modello di chat. Questo wrapper semplifica l'integrazione della chiamata e del concatenamento degli strumenti. Ti consigliamo di utilizzare i riferimenti API nella documentazione ufficiale di ChatWatsonx per ulteriori informazioni. Possiamo utilizzare il nostro model_id per Mistral Medium 3 e il nostro client come parametri.
Nota: se utilizzi un provider API diverso, dovrai modificare il wrapper di conseguenza. Ad esempio, per usare l'API OpenAI per accedere a modelli come GPT-4, avrai bisogno di un openai_api_key così come del wrapper ChatOpenAI.
model_id = "mistralai/mistral-medium-2505"
llm = ChatWatsonx(model_id=model_id, watsonx_client=client)
In questo tutorial, il tuo agente utilizzerà gli strumenti disponibili per interagire con un database SQLite. Se hai già un set di dati memorizzato in un sistema di gestione di database relazionali come PostgreSQL o SQLite, puoi saltare questo passaggio. Altrimenti, esegui la cella seguente per impostare la variabile sql_script uguale a uno script che genera un database contenente i dati di vendita sintetici della concessionaria. Questo database conterrà diverse tabelle per memorizzare i dati di singole concessionarie, vendite e veicoli.
sql_script = """
DROP TABLE IF EXISTS [Concessionarie];
DROP TABLE IF EXISTS [Automobili];
DROP TABLE IF EXISTS [Vendite];
CREATE TABLE [Concessionarie] (
[id] INTEGER PRIMARY KEY,
[nome] TEXT NOT NULL,
[sede] TEXT NOT NULL
);
CREATE TABLE [Automobili] (
[id] INTEGER PRIMARY KEY,
[azienda] TEXT NOT NULL,
[modello] TEXT NOT NULL,
[anno] INTEGER NOT NULL,
[colore] TEXT NOT NULL
);
CREATE TABLE [Vendite] (
[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 [Concessionarie] ([id], [nome], [sede]) VALUES
(1, 'Concessionaria A', 'New York'),
(2, 'Concessionaria B', 'Los Angeles'),
(3, 'Concessionaria C', 'Chicago'),
(4, 'Concessionaria D', 'Houston'),
(5, 'Concessionaria E', 'Phoenix'),
(6, 'Concessionaria F', 'Philadelphia'),
(7, 'Concessionaria G', 'San Antonio'),
(8, 'Concessionaria H', 'San Diego'),
(9, 'Concessionaria I', 'Dallas'),
(10, 'Concessionaria J', 'San Jose');
INSERT INTO [Automobili] ([id], [azienda], [modello], [anno], [colore]) VALUES
(1, 'Toyota', 'Camry', 2020, 'Blu'),
(2, 'Honda', 'Civic', 2019, 'Rosso'),
(3, 'Ford', 'Mustang', 2021, 'Nero'),
(4, 'Chevrolet', 'Silverado', 2018, 'Bianco'),
(5, 'Nissan', 'Altima', 2020, 'Gray'),
(6, 'Kia', 'Optima', 2020, 'Silver'),
(7, 'Hyundai', 'Elantra', 2019, 'Nero'),
(8, 'Volkswagen', 'Golf', 2021, 'Rosso'),
(9, 'BMW', '3 Series', 2018, 'Biancohite'),
(10, 'Mercedes-Benz', 'C-Class', 2020, 'Grigio'),
(11, 'Audi', 'A4', 2019, 'Blu'),
(12, 'Lexus', 'ES', 2021, 'Nero'),
(13, 'Toyota', 'Corolla', 2018, 'Bianco'),
(14, 'Honda', 'Accord', 2020, 'Grigio'),
(15, 'Ford', 'Fusion', 2019, 'Rosso');
INSERT INTO [Vendite] ([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);
"""
Per eseguire questo script e creare il database SQLite locale, dobbiamo configurare un oggetto Connection del database. Utilizzando il nome del percorso ":memory:", possiamo connetterci a un database transitorio in memoria.
connection = sqlite3.connect(":memory:")
A seguire, possiamo utilizzare il metodo executescript per creare un cursore di database ed eseguire il nostro script SQL. Questo oggetto Cursor ci consente di eseguire tali script.
connection.executescript(sql_script)
Output
<sqlite3.Cursor at 0x30c511240>
Infine, esegui il seguente comando per creare un'istanza SQLAlechemy Engine con una connessione al file del database nella tua directory di lavoro. Il file deve avere lo stesso nome di Jupyter Notebook con estensione di file di database. Possiamo mantenere l'URL vuoto in modo che la connessione venga effettuata al database locale SQLite. Possiamo fornire il nostro oggetto Connection nel parametro creator. Questo parametro è responsabile della creazione della connessione al database.
engine = create_engine(
"sqlite://",
creator=lambda: connection
)
Se preferisci usare un database alternativo per questo tutorial che non viene memorizzato localmente, basta sostituire il percorso vuoto con "sqlite:///Chinook.db" per accedere a un database di esempio di negozio multimediale digitale.
Questo tutorial utilizza il LangChain precostituito SQLDatabaseToolkit. Il toolkit richiede un oggetto SQLDatabase, che prende come parametro il motore SQLAlchemy e l'LLM scelto. Una volta istanziato il toolkit, possiamo recuperarne gli strumenti. Stampiamo gli strumenti per vedere maggiori dettagli.
db = SQLDatabase(engine)
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()
tools
Output
[[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={}))]
Possiamo vedere che ci sono 4 strumenti disponibili come parte del toolkit. Ogni strumento ha un proprio scopo, come si evince dalle descrizioni degli strumenti. Gli strumenti sono progettati per elencare i database, eseguire query, restituire schemi di tabelle e verificare le query SQL prima di eseguirle.
Per dotare il nostro agente della capacità di generare ed eseguire codice Python, possiamo utilizzare la classe PythonREPLTool di LangChain. Questo codice imposta uno strumento Python REPL (Read-Eval-Print Loop), definendone la funzionalità e aggiungendolo a un elenco di strumenti per utilizzarlo in seguito.
python_repl = PythonREPLTool()
tools.append(python_repl)
Per garantire che al nostro agente venga fornito un prompt di sistema su misura per gli strumenti e i database SQL, possiamo estrarre un prompt fornito da LangChain Hub. Stampiamo il system_message per vedere il prompt in dettaglio.
chatprompttemplate = hub.pull("langchain-ai/sql-agent-system-prompt")
system_message = chatprompttemplate.format(dialect="SQLite", top_k=5)
print(system_message)
Output
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.
In LangGraph è disponibile una funzione predefinita create_react_agent che genera un grafico di agenti che segue l'architettura ReAct (ragionamento e azione). Questa architettura consente all'agente di chiamare gli strumenti in modo iterativo in un ciclo fino a quando non viene soddisfatta una condizione di arresto.
Per offrire un approccio più pratico, in questo tutorial costruiremo un agente ReAct da zero. Come primo passaggio, possiamo creare una AgentState classe per memorizzare il contesto dei messaggi dell'utente, degli strumenti e dell'agente stesso. Qui viene utilizzata la classe TypedDict di Python per garantire che i messaggi siano nel formato del dizionario appropriato. Possiamo anche usare la funzione di riduttore add_messages di LangGraph per aggiungere qualsiasi nuovo messaggio all'elenco di messaggi esistente.
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
A seguire, possiamo creare la classe ReActAgent. Questa classe fornisce una struttura di base per la creazione di agenti che possono reagire ai cambiamenti dell'ambiente circostante. Le funzioni della classe ReActAgent consentono la chiamata iterativa dello strumento in risposta allo stato del grafico.
La funzione __init__ inizializza gli attributi della classe con il modello linguistico di grandi dimensioni, gli strumenti e il messaggio di sistema come parametri. Questo costruttore crea un grafo di stato con nodi per il modello guardian, LLM e strumenti. Il grafo inizia dal nodo guardian, che chiama il metodo guardian_moderation per rilevare i contenuti dannosi prima che raggiungano l'LLM e il database. L'edge condizionale tra i nodi guardian e llm indirizza lo stato del grafico verso il nodo llm o verso la fine. Questo è determinato dall'output della funzione guardian_moderation. I messaggi sicuri vengono passati al nodo llm, che esegue il metodo call_llm. Aggiungiamo anche un edge condizionale tra i nodi llm e tools per instradare i messaggi in modo appropriato. Se l'LLM restituisce una chiamata a uno strumento, il metodo should_call_tools restituisce un True booleano. In caso contrario, viene restituito il valore False e il grafico viene indirizzato alla fine. Questo passaggio fa parte dell'architettura dell'agente ReAct: vogliamo che l'agente riceva l'output dello strumento e poi reagisca al cambiamento di stato per determinare la sua prossima azione.
A seguire, possiamo compilare il grafo, che ci permette di invocare l'agente in un passaggio successivo. Per rendere persistenti i messaggi, possiamo usare il checkpointer MemorySaver. Le due righe finali di questo metodo inizializzano gli attributi tools e llm dell'istanza della classe. L'attributo strumenti è un dizionario che mappa i nomi degli strumenti agli oggetti strumento. L'attributo llm è l'LLM, che viene legato agli strumenti tramite l'uso del metodo 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 funzione successiva nella classe ReActAgent è call_llm. Questa funzione chiama l'LLM recuperando i messaggi dallo stato. Se è presente un messaggio di sistema, il metodo lo aggiunge all'inizio della lista dei messaggi. L'LLM viene quindi invocato con i messaggi e viene restituito un nuovo stato con una risposta 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 prossima funzione è call_tools nella classe ReActAgent. Questo metodo recupera le chiamate agli strumenti dall'ultimo messaggio nello stato, li sposta sopra e invoca ogni strumento con gli argomenti forniti. Successivamente, i risultati di ogni chiamata allo strumento vengono memorizzati in un elenco chiamato risultati. Infine, questo nuovo stato viene restituito sotto forma di dizionario, dove la chiave dei messaggi corrisponde all'elenco dei risultati.
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 seguente funzione nella classe ReActAgent è should_call_tools. Questa funzione determina se chiamare gli strumenti in base allo stato, recuperando la risposta LLM precedente dallo stato e controllando se contiene chiamate di strumenti.
def should_call_tools(self, state: AgentState):
result = state['messages'][-1]
return "tools" if len(result.tool_calls) > 0 else END
La guardian_moderation funzione eseguita nel nodo guardrain è progettata per moderare i messaggi tramite un sistema guardian, con lo scopo di rilevare e bloccare contenuti indesiderati o sensibili. Per prima cosa, viene recuperato l'ultimo messaggio. Successivamente, viene definito un dizionario denominato detectors , che contiene le configurazioni dei detector e i relativi valori di soglia. Questi rilevatori identificano specifici tipi di contenuto nei messaggi, come le informazioni di identificazione personale (PII) nonché odio, linguaggio offensivo e blasfemia (HAP). Successivamente, viene creata un'istanza della classe Guardian, utilizzando un oggetto api_client chiamato client e il dizionario deirilevatori. Il metodo di rilevamento dell'istanza Guardian viene chiamato, utilizzando il contenuto dell'ultimo messaggio e il dizionario dei rilevatori. Il metodo restituisce quindi un dizionario in cui la moderation_verdict chiave memorizza un valore di "sicuro" o "inappropriato", a seconda dell'output del modello 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 funzione block_message funge da meccanismo di notifica, informando l'utente che la sua query di input contiene contenuti inappropriati ed è stata bloccata.
def block_message(self, state: AgentState):
return {"messages": [AIMessage(content="This message has been blocked due to inappropriate content.")]}
Ora possiamo mettere insieme tutto questo codice ed eseguire la seguente cella.
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.")]}
La prima riga del blocco di codice successivo crea un'istanza della ReActAgent classe, utilizzando come parametri il LLM, gli strumenti SQL e il messaggio di sistema. Successivamente, specifichiamo un thread per memorizzare gli stati del grafo in memoria. Pensiamo a ogni thread_id come una nuova finestra di chat. Possiamo anche definire l'input dell'utente per qualsiasi stringa di scelta. Successivamente, possiamo utilizzare una lista composta dall'input utente in HumanMessage type per invocare l'agente.
Per prima cosa, proviamo un prompt che dovrebbe essere bloccato dal modello 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()
Output
================================ [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.
Il modello Granite Guardian è stato in grado di impedire all'utente di richiedere informazioni sensibili sui clienti. Possiamo vedere che il grafo non ha raggiunto il nodo LLM prima di terminare la conversazione. Poi, facciamo una domanda appropriata in un altro thread. Ad esempio, "Qual è il fatturato totale delle vendite delle 5 concessionarie con migliori risultati nell'anno 2022?" come input dell'utente.
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()
Output
================================ [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), ('Concessionaria 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), ('Concessionaria 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
Fantastico! L'agente ha eseguito con successo una serie di compiti, tra cui chiamare più strumenti per estrarre lo schema della tabella SQL, generare nuove query SQL e verificarne la correttezza prima di eseguirle. Di conseguenza, l'agente restituisce il fatturato totale corretto per le 5 concessionarie auto più performanti del nostro database nel 2022. Possiamo vedere il ragionamento multistep dell'agente mentre stampa ogni SQL query generata. Nella risposta, vediamo anche che il modello Granite Guardian ha determinato che la query utente è appropriata.
Facciamo una domanda di follow-up. Questa volta chiediamo all'agente di generare ed eseguire codice Python per mostrare una rappresentazione a barre dei risultati. L'agente dovrebbe recuperare i messaggi precedenti poiché stiamo usando gli stessi 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()
Output
================================ [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), ('Concessionaria 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:
Come previsto, l'agente ha chiamato con successo lo strumento python_repl per generare ed eseguire il codice Python, ottenendo una rappresentazione grafica dei risultati. Si noti che l'agente è stato anche in grado di determinare i valori, le etichette e il titolo appropriati per gli assi x e y. Questo evidenzia il ragionamento basato su agenti che distingue gli agenti AI dai chatbot LLM tradizionali.
Per ottenere una rappresentazione visiva del grafico dell'agente, possiamo visualizzare il flusso del grafico.
display(Image(agent.graph.get_graph().draw_mermaid_png()))
Output
In questo tutorial, abbiamo esplorato come costruire un agente ReAct testo-to-SQL con LangGraph. Invece di usare la funzione di esecutore dell'agente preimpostata, cioè create_react_agent, abbiamo creato il nostro agente da zero. Dopo aver analizzato la domanda di un utente in linguaggio naturale, il nostro agente di intelligenza artificiale utilizza i suoi strumenti per generare, validare ed eseguire SQL query, dimostrando abilità sia in SQL query che in Python. Data la classe LangChain PythonREPLTool, l'agente è dotato non solo della capacità di generare ma anche di eseguire codice Python. Abbiamo visto l'esecuzione efficace di questo strumento quando abbiamo chiesto all'agente una rappresentazione grafica della sua risposta. Il ciclo condizionale tra il LLM e i nodi degli strumenti consente l'architettura degli agenti ReAct. Con questo tutorial, abbiamo mostrato le funzionalità di codifica e ragionamento multimodale di Mistral Medium 3. Come prossimo passaggio, prova a sperimentare con nodi e edge aggiuntivi nel grafo.
Metti l'AI al servizio della tua azienda grazie all'esperienza leader di settore e alla gamma di soluzioni di IBM nel campo dell'AI.
Reinventa i flussi di lavoro e le operazioni critiche aggiungendo l'AI per massimizzare le esperienze, il processo decisionale in tempo reale e il valore di business.
1 “Medium Is the New Large.” Mistral.ai, 7 May 2025, mistral.ai/news/mistral-medium-3.