This article breaks down a LangChain-based AI Agent example that enables an LLM to decide between querying a company knowledge base and performing precise calculations, then complete complex tasks through multi-turn tool calls. The core challenge is upgrading a system from “can chat” to “can act” while controlling infinite-loop and tool-injection risks. Keywords: LangChain, AI Agent, RAG.
The technical specification snapshot is straightforward.
| Parameter | Description |
|---|---|
| Language | Python |
| Framework | LangChain |
| Model API | Tongyi Qianwen qwen-plus |
| Vector Retrieval | FAISS |
| Embedding Model | DashScope Embeddings |
| Agent Pattern | LLM + Tool Calling + Multi-turn Message Loop |
| GitHub Stars | Not provided in the source |
| Core Dependencies | langchain_community, langchain_core, langchain_text_splitters, faiss |
This article demonstrates a minimal runnable Agent loop.
The core of the original example is not “chat,” but “decision-driven execution.” An Agent connects the LLM, tools, short-term context, and an external knowledge base so the model can evaluate first, invoke tools next, and synthesize a final answer afterward instead of guessing in a single pass.
This sample exposes only two tools, yet it captures a complete execution path: use RAG for company information queries, use a calculator for budget conversion, and then feed the tool results back into the model to generate a natural-language answer.
The Agent architecture can be reduced to four components.
- LLM: understands intent, reasons, and selects tools
- Planner: decides whether to retrieve first or calculate first
- Memory: stores the current conversation and tool outputs
- Tools: extend the model’s capabilities to external systems
# Minimal abstraction of an Agent capability structure
agent = {
"llm": "Handles reasoning and generation", # Decides whether a tool call is needed
"memory": "Stores conversation context", # Preserves multi-turn state
"planner": "Determines execution order", # Retrieve first or calculate first
"tools": ["rag_search", "calculator"] # Provides external action capabilities
}
This snippet captures the essential difference between an Agent and a standard chatbot: the former has an action interface.
Tool description quality directly determines tool selection accuracy.
In LangChain, @tool is not just a decorator. It is also the interface that exposes function semantics to the model. What the model actually relies on is the function name, parameter description, usage scenario, and return format—not the intent you assume implicitly.
If the tool description is vague, the model may send a retrieval task to the calculator or pass a mathematical expression into the retriever as natural language. In other words, tool documentation itself is part of the prompt.
A usable tool must be describable, invocable, and able to return a string.
from langchain_core.tools import tool
@tool
def calculator(expression: str) -> str:
"""
Calculate a mathematical expression; use this when the question requires precise numerical computation.
Example argument: "500 * 1.46"
Example return value: "730.0"
"""
# This only demonstrates the tool shape; do not use eval directly in production
return str(eval(expression)) # Core logic: execute the mathematical expression
This code shows the standard pattern for declaring a tool: clearly describe its purpose, parameters, and return value.
The multi-turn message loop is the mechanism that makes tool calling actually work.
After bind_tools(), the model output object includes tool_calls. That means the model no longer returns only text. It also returns a structured representation of which tool it wants to call and with which arguments.
Developers must execute those calls one by one, then wrap the results into ToolMessage objects and feed them back to the model. Only after completing this loop can the model incorporate intermediate results into later reasoning and exhibit true Agent behavior.
A safe execution loop should at least limit the number of rounds and validate tool names.
from langchain_core.messages import HumanMessage, ToolMessage
messages = [HumanMessage(content="What is the company budget, and what would it be after a 46% increase?")]
for _ in range(5):
response = tool_llm.invoke(messages) # Let the model decide whether to call a tool
messages.append(response)
if not response.tool_calls:
print(response.content) # Output the final answer when no tool call is needed
break
for tool_call in response.tool_calls:
name = tool_call["name"]
args = tool_call["args"]
if name not in tool_maps:
output = f"Error: tool {name} does not exist" # Core logic: block illegal tool names
else:
output = tool_maps[name].invoke(args) # Execute a registered tool
messages.append(ToolMessage(
content=output,
tool_call_id=tool_call["id"],
name=name,
))
This loop completes the full closed cycle of model decision → tool execution → result return → continued reasoning.
The RAG tool gives the Agent controlled retrieval over private knowledge.
In the example, rag_search splits internal company text into chunks, stores them in FAISS, and returns relevant fragments through vector similarity search. That allows the model to answer questions such as “What is the company plan?” based on an external knowledge source rather than relying on parametric memory.
This design is especially effective for frequently changing content such as internal policies, product specifications, customer records, and process documents. Compared with stuffing full documents directly into the prompt, RAG saves context window space and is much easier to update.
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
raw_text = "The company codename is Deep Blue Plan, and the deadline is December 31, 2026."
docs = [Document(page_content=raw_text)]
splitter = RecursiveCharacterTextSplitter(chunk_size=25, chunk_overlap=5)
chunks = splitter.split_documents(docs) # Core logic: split long text into retrievable chunks
# Embeddings initialization omitted
# ragdb = FAISS.from_documents(chunks, embeddings)
The purpose of this code is to organize raw knowledge into the smallest practical unit for vector retrieval.
Security boundaries must be enforced at the tool layer, not delegated to model self-restraint.
The most concerning point in the original example is eval(expression). Once the model or user passes a malicious expression into the calculator, the risk escalates from “incorrect answer” to “arbitrary code execution.” This is a classic tool-injection surface.
For that reason, your security strategy cannot live only in the system prompt. It must be implemented through parameter validation, whitelist parsing, execution sandboxing, and permission isolation. The dangerous part of an Agent is usually not the model itself, but what the model is allowed to call.
A safer approach is to allow only numbers and arithmetic operators into the calculator.
import re
def safe_calculator(expression: str) -> str:
# Core logic: allow only digits, spaces, decimal points, and basic operators
if not re.fullmatch(r"[0-9\s\+\-\*/\(\)\.]+", expression):
return "Calculation error: the expression contains illegal characters"
try:
return str(eval(expression, {"__builtins__": {}}, {})) # Narrow the execution context
except Exception as e:
return f"Calculation error: {e}"
This code is still not perfect, but it is far more controllable than raw eval and works well as a transitional solution.
This example proves that Agent engineering is primarily about orchestration, not model parameters.
From an engineering perspective, usability depends less on how powerful the model is and more on whether tool definitions are clear, loops can terminate, message replay is complete, and risk controls are actually enforced. The model is the reasoning core, not the entire system.
If you plan to extend this pattern into production, add at least three more capabilities: tool timeout control, structured logging and auditing, and role-based tool permissions. Those additions help an Agent evolve from a demo into a deployable system.
 AI Visual Insight: This image comes from an ad slot on the page. It does not show the specific Agent architecture, call chain, or runtime state of the code, and it does not contain structured information useful for technical analysis.
The FAQ section answers the most important implementation questions.
Q1: Why can’t an Agent call the model only once and stop there?
A: Because tool calls generate intermediate results, and the model must continue reasoning over those results before it can compose a final answer. That requires a multi-turn message exchange.
Q2: What is the fundamental difference between RAG and ordinary prompt stuffing?
A: RAG retrieves first and generates second, which lets you inject external knowledge as dynamic context and makes it suitable for frequently updated data. Ordinary prompt stuffing works better for static, short, and stable information.
Q3: Why is the calculator in this example a security risk?
A: Because eval may execute malicious expressions. You should use whitelist validation, a restricted execution environment, or replace it entirely with AST parsing and custom arithmetic logic.
AI Readability Summary: This article reconstructs the implementation path of an LLM + planner + memory + tools architecture from a LangChain Agent example. It focuses on RAG retrieval, calculator tool binding, the multi-turn ToolMessage loop, and mitigation for eval-based risks. It is well suited for developers who want to build executable AI Agents quickly and safely.