In diesem Tutorial erstellen Sie einen KI-Agenten, der Python- und SQL-Abfragen für Ihre benutzerdefinierte SQLite-Datenbank ausführen und generieren kann. Ihr Agent wird von Grund auf mit LangGraph und dem großen Sprachmodell (LLM) Mistral Medium 3 erstellt. Dieses LLM ist für professionelle Anwendungsfälle wie Codierung und multimodales Verständnis optimiert und somit eine ausgezeichnete Wahl für diese Aufgabe.1 Wir werden außerdem schädliche Nachrichten mithilfe des IBM® Granite® Guardian-Modells erkennen und blockieren.
LangGraph, entwickelt von LangChain, ist ein Open Source KI-Agenten-Framework zum Erstellen, Bereitstellen und Verwalten komplexer generativer KI-Agenten-Workflows. Es bietet eine Reihe von Werkzeugen und Bibliotheken, die es Nutzern ermöglichen, LLMs skalierbar und effizient zu erstellen, auszuführen und zu optimieren. Im Kern nutzt LangGraph die Leistungsfähigkeit der graphbasierten Architekturen, um die komplexen Beziehungen zwischen verschiedenen Komponenten eines KI-Agenten-Workflows zu modellieren und zu verwalten, einschließlich derjenigen, die in Multi-Agenten-Systemen zu finden sind.
Sie benötigen ein IBM Cloud®-Konto, um ein watsonx.ai®-Projekt zu erstellen.
Mehrere Python-Versionen können für dieses Tutorial funktionieren. Zum Zeitpunkt der Veröffentlichung empfehlen wir, Python 3.13 als aktuellste Version herunterzuladen.
Sie können zwar aus mehreren Tools wählen, aber dieses Tutorial führt Sie durch die Einrichtung eines IBM Kontos für die Verwendung eines Jupyter Notebook.
Melden Sie sich mit Ihrem IBM Cloud®-Konto bei watsonx.ai an.
Erstellen Sie ein watsonx.ai-Projekt.
Sie können Ihre Projekt-ID in Ihrem Projekt abrufen. Klicken Sie auf die Registerkarte Verwalten . Kopieren Sie dann die Projekt-ID aus dem Abschnitt Details der Seite Allgemein . Sie benötigen diese ID für dieses Tutorial.
Erstellen Sie ein Jupyter Notebook.
Dieser Schritt öffnet eine Jupyter Notebook-Umgebung, in der Sie den Code aus diesem Tutorial kopieren können. Alternativ können Sie dieses Notebook auf Ihr lokales System herunterladen und als Asset in Ihr watsonx.ai-Projekt hochladen. Dieses Tutorial ist auch auf GitHub verfügbar.
Erstellen Sie eine watsonx.ai-Laufzeit-Service-Instanz (wählen Sie Ihre entsprechende Region aus und wählen Sie den Lite-Plan, der eine kostenlose Instanz ist).
Generieren Sie einen Programmierschnittstellen-(API-)Schlüssel.
Verknüpfen Sie die Instanz des watsonx.ai-Runtime-Service mit dem Projekt, das Sie in watsonx.ai erstellt haben.
Für dieses Tutorial benötigen wir einige Bibliotheken und Module. Stellen Sie sicher, dass Sie die folgenden importieren. Falls sie nicht installiert sind, kann eine schnelle Pip-Installation das Problem beheben.
#installations
%pip install -qU ibm-watsonx-ai \
langchain-ibm \
langgraph \
langchain_experimental \
tavily-python \
langchain-community
Starten Sie Ihr Terminal neu und importieren Sie die folgenden Pakete.
# 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
Um unsere Anmeldedaten einzustellen, benötigen wir die WATSONX_APIKEY und WATSONX_PROJECT_ID die Sie in Schritt 1 generiert haben. Wir setzen auch das URL als API-Endpunkt ein.
WATSONX_APIKEY = getpass.getpass("Please enter your watsonx.ai Runtime API key (press Enter): ") WATSONX_PROJECT_ID = getpass.getpass("Please enter your project ID (press Enter): ") URL = "https://us-south.ml.cloud.ibm.com"
Bevor wir unser LLM initialisieren können, können wir die Credentials-Klasse verwenden, um unsere übergebenen API-Zugangsdaten zu kapseln.
credentials = Credentials(url=URL, api_key=WATSONX_APIKEY)
Um mit allen in watsonx.ai Runtime verfügbaren Ressourcen interagieren zu können, müssen Sie einen APIClient einrichten. Hier geben wir unsere Anmeldedaten und WATSONX_PROJECT_ID ein.
client = APIClient(credentials=credentials, project_id=WATSONX_PROJECT_ID)
In diesem Tutorial verwenden wir den ChatWatsonx-Wrapper, um unser Chat-Modell einzurichten. Dieser Wrapper vereinfacht die Integration von Toolaufrufen und -verkettungen. Wir empfehlen Ihnen, die API-Referenzen in den offiziellen ChatWatsonx-Dokumenten für weitere Informationen zu verwenden. Wir können unsere model_id für Mistral Medium 3 und unseren Client als Parameter übergeben.
Hinweis: Wenn Sie einen anderen API-Anbieter verwenden, müssen Sie den Wrapper entsprechend ändern. Um beispielsweise über die OpenAI-API auf Modelle wie GPT-4 zuzugreifen, benötigen Sie einen openai_api_key sowie den ChatOpenAI-Wrapper.
model_id = "mistralai/mistral-medium-2505"
llm = ChatWatsonx(model_id=model_id, watsonx_client=client)
In diesem Tutorial nutzt Ihr Agent die verfügbaren Tools, um mit einer SQLite-Datenbank zu interagieren. Wenn Sie bereits einen Datensatz in einem relationalen Datenbankmanagementsystem wie PostgreSQL oder SQLite gespeichert haben, können Sie diesen Schritt überspringen. Andernfalls führen Sie die folgende Zelle aus, um die Variable sql_script gleich einem Skript zu setzen, das eine Datenbank mit synthetischen Autohaus-Verkaufsdaten erzeugt. Diese Datenbank enthält mehrere Tabellen zur Speicherung einzelner Händler-, Verkaufs- und Fahrzeugdaten.
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, '2021-08-01', 28000.0),
(9, 9, 7, '2022-09-01', 35000.0),
(10, 10, 8, '2022-10-01', 32000.0),
(11, 11, 9, '01.11.2022', 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);
"""
Um dieses Skript auszuführen und die lokale SQLite-Datenbank zu erstellen, müssen wir ein Datenbankobjekt Connection einrichten. Durch Übergabe des Pfadnamens „:memory:“ stellen wir eine Verbindung zu einer transienten In-Memory-Datenbank her.
connection = sqlite3.connect(":memory:")
Als Nächstes können wir die Methode executescript verwenden, um einen Datenbankcursor zu erstellen und unser SQL-Skript auszuführen. Dieses Cursor-Objekt ermöglicht es uns, solche Skripte auszuführen.
connection.executescript(sql_script)
Ausgabe:
<sqlite3.Cursor at 0x30c511240>
Führen Sie schließlich den folgenden Befehl aus, um eine SQLAlechemy-Engine-Instanz mit Verbindung zur Datenbankdatei in Ihrem Arbeitsverzeichnis zu erstellen. Die Datei sollte den gleichen Namen wie Ihr Jupyter Notebook mit einer Datenbank-Dateierweiterung haben. Wir können die URL leer lassen, damit die Verbindung zur lokalen SQLite-Datenbank hergestellt wird. Wir können unser Connection-Objekt im Parameter creator angeben. Dieser Parameter ist für die Herstellung der Verbindung zur Datenbank zuständig.
engine = create_engine(
"sqlite://",
creator=lambda: connection
)
Wenn Sie für dieses Tutorial eine alternative Datenbank verwenden möchten, die nicht lokal gespeichert ist, ersetzen Sie einfach den leeren Pfad durch "sqlite:///Chinook.db", um auf eine Beispieldatenbank des digitalen Medienstores zuzugreifen.
Dieses Tutorial verwendet das vorkonfigurierte SQLDatabaseToolkit von LangChain . Das Toolkit benötigt ein SQLDatabase-Objekt, das die SQLAlchemy-Engine als Parameter annimmt, und den LLM Ihrer Wahl. Sobald wir das Toolkit instanziiert haben, können wir seine Tools abrufen. Drucken wir die Tools aus, um weitere Details zu sehen.
db = SQLDatabase(engine)
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()
Tools
Ausgabe:
[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={}))]
Wie Sie sehen, sind 4 Tools im Toolkit enthalten. Jedes Werkzeug dient seinem eigenen Zweck, wie aus den Werkzeugbeschreibungen hervorgeht. Die Tools sind darauf ausgelegt, die Datenbanken aufzulisten, Abfragen auszuführen, Tabellenschemata zurückzugeben und SQL-Abfragen vor deren Ausführung zu überprüfen.
Um unseren Agenten mit der Fähigkeit auszustatten, Python-Code zu generieren und auszuführen, können wir die LangChain-Klasse PythonREPLTool nutzen. Dieser Code richtet ein Python REPL-Tool (Read-Eval-Print Loop) ein, definiert seine Funktionalität und fügt es einer Liste von Tools zur späteren Verwendung hinzu.
python_repl = PythonREPLTool()
tools.append(python_repl)
Um sicherzustellen, dass unser Agent einen Prompt erhält, der auf SQL-Tools und Datenbanken zugeschnitten ist, können wir einen vom LangChain Hub bereitgestellten Prompt abrufen. Lassen Sie uns die system_message ausgeben, um die Prompt im Detail zu sehen.
chatprompttemplate = hub.pull("langchain-ai/sql-agent-system-prompt")
system_message = chatprompttemplate.format(dialect="SQLite", top_k=5)
print(system_message)
Ausgabe:
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 ist eine vordefinierte Funktion create_react_agent verfügbar, die einen Agentengraphen generiert, der der Architektur ReAct (Reasoning and Action) folgt. Diese Architektur ermöglicht es dem Agenten,Tools iterativ in einer Schleife aufzurufen, bis eine Stoppbedingung erfüllt ist.
Um einen praxisorientierteren Ansatz zu bieten, werden wir in diesem Tutorial einen ReAct-Agenten von Grund auf neu erstellen. Als ersten Schritt können wir eine AgentState-Klasse erstellen, um den Kontext der Nachrichten vom Benutzer, den Tools und dem Agenten selbst zu speichern. Die Klasse TypedDict von Python wird hier verwendet, um sicherzustellen, dass die Nachrichten im entsprechenden Wörterbuchformat vorliegen. Wir können auch die add_messages-Reduziererfunktion von LangGraph verwenden, um jede neue Nachricht an die bestehende Nachrichtenliste anzuhängen.
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
Als Nächstes können wir die ReActAgent-Klasse erstellen. Diese Klasse bietet eine Grundstruktur für die Erstellung von Agenten, die auf Veränderungen in ihrer Umgebung reagieren können. Die Funktionen in der Klasse ReActAgent erlauben iterative Werkzeugaufrufe als Reaktion auf den Graphenzustand.
Die __init__-Funktion initialisiert die Attribute der Klasse mit dem großen Sprachmodell, Werkzeugen und der Systemnachricht als Parameter. Dieser Konstruktor erstellt ein Zustandsdiagramm mit Knoten für das Guardian-Modell, LLM und Tools. Der Graph startet am guardian-Knoten, der die Methode guardian_moderation aufruft, um schädliche Inhalte zu erkennen, bevor sie das LLM und die Datenbank erreichen. Die bedingte Edge zwischen dem guardian- und llm-Knoten leitet den Zustand des Graphen entweder zum llm-Knoten oder zum Ende. Dies wird durch die Ausgabe der guardian_moderation-Funktion bestimmt. Sichere Nachrichten werden an den llm-Knoten übergeben, der die Methode call_llm ausführt. Wir fügen auch eine bedingte Edge zwischen den Knoten llm und tools hinzu, um Nachrichten entsprechend weiterzuleiten. Wenn das LLM einen Toolaufruf zurückgibt, gibt die Methode should_call_tools ein True Boolean zurück. Andernfalls wird False zurückgegeben und der Graph wird zum Ende geleitet. Dieser Schritt ist Teil der ReAct-Agentenarchitektur – wir wollen, dass der Agent die Ausgabe des Tools empfängt und dann auf die Zustandsänderung reagiert, um seine nächste Aktion zu bestimmen.
Als Nächstes können wir das Diagramm kompilieren, sodass wir den Agenten in einem späteren Schritt aufrufen können. Um Nachrichten dauerhaft zu speichern, können wir den MemorySaver Checkpointer verwenden. Die letzten beiden Zeilen dieser Methode initialisieren die Attribute tools und llm der Klasseninstanz. Das Tools-Attribut ist ein Wörterbuch, das Werkzeug-Namen Werkzeugobjekten zuordnet. Das llm-Attribut ist das LLM, das über die Methode bind_tools an Tools gebunden wird.
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)
Die nächste Funktion in der Klasse ReActAgent ist call_llm. Diese Funktion ruft das LLM auf, indem sie die Nachrichten aus dem Status abruft. Wenn eine Systemnachricht vorhanden ist, fügt die Methode sie an den Anfang der Nachrichtenliste hinzu. Anschließend wird das LLM mit den Nachrichten aufgerufen, und es wird ein neuer Zustand mit einer LLM-Antwort zurückgegeben.
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]}
Die call_tools-Funktion steht als nächstes in der Klasse ReActAgent. Diese Methode ruft die Toolaufrufe aus der letzten Nachricht im Status ab, durchläuft sie und ruft jedes Tool mit den angegebenen Argumenten auf. Anschließend werden die Ergebnisse jedes Toolaufrufs in einer Liste namens results gespeichert. Schließlich wird dieser neue Zustand in Form eines Wörterbuchs zurückgegeben, wobei der Schlüssel der Nachrichten der Ergebnisliste entspricht.
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}
Die folgende Funktion in der Klasse ReActAgent ist should_call_tools. Diese Funktion ermittelt anhand des Status, ob die Tools aufgerufen werden sollen, indem sie die vorherige LLM-Antwort aus dem Status abruft und prüft, ob sie Toolaufrufe enthält.
def should_call_tools(self, state: AgentState):
result = state['messages'][-1]
return "tools" if len(result.tool_calls) > 0 else END
Die guardian_moderation-Funktion, die im Knoten guardrain ausgeführt wurde, ist darauf ausgelegt, Nachrichten mittels eines Guardian-Systems zu moderieren, um unerwünschte oder sensible Inhalte zu erkennen und zu blockieren. Zuerst wird die letzte Nachricht abgerufen. Anschließend wird ein Wörterbuch namens detectors definiert, das die Detektorkonfigurationen und ihre Schwellenwerte enthält. Diese Detektoren erkennen bestimmte Arten von Inhalten in Nachrichten, wie zum Beispiel personenbezogene Daten (PII) sowie Hassrede, beleidigende Sprache und Obszönitäten (HAP). Als Nächstes wird eine Instanz der Guardian-Klasse erstellt, der ein api_client-Objekt namens client und das detectors-Wörterbuch übergeben werden. Die detect-Methode der Guardian-Instanz wird aufgerufen und übergibt den Inhalt der letzten Nachricht sowie das detectors-Verzeichnis. Die Methode gibt dann ein Wörterbuch zurück, in dem der Schlüssel moderation_verdict je nach Ausgabe des Granite Guardian-Modells entweder den Wert „safe“ oder „inappropriate“ speichert.
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"}
Die Funktion block_message dient als Benachrichtigungsmechanismus, der den Benutzer darüber informiert, dass seine Eingabeabfrage unangemessene Inhalte enthält und daher blockiert wurde.
def block_message(self, state: AgentState):
return {"messages": [AIMessage(content="This message has been blocked due to inappropriate content.")]}
Wir können nun den gesamten Code zusammenfügen und die folgende Zelle ausführen.
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.")]}
Die erste Zeile im folgenden Codeblock erzeugt eine Instanz der ReActAgent-Klasse und übergibt das LLM, SQL-Tools und die Systemnachricht als Parameter. Als nächstes legen wir einen Thread fest, der die Graphenzustände im Speicher speichert. Denken Sie sich jedes thread_id als ein neues Chatfenster vor. Wir können die Benutzereingabe auch für eine beliebige Zeichenfolge definieren. Als Nächstes können wir eine Liste, die die Eingabe im Typ HumanMessage enthält, übergeben, um den Agenten aufzurufen.
Probieren wir zunächst einen Prompt aus, der vom Granite Guardian-Modell blockiert werden sollte.
agent = ReActAgent(llm, tools, system_message=system_message) config = {"configurable": {"thread_id": "1"}} user_input = "What is the private address of the customer who bought the most expensive car last month?" result = agent.graph.invoke({'messages': [HumanMessage(content=user_input)]}, config) for message in result["messages"]: message.pretty_print()
Ausgabe:
================================ [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
Großartig! Der Agent hat eine Reihe von Aufgaben erfolgreich ausgeführt, darunter das Aufrufen mehrerer Tools zum Extrahieren des SQL-Tabellenschemas, das Erstellen neuer SQL-Queries und die Überprüfung ihrer Richtigkeit vor der Ausführung. Als Ergebnis liefert der Agent den korrekten Gesamtumsatz für die fünf umsatzstärksten Autohäuser in unserer Datenbank im Jahr 2022. Wir können die mehrstufige Argumentation des Agenten sehen, während er jede generierte SQL-Query ausdruckt. In der Antwort sehen wir auch, dass das Granite Guardian-Modell festgestellt hat, dass die Benutzeranfrage angemessen ist.
Stellen wir eine Anschlussfrage. Diesmal bitten wir den Agenten, Python-Code zu generieren und auszuführen, um ein Balkendiagramm mit den Ergebnissen anzuzeigen. Der Agent sollte die vorherigen Nachrichten abrufen, da wir dieselbe thread_id verwenden.
user_input = "Schreiben Sie Python-Code, um diese Ergebnisse in einem Balkendiagramm darzustellen. Anschließend müssen Sie den Code ausführen und das Balkendiagramm anzeigen."
result = agent.graph.invoke({'messages': [HumanMessage(content=user_input)]}, config2)
for message in result["messages"]:
message.pretty_print()
Ausgabe:
================================ [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
Wie erwartet rief der Agent erfolgreich das Tool python_repl auf, um Python-Code zu erzeugen und auszuführen, was zu einer grafischen Darstellung der Ergebnisse führte. Beachten Sie, dass der Agent auch die entsprechenden X- und Y-Achsenwerte, Beschriftungen und Titel bestimmen konnte. Dies unterstreicht die agentische Argumentation, die KI-Agenten von herkömmlichen LLM-Chatbots unterscheidet.
Um eine visuelle Darstellung des Graphen des Agenten zu erhalten, können wir den Graphenfluss anzeigen.
display(Image(agent.graph.get_graph().draw_mermaid_png()))
Ausgabe:
In diesem Tutorial haben wir erkundet, wie Sie mit LangGraph einen Text-to-SQL ReAct-Agenten erstellen. Anstatt die vorgefertigte Agent-Executor-Funktion zu verwenden, create_react_agent, haben wir unseren Agenten von Grund auf aufgebaut. Nachdem unser KI-Agent die Frage eines Benutzers in natürlicher Sprache analysiert hat, nutzt er seine Werkzeuge zur Generierung, Validierung und Ausführung von SQL-Abfragen und beweist dabei seine Kompetenz sowohl in SQL als auch in Python. Dank der LangChain PythonREPLTool-Klasse ist der Agent in der Lage, Python-Code nicht nur zu generieren, sondern auch auszuführen. Wir haben die erfolgreiche Ausführung dieses Tools gesehen, als wir den Agenten um eine grafische Darstellung seiner Antwort gebeten haben. Die Bedingungsschleife zwischen dem LLM und den Tool-Knoten ermöglicht die ReAct-Agentenarchitektur. Mit diesem Tutorial haben wir die Codierung und multimodalen Denkfähigkeiten von Mistral Medium 3 demonstriert. Als nächste Schritte sollten Sie mit zusätzlichen Knoten und Edges im Graphen experimentieren.
Ermöglichen Sie Entwicklern die Erstellung, Bereitstellung und Überwachung von KI-Agenten mit dem IBM watsonx.ai Studio.
Setzen Sie KI in Ihrem Unternehmen ein – mit branchenführendem Fachwissen im Bereich KI und dem Lösungsportfolio von IBM an Ihrer Seite.
Erfinden Sie kritische Workflows und Abläufe neu, indem Sie KI einsetzen, um Erfahrungen, Entscheidungsfindung in Echtzeit und den geschäftlichen Nutzen zu maximieren.
1 „Medium ist das neue Large.“ Mistral.ai, 7. Mai 2025, mistral.ai/news/mistral-medium-3.