Skip to main content

TST, Hongkong

LangChain ReAct Agent

ReAct :: Reasoning before Action - empower your agent with tools it can use to research a topic before replying to your request.

Environment Setup​

Start by creating your virtual environment using uv:

uv init
uv sync
git add .
git commit -m "Initial commit"
uv add langchain langchain-core langchain-openai langchain-community ddgs wikipedia

And make sure your terminal is using it:

which python
/usr/bin/python
source .venv/bin/activate
which python
~/Projects/langchain-experiments//02_basic_chains/.venv/bin/python

Adding Python Integrations as Tooling​

Search for "langchain python integrations" and you will find a variety of official tools to use - here as an example the "DuckDuckGo Search Tool":

# EXAMPLE TOOL 1 - DuckDuckGo Search
from langchain_community.tools import DuckDuckGoSearchRun

search_tool = DuckDuckGoSearchRun()
search_tool.invoke("who is mike polinowski")
python -m 03_react.main

Mike Polinowski is an AI & MLOps Engineer and the CTO of INSTAR Deutschland, specializing in AI, computer vision, and DevOps practices. He has extensive experience in building AI inference systems and has worked on various projects related to machine learning and automation.
GitHub mpolinowski.github.io
Background Mike Polinowski is an AI & MLOps Engineer and the CTO of INSTAR Deutschland. He has a strong focus on AI, computer vision, and DevOps practices.
Professional Experience
Key Roles CTO at INSTAR Deutschland: Leading the research and development efforts in AI and machine learning.
Expertise Specializes in building AI inference systems, particularly for edge hardware.
Achievements Developed a CPU-only cloud AI inference platform, significantly reducing compute costs while maintaining accuracy. Worked on various projects involving machine learning, automation, and computer vision.
Skills and Specializations
MLOps: Combines software development and IT operations to enhance machine learning model deployment.
DevOps: Implements practices to shorten development cycles and improve software quality.
Computer Vision: Engages in projects related to object detection, classification, and segmentation.
Mike Polinowski's work reflects a commitment to advancing technology in AI and machine learning, particularly in practical applications and infrastructure development.

Or the "Wikipedia Query Tool":

# EXAMPLE TOOL 2 - Wikipedia Search
from langchain_classic.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

wiki_tool = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
wiki_tool.invoke("who is mike polinowski")

But since the China-net does not allow DDG or Wikipedia ~ only mock-tool runs for me... for now:

# EXAMPLE TOOL 3 - Custom Email Tool

@tool
def email_tool(query: str) -> str:
'''This is a tool to send emails to employees'''
return "Email Sent!"

# EXAMPLE TOOL 4 - Custom Slack Tool

@tool
def slack_tool(query: str) -> str:
'''This is a tool to send Slack notifications to employees'''
return "Slack Notifications Sent!"

# Reasoning Chain Invocation

ToolKit = [email_tool, slack_tool]

print(f"Registered Tools: {ToolKit}")

Alright, now we can test it (Note: I am getting a deprecation warning for importing WikipediaQueryRun from community tools - but I cannot find an updated import for it anywhere). The Tools are imported successfully:

python -m 03_react.main

~/home/kentaro/Projects~/langchain-experiments/03_react/src/03_react/main.py:1: DeprecationWarning: `langchain-community` is being sunset and is no longer actively maintained. See https://github.com/langchain-ai/langchain-community/issues/674 for details and migration guidance toward standalone integration packages.
from langchain_community.tools import WikipediaQueryRun

Registered Tools: [StructuredTool(name='email_tool', description='This is a tool to send emails to employees', args_schema=<class 'langchain_core.utils.pydantic.email_tool'>, func=<function email_tool at 0x72e5d4a2d6f0>), StructuredTool(name='slack_tool', description='This is a tool to send Slack notifications to employees', args_schema=<class 'langchain_core.utils.pydantic.slack_tool'>, func=<function slack_tool at 0x72e5d4a2ddd0>)]

The ReAct Agent​

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

client = ChatOpenAI(
model="qwen3.8:latest",
temperature=0.6,
top_p=0.95,
base_url="http://localhost:11434/v1",
api_key="ollama"
)

agent = create_agent(model, tools=tools)

Agent Invocation (with Streaming Support)​

agent = create_agent(model, tools=ToolKit)

# Agent Invocation

user_query = "Tell me the population number of the capital of Malta."

events = agent.stream(
{"messages": [{"user": user_query}]},
stream_mode="values"
)
for event in events:
event["messages"][-1].pretty_print()

Running the agent will stream the results the Agent receive from multiple tool calls. These will be the RAW responses from the API request.

Manual Tool Binding​

ToolKit = [email_tool, slack_tool]

model = ChatOpenAI(
model="qwen3.8:latest",
temperature=0.6,
top_p=0.95,
base_url="http://localhost:11434/v1",
api_key="ollama"
)

# Manual Tool-binding
model_with_tools = model.bind_tools(tools=ToolKit)
print(model_with_tools.invoke("Tell me the population number of the capital of Malta"))

