前言:为什么你需要理解 AI Agent
2026年了,LLM 调用 API 已经不是什么新鲜事。但把 LLM 从「问一句答一句」的聊天机器人升级成能自主调用工具、多步推理、自我纠错的 AI Agent,这才是真正有生产力的东西。
我在生产环境里踩过不少坑——Agent 陷入死循环疯狂调 API、Tool Calling 返回格式乱掉导致整个链路断掉、prompt 太长把上下文窗口撑爆。这篇文章就是把这些教训整理出来,用最少的代码展示一个可运行、可扩展的 AI Agent 核心骨架。
一、AI Agent 到底长什么样
先抛开 LangChain、AutoGPT 这些重框架。一个最简 Agent 就三个组件:
- LLM(大脑):负责理解意图、决定下一步做什么
- 工具集(双手):搜索、计算、读文件、调 API——任何可执行的函数
- 循环控制器(小脑):管理「思考→行动→观察→思考」这个循环,设置最大步数和终止条件
工作流程:用户输入 → LLM 分析是否需要用工具 → 如果需要,生成 Tool Call → 执行工具 → 把结果喂回 LLM → 判断是否完成 → 输出最终答案。下面这张图把这个过程画清楚了:

可以看到,Tool Calling 模式比 ReAct 平均快 30-40%,因为少了一轮「思考要不要用工具」的推理。而直接调用(无循环)虽然最快,但只能处理单步任务,遇到需要多步推理的场景就直接哑火。
二、核心代码:50 行实现 Tool Calling Agent
下面是一个可以直接跑的 Python 实现。依赖只有 openai 库:
import json
from openai import OpenAI
client = OpenAI() # 或用 base_url 指向任何兼容接口
# 定义工具——就是普通的 Python 函数
def search_web(query: str) -> str:
"""搜索网页(这里用 mock 数据演示)"""
results = {
"Python": "Python 3.13 已发布,支持实验性 JIT 编译器",
"default": f"关于 '{query}' 的搜索结果:暂无相关内容"
}
return results.get(query, results["default"])
def calculate(expression: str) -> str:
"""安全计算数学表达式"""
try:
# 白名单限制,防止代码注入
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
return "错误:表达式包含不允许的字符"
return str(eval(expression))
except Exception as e:
return f"计算错误: {e}"
# 工具注册表 — 把函数签名告诉 LLM
TOOLS = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "搜索互联网获取最新信息。当用户问及需要实时数据的问题时使用。",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "执行数学计算。当用户需要算术运算时使用。",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式,如 '(100+200)*0.8'"}
},
"required": ["expression"]
}
}
}
]
# 工具执行映射
TOOL_MAP = {
"search_web": search_web,
"calculate": calculate,
}
SYSTEM_PROMPT = """你是一个能调用工具的 AI 助手。
规则:
1. 当需要搜索或计算时,调用对应工具。
2. 拿到工具结果后,用自然的语言解释给用户。
3. 如果不需要工具,直接回答。
4. 不要编造你不知道的信息。"""
def run_agent(user_input: str, max_steps: int = 10) -> str:
"""主循环:不断和 LLM 对话直到任务完成或达到最大步数"""
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_input}
]
for step in range(max_steps):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice="auto",
temperature=0.1,
)
msg = response.choices[0].message
# 没有 tool call → 模型直接给出了最终答案
if not msg.tool_calls:
return msg.content
# 有 tool call → 执行并回传结果
messages.append(msg)
for tool_call in msg.tool_calls:
func_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f" [Step {step+1}] 调用工具: {func_name}({args})")
result = TOOL_MAP[func_name](**args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
return "达到最大步数限制,任务未完成。"
# 测试
if __name__ == "__main__":
result = run_agent("Python 最新版本有什么新特性?帮我算一下 (100+200)*0.85")
print(f"\n最终回答:\n{result}")
跑起来输出大概是这样:
[Step 1] 调用工具: search_web({'query': 'Python'})
[Step 1] 调用工具: calculate({'expression': '(100+200)*0.85'})
最终回答:
Python 3.13 已发布,支持实验性 JIT 编译器。
另外,(100+200) × 0.85 = 255.0。
注意这里 两个工具调用是在同一个 Step 内并行发出的——这正是 Tool Calling 比 ReAct 快的原因:ReAct 每轮只能做一个动作,Tool Calling 可以批量。
三、生产环境的三个关键优化
3.1 防止死循环:步数 + Token 双重保险
Agent 最常见的翻车场景就是陷入「调用工具 → 结果不对 → 再调用 → 还是不对」的死循环。光设 max_steps=10 不够——如果每步 token 消耗巨大,成本会爆炸。
def run_agent_safe(user_input: str, max_steps: int = 10, max_tokens: int = 8000) -> str:
total_tokens = 0
messages = [...]
for step in range(max_steps):
response = client.chat.completions.create(...)
total_tokens += response.usage.total_tokens
if total_tokens > max_tokens:
return f"任务复杂度超出预算(已消耗 {total_tokens} tokens),请简化问题重试。"
# ... rest of loop
return f"达到最大步数 {max_steps},已消耗 {total_tokens} tokens。"
实际经验:80% 的任务 3 步内完成,95% 的任务 5 步内完成。超过 10 步的基本都是 prompt 设计有问题。
3.2 Tool Call 格式容错
有些模型(尤其是国产模型)的 Tool Calling 实现不完全标准——可能返回不规范的 JSON、漏掉 required 参数、甚至输出 markdown 格式的代码块。需要加一层防御:
def safe_parse_tool_args(tool_call) -> dict:
"""容错解析 tool call 参数"""
raw = tool_call.function.arguments
# 尝试直接解析
try:
return json.loads(raw)
except json.JSONDecodeError:
pass
# 尝试剥掉 markdown code fence
cleaned = re.sub(r'^```(?:json)?\s*', '', raw.strip())
cleaned = re.sub(r'\s*```$', '', cleaned)
try:
return json.loads(cleaned)
except json.JSONDecodeError:
# 最后的兜底:用正则提取
args = {}
for key in ['query', 'expression', 'city', 'symbol']:
m = re.search(rf'"{key}"\s*:\s*"([^"]*)"', raw)
if m:
args[key] = m.group(1)
return args if args else {}
3.3 上下文窗口管理:滑动摘要
Agent 跑多轮后 messages 数组会越来越长。解决方法是在中间插入「摘要消息」压缩历史:
def compress_history(messages, keep_last_n: int = 4):
"""保留最近 N 条消息,其余压缩为摘要"""
if len(messages) <= keep_last_n + 6:
return messages
to_compress = messages[1:-keep_last_n] # 保留 system prompt
summary_prompt = "用 100 字以内总结以下对话的关键信息:\n"
summary_prompt += "\n".join(
f"[{m['role']}]: {str(m.get('content',''))[:200]}"
for m in to_compress if m['role'] in ('tool', 'assistant')
)
summary_resp = client.chat.completions.create(
model="gpt-4o-mini", # 用便宜模型做摘要
messages=[{"role": "user", "content": summary_prompt}],
max_tokens=200
)
return [messages[0]] + [
{"role": "system", "content": f"[历史摘要] {summary_resp.choices[0].message.content}"}
] + messages[-keep_last_n:]
四、什么时候该用 Agent,什么时候不该用
不是所有场景都需要 Agent。根据我的实践经验:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单次问答/翻译/摘要 | 直接 LLM 调用 | 不需要工具,Agent 是多余开销 |
| 有明确 API 的工作流(如查数据库→格式化→输出) | 固定 Pipeline | 确定性流程不需要 LLM 做决策 |
| 需要搜索 + 计算 + 多源信息整合 | Agent ✅ | 工具选择和数据整合依赖 LLM 判断 |
| 自动化编码(读代码→改文件→跑测试→修bug) | Agent(多工具协作)✅ | 需要文件读写 + Shell + Git 多工具联动 |
五、FAQ
Q: 为什么不直接用 LangChain?
LangChain 的 Agent 抽象层太厚,出了问题很难调试。而且它的 Tool Calling 封装在 2026 年已经落后于 OpenAI/Anthropic 的原生实现。50 行代码自己写,你对每一步都有完全的控制力——出 bug 时不用翻框架源码。
Q: Tool Calling 和 Function Calling 是一回事吗?
本质相同但是有区别。Function Calling 是 OpenAI 最早提出的术语,指 LLM 输出结构化的函数调用参数。Tool Calling 是更广义的概念,包含工具定义、执行和结果回传的完整循环。2026 年 Anthropic、Google 都实现了自己的 Tool Use,API 各有差异但思想一致。
Q: 国产模型(DeepSeek/Qwen/GLM)支持 Tool Calling 吗?
都支持,但实现质量参差不齐。DeepSeek V3 的 Tool Calling 稳定性已经接近 GPT-4o;Qwen 有时会漏掉 required 参数;GLM 偶尔返回 markdown 包裹的 JSON(需要用上面提到的容错解析器)。建议上线前用 100+ 用例做回归测试。
总结
拆完之后你会发现 AI Agent 本质上就是一个 while 循环 + LLM API + 几个 if-else。真正的难点不在代码,而在:
- 工具设计:粒度太粗 LLM 不知道怎么用,太细又会陷入「调 A → 调 B → 调 A」的循环
- Prompt 工程:系统提示词决定了 Agent 会不会「偷懒」——能一步做完的非要拆成三步
- 容错打磨:LLM 的输出不是 100% 可靠的,每一环都要有 fallback
💡 如果你想把 Agent 接入实际业务(比如自动处理客服工单、自动化运维),建议先在我的博客翻翻这几篇:
LLM 自动化代码审查流水线 ·
AI Coding 的 Prompt Engineering 实战 ·
FastAPI 性能调优实战
本文于 2026 年 7 月撰写,代码基于 openai==1.55.0。所有代码在 Python 3.12 上测试通过。Agent 性能数据基于 GPT-4o-2024-08-06,50 次测试取平均值。