手把手教你写一个 LLM Agent:60 行代码理解核心原理
不依赖任何框架,用纯 Python + OpenAI 兼容接口,从零实现一个能自主调用工具的 ReAct Agent。
市面上 Agent 框架一大堆:LangChain、LlamaIndex、AutoGen……但很多人用了半年框架,被问到「Agent 到底是怎么跑起来的」还是说不清。今天我们抛开框架,60 行代码写一个最小的 Agent,把核心原理吃透——理解了原理,框架只是锦上添花。
一、Agent 到底是什么
先说清楚 Agent 和普通 Chatbot 的区别:
- Chatbot:你问一句,它答一句。模型只能靠参数里的知识说话,不知道今天天气、查不了数据库、动不了你的系统。
- Agent:在「模型」外面套了一个循环,让模型能调用工具、看到执行结果、再决定下一步。感知 → 决策 → 行动,直到任务完成。
一个 Agent 的三个核心组件:
┌─────────────────────────────────┐ │ LLM(大脑):理解任务、做决策 │ │ Tools(手脚):查天气/查库/执行代码 │ │ Loop(循环):把结果喂回去,直到完成 │ └─────────────────────────────────┘这个「模型 + 工具 + 循环」的模式就是 ReAct(Reasoning + Acting):模型先推理要不要用工具,用完看结果,再推理下一步,如此往复。
二、60 行最小实现
用 OpenAI 兼容接口(DeepSeek、Kimi、Qwen 都行,换个 base_url 就好):
import json from openai import OpenAI client = OpenAI(base_url="https://api.deepseek.com/v1", api_key="你的key") MODEL = "deepseek-chat" # 1. 声明工具:告诉模型「你有什么手脚可用」 tools = [{ "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的实时天气", "parameters": { "type": "object", "properties": {"city": {"type": "string", "description": "城市名"}}, "required": ["city"], }, }, }] # 2. 实现工具:真实逻辑,返回字符串 def get_weather(city: str) -> str: # 实际项目里这里是真实 API 调用 return f"{city} 今天晴,25°C" TOOL_MAP = {"get_weather": get_weather} # 3. Agent 循环:推理 → 调用 → 喂回 → 再推理 def run_agent(question: str, max_turns: int = 10) -> str: messages = [ {"role": "system", "content": "你是助手。需要实时信息时必须调用工具,拿到结果后再回答。"}, {"role": "user", "content": question}, ] for _ in range(max_turns): resp = client.chat.completions.create( model=MODEL, messages=messages, tools=tools, ) msg = resp.choices[0].message messages.append(msg) if not msg.tool_calls: # 模型不再要求调用工具 return msg.content # → 给出最终答案,任务完成 for tc in msg.tool_calls: # 执行每个工具调用 result = TOOL_MAP[tc.function.name](**json.loads(tc.function.arguments)) messages.append({ "role": "tool", "tool_call_id": tc.id, "content": result, }) return "达到最大轮数,任务未完成" if __name__ == "__main__": print(run_agent("北京和上海今天哪个更热?适合穿短袖吗?"))运行一下,模型的完整决策链是这样的:
第 1 轮:模型返回 tool_calls = [get_weather(北京)] → 还没答案,继续 第 2 轮:模型返回 tool_calls = [get_weather(上海)] → 对比需要两地数据 第 3 轮:模型返回「上海更热,两地都适合穿短袖」 → 无 tool_calls,结束这就是 Agent 的全部秘密:没有魔法,就是「模型的 function calling + 一个 while 循环」。
三、逐点拆解
1. function calling 协议
模型本身不会执行代码,它只会返回一个 JSON:想调哪个工具、参数是什么。执行永远是你的代码干的。所以「工具描述」写得越清楚,模型选工具越准——description是写给模型看的提示词,不是注释。
2. 为什么必须有循环
很多任务一步做不完。「对比两地天气」需要两次工具调用,「查订单再退款」是两步依赖。循环让模型能把任务拆成多步,每一步基于上一步的真实结果决策——这是 Agent 区别于单次问答的根本。
3.max_turns是保命符
模型可能陷入死循环(反复调用同一个工具、或者工具一直报错)。没有轮数上限,一次失控的任务能把你的 API 余额烧穿。任何 Agent 循环都必须有硬性终止条件:最大轮数、超时、预算上限,至少要有一样。
4. 工具结果必须是字符串
role: tool的消息内容是字符串。工具返回 dict/list 时记得json.dumps,模型才能读到。
四、往上加:从玩具到生产
60 行是骨架,生产级 Agent 在骨架上加四样东西:
| 组件 | 解决什么问题 | 常用方案 |
|---|---|---|
| 记忆 | 多轮对话记住上下文 | messages 列表本身就是短期记忆;长期记忆存向量库 |
| RAG | 模型不知道你的私有知识 | 文档切块 → embedding → 检索 → 塞进 prompt |
| 更多工具 | 能力扩展:执行代码、操作数据库、调内部 API | MCP 协议,工具即插即用 |
| 错误处理 | 工具报错、参数解析失败 | try/except 后把错误信息喂回给模型,让它自己调整 |
错误处理值得单独说:工具报错不要直接崩掉,把报错文本作为工具结果喂回给模型。模型经常能根据报错自己修正参数重试——这比你在代码里写一堆 if-else 重试逻辑优雅得多,模型自己就是个不错的错误恢复器。
五、什么时候用框架
理解了原理之后,框架的取舍就清楚了:
- 2~3 个工具、线性流程→ 60 行手写,没有任何依赖,最好调试
- 多 Agent 协作、复杂编排、需要可视化和回调→ LangGraph / AutoGen 这类框架省事
- 工具生态对接→ 直接用 MCP(Model Context Protocol),写一次工具,所有支持 MCP 的 Agent 都能用
我的建议:先用 60 行跑通一个真实需求,再决定要不要框架。反过来先上框架的人,最后往往被框架的抽象层搞得怀疑人生。
总结
- Agent = LLM + 工具 + 循环,核心是 ReAct 模式
- 模型只做决策(返回要调用的工具和参数),执行是你的代码的事
- 循环必须带终止条件,
max_turns是保命符 - 工具报错喂回给模型,让模型自己恢复
- 先手写跑通,再考虑框架
完整代码复制就能跑,把base_url和api_key换成你自己的模型服务即可。下一篇可以讲多 Agent 协作和 MCP 工具生态,感兴趣的点个关注。