This will return both the model response as well as the tool call, should the model decide to use one. Checking the model output we can see that the tool call happened as expected tool_calls=[{'name': 'get_population_tool', 'args': {'country_query': 'Malta'}...:

python -m 03_react.main

content='' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 66, 'prompt_tokens': 368, 'total_tokens': 434, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cache_write_tokens': None, 'cached_tokens': 0, 'image_tokens': None, 'text_tokens': None}}, 'model_provider': 'openai', 'model_name': 'qwen3.8:latest', 'system_fingerprint': 'fp_ollama', 'id': 'chatcmpl-403', 'finish_reason': 'tool_calls', 'logprobs': None} id='lc_run--01a12e4a-0be5-7723-9d83-b97404acb25c-0' tool_calls=[{'name': 'get_population_tool', 'args': {'country_query': 'Malta'}, 'id': 'call_phiii2y2', 'type': 'tool_call'}] invalid_tool_calls=[] usage_metadata={'input_tokens': 368, 'output_tokens': 66, 'total_tokens': 434, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}

Practical Example :: SQL Agent​

Environment Setup​

Create a script to initialize the database with some event data:

import sqlite3
from pathlib import Path

db_path = Path('src/03_react/db/events.db')
db_path.parent.mkdir(parents=True, exist_ok=True)

conn = sqlite3.connect(db_path)
cursor = conn.cursor()

# create camera event table
cursor.execute('''
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
time TEXT NOT NULL,
name TEXT NOT NULL,
trigger TEXT NOT NULL,
face_ids INTEGER NOT NULL,
license_plate TEXT NOT NULL
)
''')

# create camera event data
cursor.execute('''
INSERT INTO events (time, name, trigger, face_ids, license_plate) VALUES
('Mon Oct 12 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 1', 0, 'SU:GB:678'),
('Mon Oct 13 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 2', 1, 'None'),
('Mon Oct 14 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 3', 34, 'None'),
('Mon Oct 15 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 4', 0, 'SU:RT:22')
''')

conn.commit()
conn.close()
python -m 03_react.init_db

And to be verbose, let's test the DB as well:

import sqlite3
from pathlib import Path

db_path = Path('src/03_react/db/events.db')

conn = sqlite3.connect(db_path)
cursor = conn.cursor()

# create camera event table
cursor.execute('''
SELECT * FROM events
''')
rows = cursor.fetchall()
for row in rows:
print(row)

conn.commit()
conn.close()

And database is ready:

python -m 03_react.test_db

(1, 'Mon Oct 12 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 1', 0, 'SU:GB:678')
(2, 'Mon Oct 13 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 2', 1, 'None')
(3, 'Mon Oct 14 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 3', 34, 'None')
(4, 'Mon Oct 15 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 4', 0, 'SU:RT:22')

SQL ToolKit​

First let's connect to the database using the SQL ToolKit:

from pathlib import Path
from langchain_community.utilities.sql_database import SQLDatabase
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_openai import ChatOpenAI


# Connect to DB

BASE_DIR = Path(__file__).resolve().parent
DB_PATH = BASE_DIR / "db" / "events.db"
sql_db = SQLDatabase.from_uri(f"sqlite:///{DB_PATH}")

model = ChatOpenAI(
model="qwen3.8:latest",
temperature=0.6,
top_p=0.95,
base_url="http://localhost:11434/v1",
api_key="ollama"
)

toolkit = SQLDatabaseToolkit(db=sql_db, llm=model)

# # DEBUG
# print(toolkit.get_tools())

Now we can create the ReAct Agent that will be using the DB tool:

from langchain.agents import create_agent

# ReAct Agent
system_message = "You are an security guard screening the alarm events database for detected liccense plates and face ids."
user_query = "Has the license plate SU:GB:678 been detected in an alarm event?"

agent = create_agent(model, toolkit.get_tools(), system_prompt=system_message)

results = agent.stream(
{"messages": [("user", user_query)]},
stream_mode="values"
)

for result in results:
result["messages"][-1].pretty_print()

Here we are asking the if a specific license plate has been detected by one of the surveillance cameras - and it actually finds the event!

python -m 03_react.sql_agent

================================ Human Message =================================

Has the license plate SU:GB:678 been detected in an alarm event?
================================== Ai Message ==================================

Let me look into the database to check for this license plate. I'll start by exploring the available tables.
Tool Calls:
sql_db_list_tables (call_4w64i56i)
Call ID: call_4w64i56i
Args:
tool_input:
================================= Tool Message =================================
Name: sql_db_list_tables

events
================================== Ai Message ==================================
Tool Calls:
sql_db_schema (call_lffh3h3i)
Call ID: call_lffh3h3i
Args:
table_names: events
================================= Tool Message =================================
Name: sql_db_schema


CREATE TABLE events (
id INTEGER,
time TEXT NOT NULL,
name TEXT NOT NULL,
"trigger" TEXT NOT NULL,
face_ids INTEGER NOT NULL,
license_plate TEXT NOT NULL,
PRIMARY KEY (id)
)

/*
3 rows from events table:
id time name trigger face_ids license_plate
1 Mon Oct 12 06:39:16 AM HKT 2026 Motion Detected Motion Area 1 0 SU:GB:678
2 Mon Oct 13 06:39:16 AM HKT 2026 Motion Detected Motion Area 2 1 None
3 Mon Oct 14 06:39:16 AM HKT 2026 Motion Detected Motion Area 3 34 None
*/
================================== Ai Message ==================================
Tool Calls:
sql_db_query (call_9jejup4q)
Call ID: call_9jejup4q
Args:
query: SELECT id, time, name, trigger, face_ids, license_plate FROM events WHERE license_plate = 'SU:GB:678'
================================= Tool Message =================================
Name: sql_db_query

[(1, 'Mon Oct 12 06:39:16 AM HKT 2026', 'Motion Detected', 'Motion Area 1', 0, 'SU:GB:678')]
================================== Ai Message ==================================

Yes, the license plate **SU:GB:678** has been detected in an alarm event. Here are the details:

| Field | Value |
|---|---|
| **Event ID** | 1 |
| **Time** | Mon Oct 12, 06:39:16 AM HKT 2026 |
| **Alarm Name** | Motion Detected |
| **Trigger** | Motion Area 1 |
| **Face ID** | 0 (no face detected) |
| **License Plate** | SU:GB:678 |

It was flagged by the **Motion Area 1** sensor, and no face ID was associated with this event.