Membangun agen SQL dengan LangGraph dan Mistral Medium 3 menggunakan watsonx.ai

Penulis

Anna Gutowska

AI Engineer, Developer Advocate

IBM

Dalam tutorial ini, Anda juga akan membangun agen AI yang dapat mengeksekusi dan menghasilkan kueri Python dan SQL untuk basis data SQLite kustom Anda. Agen tersebut akan dibangun dari awal menggunakan LangGraph dan model bahasa besar (LLM) Mistral Medium 3. LLM ini dioptimalkan untuk kasus penggunaan profesional seperti pengodean dan pemahaman multimodal, sehingga menjadikannya pilihan yang sangat baik untuk tugas ini.1 Kami juga akan mendeteksi dan memblokir pesan berbahaya dengan menggunakan model IBM® Granite® Guardian.

Apa itu LangGraph?

LangGraph, yang dikembangkan oleh LangChain, adalah kerangka kerja sumber terbuka untuk agen AI yang dirancang guna membangun, menerapkan, dan mengelola alur kerja agen AI generatif yang kompleks. Solusi ini menyediakan seperangkat alat dan pustaka yang memungkinkan pengguna membuat, menjalankan, dan mengoptimalkan LLM secara skalabel dan efisien. Pada intinya, LangGraph memanfaatkan kekuatan arsitektur berbasis grafik untuk memodelkan dan mengelola hubungan yang kompleks antara berbagai komponen dalam alur kerja agen AI, termasuk komponen yang terdapat pada sistem multi-agen.

IBM watsonx.ai

Insight Data dengan LangGraph dan watsonx.ai

Dapatkah agen AI mengambil kueri bahasa alami kita dan melakukan pemrosesan bagi kita untuk memberi output yang bermakna? Kami menggunakan beberapa bagian teknologi sumber terbuka dan kekuatan watsonx.ai untuk mengujinya.

Prasyarat

  1. Anda memerlukan akun IBM® Cloud untuk membuat proyek watsonx.ai projek.

  2. Beberapa versi Python dapat digunakan untuk tutorial ini. Pada saat penulisan, kami merekomendasikan untuk mengunduh Python 3.13, versi terbaru.

Langkah-langkah

Langkah 1. Siapkan lingkungan Anda

Meskipun terdapat pilihan beberapa alat, tutorial ini akan memandu Anda untuk menyiapkan akun IBM menggunakan Jupyter Notebook.

  1. Masuk ke watsonx.ai dengan menggunakan akun IBM® Cloud Anda.

  2. Buat proyek watsonx.ai.

    Anda bisa mendapatkan ID proyek dari dalam proyek Anda. Klik tab Kelola . Kemudian, salin ID proyek dari bagian Detail di halaman Umum . Anda memerlukan ID ini untuk tutorial ini.

  3. Buat Jupyter Notebook.

    Langkah ini membuka lingkungan Jupyter Notebook tempat Anda dapat menyalin kode dari tutorial ini. Sebagai alternatif, Anda dapat mengunduh notebook ini ke sistem lokal Anda dan mengunggahnya ke proyek watsonx.ai Anda sebagai aset. Tutorial ini juga tersedia di GitHub.

Langkah 2. Siapkan instance waktu proses watsonx.ai dan kunci API

  1. Buat instance layanan Waktu proses watsonx.ai (pilih wilayah yang sesuai dan pilih paket Lite, yang merupakan instans gratis).

  2. Buat kunci antarmuka pemrograman aplikasi (API).

  3. Kaitkan instans layanan Waktu Proses watsonx.ai dengan proyek yang Anda buat di watsonx.ai.

Langkah 3. Instal dan impor pustaka yang relevan dan atur kredensial Anda

Kita membutuhkan beberapa pustaka dan modul untuk tutorial ini. Pastikan untuk mengimpor yang berikut ini dan jika tidak diinstal, instalasi pip cepat akan menyelesaikan masalah.

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

Mulai ulang terminal Anda dan impor paket berikut.

# impor
impor sqlite3
impor getpass

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

Untuk mengatur kredensial kami, kami memerlukan WATSONX_APIKEY dan WATSON X_PROJECT_ ID yang Anda buat di Langkah 1. Kami juga akan mengatur URL untuk berfungsi sebagai titik akhir 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"

