Update avaliable. Click RELOAD to update.
📱 安装应用到主屏幕,获得更好体验
目录

Building Ai Agent From Scratch

 1|---
 2|title: 从零手写 AI Agent:50 行 Python,没有魔法,只有循环
 3|name: building-ai-agent-from-scratch
 4|date: 2026-05-12
 5|tags: [AI, Python, LLM, Agent, MCP, 教程]
 6|excerpt: 50 行 Python 代码,一个 while 循环,就构成了 AI Agent 的核心——没有 LangChain,没有框架,没有魔法。
 7|categories: [AI]
 8|---
 9|
10|* 目录
11|{:toc}
12|
13|## 1. 什么是 AI Agent
14|
15|Sergey Nes 每天用 Claude、Codex、Cursor、Gemini,却说不清"聊天机器人"和"Agent"之间的那条线到底画在哪。最好的理解方式就是自己动手建一个,然后讲给别人听。
16|
17|**AI Agent 的定义很简单:**
18|
19|- 接受用户的**高层次任务**
20|- **推理**接下来该做什么
21|- **执行动作**(调用工具、搜索网页、读文件)
22|- **观察**结果
23|- **判断**是继续还是结束
24|
25|普通的 LLM 调用是一次性操作:发送 Prompt,收到回复,结束。Agent 之所以不同,是因为它**循环**。
26|
27|这个 think → act → observe → decide 的循环,就是把语言模型变成 Agent 的关键。
28|
29|[![ReAct 循环图](https://fla.cdn.bosyun.com/wangjundev/20260512/01-react-loop.jpg-lazy){: data-src="https://fla.cdn.bosyun.com/wangjundev/20260512/01-react-loop.jpg"}](https://fla.cdn.bosyun.com/wangjundev/20260512/01-react-loop.jpg)
30|
31|## 2. ReAct 模式:推理 + 行动
32|
33|大多数现代 Agent 遵循 **ReAct(Reason + Act)** 模式。LLM 不会直接跳到最终答案,而是:
34|
35|1. 产生一个"思考"——下一步该做什么
36|2. 执行一个"行动"——工具调用
37|3. 等待工具的返回结果
38|4. 把结果放回上下文,重复这个过程
39|
40|模型没有意识,也没有自我反思。它有的只是**对话历史**——每次行动和结果都留在上下文窗口中。
41|
42|每个循环发生的事情:
43|
44|```
45|发送当前对话给 LLM       → system prompt + 用户消息 + 历史工具结果
46|LLM 返回最终答案或工具调用 → 如果是工具调用,执行并发结果追加到对话
47|再次发送给 LLM           → 直到 LLM 返回最终答案
48|```
49|
50|**这就是整个架构。**
51|
52|## 3. 最小实现:50 行 Python
53|
54|核心 Agent 机制就是一个函数:
55|
56|```python
57|def run_agent(task: str, client: OpenAI, model: str = "gpt-4o-mini") -> str:
58|    messages = [
59|        {"role": "system", "content": "你是一个助手。可以使用工具。每次获得工具结果后,判断是否足够回答问题。"},
60|        {"role": "user", "content": task}
61|    ]
62|    
63|    while True:
64|        response = client.chat.completions.create(
65|            model=model,
66|            messages=messages,
67|            tools=TOOLS,
68|        )
69|        message = response.choices[0].message
70|        messages.append(message)
71|        
72|        if not message.tool_calls:
73|            return message.content
74|        
75|        for tool_call in message.tool_calls:
76|            result = execute_tool(tool_call)
77|            messages.append({
78|                "role": "tool",
79|                "tool_call_id": tool_call.id,
80|                "content": result
81|            })
82|```
83|
84|**关键行:** `if not message.tool_calls`——如果模型返回的文本不请求任何工具,说明它认为已经足够回答问题了。Agent 退出并返回文本。
85|
86|`messages` 列表就是 Agent 的**短期记忆**。每一次工具调用和结果都追加到它后面。当 LLM 决定完成时,它已经看到了自己做过的所有事和学到的一切。
87|
88|## 4. 换成本地模型:改一行代码
89|
90|Ollama 暴露了 OpenAI 兼容的 API,所以完全相同的 Agent 代码跑在本地模型上只需要**一个改动**:
91|
92|```python
93|ollama_client = OpenAI(
94|    base_url="http://localhost:11434/v1",
95|    api_key="ollama"  # Ollama 不验证 key,但不能留空
96|)
97|run_agent("今天的日期是什么?", client=ollama_client, model="qwen2.5")
98|```
99|    100|代码完全不知道自己在跟 OpenAI 的服务器聊天,还是在跟本地机器上的模型说话。    101|    102|### 模型选择的坑    103|    104|作者试了 Mistral 7B,Agent 没有报错,但输出变成了这样:    105|    106|```    107|Answer: I need to check the current date.     108|Tool: get_current_date()    109|Arguments: {}    110|```    111|    112|**纯文本描述工具调用,没有实际的工具调用。** `response.tool_calls` 每次都是空的,所以 Agent 立即退出了。    113|    114|这不是 Agent 代码的 Bug,是**模型不支持 OpenAI 风格的结构化函数调用**。可靠支持函数调用的模型:    115|    116|[![函数调用支持表](https://fla.cdn.bosyun.com/wangjundev/20260512/02-function-calling-table.jpg-lazy){: data-src="https://fla.cdn.bosyun.com/wangjundev/20260512/02-function-calling-table.jpg"}](https://fla.cdn.bosyun.com/wangjundev/20260512/02-function-calling-table.jpg)    117|    118|**如果你的 Agent 立即返回不调用任何工具,先怀疑模型,不要怀疑代码。**    119|    120|## 5. 混合模式:本地编排 + 云端推理    121|    122|可以在本地编排,只有在任务真正需要时才调用云端:    123|    124|```python    125|def ask_cloud_expert(question: str) -> str:    126|    """当本地模型不足以回答复杂问题时的备选"""    127|    cloud_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])    128|    response = cloud_client.chat.completions.create(    129|        model="gpt-4o-mini",    130|        messages=[{"role": "user", "content": question}],    131|    )    132|    return response.choices[0].message.content    133|```    134|    135|运行时:    136|    137|```    138|Task: 2+2 等于几?还有,解释忒修斯之船的哲学含义    139|> Local model: 处理 2+2(通过计算器工具)    140|> Local model: 意识到哲学问题超出能力范围    141|> Local model: 调用 ask_cloud_expert()    142|> GPT-4: 返回完整的哲学分析    143|```    144|    145|本地模型处理简单计算,复杂推理交给 GPT-4。**一次云端调用,成本几乎为零。**    146|    147|## 6. MCP:让 Agent 发现和调用任意工具    148|    149|DIY Agent 有一个缺失的功能:无法跨项目分享和复用工具。一切都被硬编码在脚本里。    150|    151|**MCP(Model Context Protocol)** 解决了这个问题。它定义了一套统一的标准,让任何 Agent 都能从任何服务器发现和调用工具:    152|    153|```    154|Agent (MCP Client) → MCP Server → 工具 1    155|                   → MCP Server → 工具 2    156|                   → MCP Server → 工具 3    157|```    158|    159|[![MCP 架构图](https://fla.cdn.bosyun.com/wangjundev/20260512/03-mcp-architecture.jpg-lazy){: data-src="https://fla.cdn.bosyun.com/wangjundev/20260512/03-mcp-architecture.jpg"}](https://fla.cdn.bosyun.com/wangjundev/20260512/03-mcp-architecture.jpg)    160|    161|### MCP Server 只需 10 行    162|    163|```python    164|# mcp_server.py    165|from mcp.server.fastmcp import FastMCP    166|    167|mcp = FastMCP("mini-tools")    168|    169|@mcp.tool()    170|def to_uppercase(text: str) -> str:    171|    return text.upper()    172|    173|@mcp.tool()    174|def count_words(text: str) -> int:    175|    return len(text.split())    176|    177|mcp.run()    178|```    179|    180|**关键洞察:** Agent 不关心工具是同一个文件里的 Python 函数,还是在互联网另一端运行的服务。只要它说 MCP 协议,就能用。    181|    182|## 7. vs Claude Code:理解 vs 生产    183|    184|作者的诚实对比:    185|    186|| 维度 | Claude Code | DIY Agent |    187||------|-------------|-----------|    188|| 子Agent | ✅ 隔离上下文窗口 | ❌ 不支持 |    189|| 危险操作确认 | ✅ 请求批准 | ❌ 直接执行 |    190|| 持久记忆 | ✅ 跨会话 | ❌ 仅单次对话 |    191|| 可读性 | ❌ 黑盒 | ✅ 每一行都懂 |    192|| 离线运行 | ❌ 依赖云端 | ✅ Ollama 完全离线 |    193|| 混合模式 | ❌ 不支持 | ✅ 本地+云端 |    194|    195|> "如果要交付可靠的东西,我用 Claude Code。如果要知道底层发生了什么,或者快速原型一个框架很难实现的功能,我手写循环。"    196|    197|## 8. 什么时候用框架    198|    199|> **你不需要 LangGraph 来理解什么是 Agent。当你需要重试、检查点和审批闸门时,你才需要它。**    200|    201|- **LangGraph**:把 Agent 建模为带显式节点和边的状态机    202|- **CrewAI / AutoGen**:多 Agent 协作,不同角色各司其职    203|- **Claude Agents SDK / OpenAI Assistants**:托管运行时,省去状态管理    204|    205|50 行的版本是草图。LangGraph 是同一张草图变成的带有承重墙的建筑。    206|    207|> **生产环境用框架。理解底层写循环。**    208|    209|## 9. 建这个项目教会我的    210|    211|作者说:"建这个给了我完整的思维模型。我现在能准确看到 Agent 可能在什么地方卡住、为什么它选这个工具而不是另一个、什么时候添加更多工具实际上会让事情更糟。"    212|    213|当需要使用框架时,你会知道它在帮你做什么。当不需要时,你不会引入一个无法调试的依赖。    214|    215|> **先建朴素版本。再决定。**    216|    217|知识链接:    218|    219|- 完整代码:github.com/sergenes/mini_agent    220|- OpenAI Function Calling:platform.openai.com/docs/guides/function-calling    221|- Ollama API:github.com/ollama/ollama    222|- MCP:modelcontextprotocol.io    223|- ReAct 论文 (Yao et al., 2022):arxiv.org/abs/2210.03629    224|
版权所有,本作品采用知识共享署名-非商业性使用 3.0 未本地化版本许可协议进行许可。转载请注明出处:https://www.wangjun.dev//2026/05/building-ai-agent-from-scratch/

Related posts