Sebelum kita dapat menginisialisasi LLM, kita dapat menggunakan kelas Kredentsal untuk merangkum kredensial API yang akan kita teruskan.

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

Langkah 4. Buat instans model obrolan

Untuk dapat berinteraksi dengan seluruh sumber daya yang tersedia di waktu proses watsonx.ai, Anda perlu mengonfigurasi APIClient. Di sini, kami meneruskan kredensial API serta WATSON X_PROJECT_ID.

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

Untuk tutorial ini, kita akan menggunakan wrapper ChatWatsonx untuk mengatur model obrolan kita. Wrapper ini menyederhanakan integrasi pemanggilan alat dan rantai. Kami mendorong Anda untuk menggunakan referensi API pada dokumen resmi ChatWatsonx untuk informasi lebih lanjut. Kita dapat meneruskan model_id untuk Mistral Medium 3 serta klien sebagai parameter.

Catatan: Jika Anda menggunakan penyedia API yang berbeda, Anda perlu menyesuaikan wrapper yang digunakan. Sebagai contoh, untuk menggunakan API OpenAI untuk mengakses model seperti GPT-4, Anda memerlukan openai_api_key dan juga wrapper ChatOpenAI.

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

Langkah 5. Siapkan basis data SQLite

Dalam tutorial ini, agen Anda akan menggunakan alat yang tersedia untuk berinteraksi dengan basis data SQLite. Jika Anda sudah memiliki kumpulan data yang tersimpan dalam sistem manajemen basis data relasional seperti PostgreSQL atau SQLite, Anda dapat melewati langkah ini. Jika belum, jalankan sel berikut untuk mengatur variabel sql_script agar berisi skrip yang menghasilkan basis data dengan data penjualan dealer mobil sintetis. Basis data ini akan mencakup beberapa tabel untuk menyimpan data dealer, penjualan, dan kendaraan individual.

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);
    
"""

Untuk menjalankan skrip ini dan membuat basis data SQLite lokal, kita perlu mengatur objek koneksi basis data. Dengan meneruskan nama jalur ":memory:", kita dapat terhubung ke basis data sementara yang berada di dalam memori.

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

Selanjutnya, kita dapat menggunakan metode executescript untuk membuat kursor basis data dan menjalankan skrip SQL kita. Objek Cursor ini memungkinkan kita mengeksekusi skrip tersebut.

connection.executescript(sql_script)

Output:

<sqlite3.Cursor at 0x30c511240>

Akhirnya, jalankan perintah berikut untuk membuat instance SQLAlchemy Engine dengan koneksi ke file basis data di direktori kerja Anda. File tersebut harus memiliki nama yang sama dengan Jupyter Notebook Anda dengan ekstensi file basis data. Kita dapat menjaga URL kosong sehingga koneksi dibuat ke basis data lokal SQLite. Kita dapat menyediakan objek Connection melalui parameter creator . Parameter ini bertanggung jawab untuk membuat koneksi ke basis data.

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

Jika Anda lebih memilih menggunakan basis data alternatif untuk tutorial ini yang tidak disimpan secara lokal, cukup ganti jalur kosong dengan "sqlite:///Chinook.db" untuk mengakses basis data contoh toko media digital.

Langkah 6. Siapkan alat

Tutorial ini menggunakan toolkit basis data SQL yang dibangun sebelumnya oleh LangChain. Toolkit ini memerlukan objek SQLDatabase, yang menerima SQLAlchemy Engine sebagai parameter, serta LLM pilihan. Setelah kita membuat instance toolkit tersebut, kita dapat mengambil alat-alat yang disediakannya. Mari kita cetak daftar alat untuk melihat detail lebih lanjut.

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

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={}))]

Kita dapat melihat bahwa terdapat empat alat yang tersedia sebagai bagian dari toolkit ini. Setiap alat memiliki fungsi dan tujuan masing-masing, sebagaimana dijelaskan dalam deskripsi alatnya. Alat-alat ini dirancang untuk membuat daftar basis data, menjalankan kueri, mengembalikan skema tabel, serta memverifikasi kueri SQL sebelum dijalankan.

Untuk melengkapi agen dengan kemampuan menghasilkan dan mengeksekusi kode Python, kita dapat memanfaatkan kelas PythonREPLTool dari LangChain. Kode ini mengonfigurasi alat Python REPL (Read-Eval-Print Loop), mendefinisikan fungsinya, dan menambahkannya ke dalam daftar alat untuk digunakan pada tahap selanjutnya.

python_repl = PythonREPLTool()
tools.append(python_repl)

Untuk membantu memastikan bahwa agen kami dilengkapi dengan prompt sistem yang disesuaikan dengan alat dan basis data SQL, kami dapat menarik prompt yang disediakan oleh LangChain Hub. Mari kita cetak system_message untuk melihat prompt secara detail.

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.

Langkah 7. Tentukan status agen

Tersedia fungsi bawaan create_react_agent di LangGraph yang menghasilkan grafik agen berdasarkan arsitektur ReAct (penalaran dan aksi). Arsitektur ini memungkinkan agen untuk secara berulang memanggil alat dalam satu loop hingga kondisi penghentian terpenuhi.

Diagram alir agen React Alur agen ReAct

Untuk memberikan pendekatan yang lebih langsung, kami akan membangun agen ReAct dari awal dalam tutorial ini. Sebagai langkah pertama, kita dapat membuat kelas AgentState untuk menyimpan konteks pesan dari pengguna, alat, dan agen itu sendiri. Kelas TypedDict Python digunakan di sini untuk membantu memastikan pesan berada dalam format kamus yang sesuai. Kita juga dapat menggunakan fungsi reducer add_messages dari LangGraph untuk menambahkan pesan baru ke dalam daftar pesan yang sudah ada.

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

Langkah 8. Definisikan kelas ReActAgent

Selanjutnya, kita dapat membuat kelas ReActAgent. Kelas ini menyediakan struktur dasar untuk membangun agen yang dapat bereaksi terhadap perubahan di lingkungannya. Fungsi-fungsi dalam kelas ReActAgent memungkinkan pemanggilan alat secara berulang sebagai respons terhadap keadaan grafik.

Fungsi __init__ menginisialisasi atribut kelas dengan model bahasa besar, alat, dan pesan sistem sebagai parameter. Konstruktor ini membangun grafik status dengan node untuk model penjaga, LLM, dan alat. Grafik dimulai pada node guardian, yang memanggil metode guardian_moderation untuk mendeteksi konten berbahaya sebelum mencapai LLM dan basis data. Sisi bersyarat antara node guardian dan llm merutekan status grafik ke node llm atau ke akhir. Hal ini bergantung pada keluaran fungsi guardian_moderation. Pesan yang dinilai aman diteruskan ke node llm, yang mengeksekusi metode call_llm. Kami juga menambahkan sisi bersyarat antara node llm dan alat untuk merutekan pesan secara tepat. Jika LLM mengembalikan panggilan alat, metode should_call_tools akan mengembalikan nilai boolean True. Jika tidak, nilai False dikembalikan dan grafik diarahkan ke akhir. Langkah ini merupakan bagian dari arsitektur agen ReAct—kami ingin agen menerima keluaran dari alat, lalu bereaksi terhadap perubahan status untuk menentukan tindakan berikutnya.

Selanjutnya, kita dapat mengkompilasi grafik, yang memungkinkan kita untuk memanggil agen di langkah selanjutnya. Untuk mempertahankan pesan, kita dapat menggunakan checkpointer MemorySaver. Dua baris terakhir dari metode ini menginisialisasi atribut tools dan llm dari instance kelas. Atribut alat bantu adalah kamus yang memetakan nama alat ke objek alat. Atribut llm adalah LLM, yang terikat ke alat dengan menggunakan metode 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, "wali")
 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)

Fungsi berikutnya dalam kelas ReActAgent adalah call_llm. Fungsi ini memanggil LLM dengan mengambil pesan dari status. Jika pesan sistem hadir, metode menambahkannya ke awal daftar pesan. LLM kemudian dipanggil dengan pesan, dan status baru dengan respons LLM dikembalikan.

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

Fungsi call_tools adalah fungsi berikutnya dalam kelas ReActAgent. Metode ini mengambil pemanggilan alat dari pesan terakhir dalam state, mengulanginya dan memanggil setiap alat dengan argumen yang diberikan. Selanjutnya, hasil dari setiap panggilan alat disimpan dalam daftar yang disebut hasil. Akhirnya, status baru ini dikembalikan dalam bentuk kamus, di mana kunci pesan dipetakan ke daftar hasil.

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}

Fungsi berikut dalam kelas ReActAgent adalah should_call_tools. Fungsi ini menentukan apakah akan memanggil alat berdasarkan status dengan mengambil respons LLM sebelumnya dari status dan memeriksa apakah itu berisi panggilan alat apa pun.

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

Fungsi guardian_moderation yang dijalankan di node guardrain dirancang untuk memoderasi pesan menggunakan sistem penjaga, untuk tujuan mendeteksi dan memblokir konten yang tidak diinginkan atau sensitif. Pertama, pesan terakhir diambil. Selanjutnya, kamus bernama detektor didefinisikan, yang berisi konfigurasi detektor dan nilai ambang batasnya. Detektor ini mengidentifikasi jenis konten tertentu dalam pesan, seperti informasi identifikasi pribadi (PII) serta ucapan kebencian, bahasa kasar, dan kata-kata kotor (HAP). Selanjutnya, sebuah instance dari kelas Guardian dibuat, dengan mengoper objek api_client bernama client dan kamus detector. Metode deteksi dari instance Guardian dipanggil, dengan memasukkan isi pesan terakhir dan kamus detektor. Metode ini kemudian mengembalikan kamus di mana kunci moderation_verdict menyimpan nilai "aman" atau "tidak sesuai," tergantung pada output model 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"}

Fungsi block_message berfungsi sebagai mekanisme pemberitahuan, memberi tahu pengguna bahwa permintaan input mereka berisi konten yang tidak pantas dan telah diblokir.

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

Kita sekarang dapat menempatkan semua kode ini bersama-sama dan menjalankan sel berikut.

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

Langkah 9. Buat dan panggil objek ReactAgent

Baris pertama dalam blok kode berikut membuat instance kelas ReactAgent, meneruskan LLM, alat SQL dan pesan sistem sebagai parameter. Selanjutnya, kita tentukan sebuah thread untuk toko status grafik dalam memori. Think setiap thread_id sebagai mewakili jendela obrolan baru. Kami juga dapat menentukan input pengguna ke string pilihan apa pun. Selanjutnya, kita dapat meneruskan daftar yang terdiri dari input pengguna dalam tipe HumanMess age untuk memanggil agen.

Pertama, mari kita coba prompt yang harus diblokir oleh model 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.

Model Granite Guardian berhasil memblokir pengguna dari meminta informasi klien yang sensitif. Kita dapat melihat bahwa grafik tidak mencapai node LLM sebelum mengakhiri percakapan. Selanjutnya, mari kita ajukan pertanyaan yang sesuai di thread yang berbeda. Misalnya, " Berapa total pendapatan penjualan untuk 5 dealer berkinerja teratas di tahun 2022? " sebagai input pengguna.

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

Hebat! Agen tersebut telah menyelesaikan sejumlah proses secara efektif, mulai dari mengakses beberapa tool untuk memperoleh skema tabel SQL, menyusun query SQL baru, hingga melakukan validasi terhadap kueri tersebut sebelum eksekusi dilakukan. Akibatnya, agen mengembalikan total pendapatan penjualan yang benar untuk 5 dealer mobil berkinerja teratas dalam basis data kami pada tahun 2022. Kita dapat melihat penalaran multistep agen saat mencetak setiap SQL query yang dihasilkan. Dalam tanggapan, kami juga melihat bahwa model Granite Guardian menentukan bahwa permintaan pengguna sesuai.

Mari kita ajukan pertanyaan lanjutan. Kali ini mari kita meminta agen untuk menghasilkan dan mengeksekusi kode Python untuk menampilkan representasi grafik batang dari hasil. Agen harus mengambil pesan sebelumnya karena kita menggunakan thread_ id yang sama.

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

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

for messages in the results ["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), ('Dealership H', 32000.0)]
================================== [1m Ai Message  [0m==================================
    
    The total sales revenue for the top 5 performing dealerships in the year 2022 are:
    
    1. Dealership A: $65,000
    2. Dealership B: $48,000
    3. Dealership J: $38,000
    4. Dealership G: $35,000
    5. Dealership H: $32,000
    ================================ [1m Human Message  [0m=================================
    
    Write Python code to plot these results on a bar graph. Then, you must execute the code and display the bar graph.
================================== [1m Ai Message  [0m==================================
    Tool Calls:
      Python_REPL (5X1fPIODL)
     Call ID: 5X1fPIODL
      Args:
        query: import matplotlib.pyplot as plt
    
    # Data for total sales revenue
    dealerships = ['Dealership A', 'Dealership B', 'Dealership J', 'Dealership G', 'Dealership H']
    sales_revenue = [65000, 48000, 38000, 35000, 32000]
    
    # Create a bar graph
    plt.bar(dealerships, sales_revenue, color='blue')
    
    # Customize the plot
    plt.title('Total Sales Revenue for Top 5 Performing Dealerships in 2022')
    plt.xlabel('Dealerships')
    plt.ylabel('Total Sales Revenue ($)')
    plt.xticks(rotation=45)
    plt.grid(axis='y')
    
    # Display the plot
    plt.tight_layout()
    plt.show()
    ================================= [1m Tool Message  [0m=================================
    Name: Python_REPL
    
    
    ================================== [1m Ai Message  [0m==================================
    
    Your requirements have been fulfilled. Code has been executed in repl.
    The bar graph has been plotted and shown in the python REPL.
    The graph can also be seen below:
Total pendapatan penjualan untuk 5 dealer berkinerja teratas tahun 2022

Seperti yang diharapkan, agen berhasil memanggil alat python_repl untuk menghasilkan dan mengeksekusi kode Python, menghasilkan representasi grafis dari hasil. Perhatikan bahwa agen juga dapat menentukan nilai sumbu x dan sumbu y yang sesuai, label, dan judul. Ini menyoroti alasan agen yang membedakan agen AI dari chatbot LLM tradisional.

Untuk mendapatkan representasi visual dari grafik agen, kita dapat menampilkan alur grafik.

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

Output:

Diagram visualisasi grafik agen Visualisasi grafik agen LangGraph

Ringkasan

Dalam tutorial ini, kita jelajahi cara membangun agen ReAct Text-to-SQL dengan LangGraph. Alih-alih menggunakan fungsi eksekutor agen bawaan, create_react_agent, kami membangun agen kami dari awal. Setelah mengurai pertanyaan pengguna dalam bahasa alami, agen kecerdasan buatan kami menggunakan alatnya untuk pembuatan, validasi, dan eksekusi SQL query, menunjukkan keterampilan dalam SQL query dan Python. Mengingat kelas LangChain PythonReplTool, agen dilengkapi dengan kemampuan untuk tidak hanya menghasilkan tetapi juga mengeksekusi kode Python. Kami melihat keberhasilan eksekusi alat ini setelah meminta agen untuk representasi grafis dari responsnya. Loop kondisional antara LLM dan node alat memungkinkan arsitektur agen ReAct. Dengan tutorial ini, kami telah menunjukkan kemampuan pengodean dan penalaran multimodal dari Mistral Medium 3. Sebagai langkah selanjutnya, pertimbangkan untuk bereksperimen dengan node dan tepi tambahan dalam grafik.

Solusi terkait
Pengembangan agen AI IBM 

Bantu pengembang untuk membangun, menerapkan, dan memantau agen AI dengan studio IBM watsonx.ai.

 

Jelajahi watsonx.ai
Solusi kecerdasan buatan (AI)

Gunakan AI di bisnis Anda dalam perpaduan antara keahlian AI terdepan di industri dari IBM dan portofolio solusi Anda.

Jelajahi solusi AI
Konsultasi dan layanan AI

Temukan kembali alur kerja dan operasi yang penting dengan menambahkan AI untuk memaksimalkan pengalaman, pengambilan keputusan secara real-time, dan nilai bisnis.

Jelajahi layanan AI
Ambil langkah selanjutnya

Baik Anda memilih untuk menyesuaikan aplikasi dan keterampilan yang dibangun sebelumnya atau membangun dan menerapkan layanan agen khusus menggunakan studio AI, platform IBM watsonx siap membantu Anda.

Menjelajahi watsonx Orchestrate Jelajahi watsonx.ai
Catatan kaki

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