微信扫码
添加专属顾问
从零拆解AI Agent的核心循环,让你亲手实现自己的智能体。 核心内容: 1. AI Agent的本质与ReAct循环模式解析 2. 从零构建Agent的完整实验记录与代码实现 3. 本地与云端混合模式的实战应用
作者:AI拉呱(Errol Yan)
定位:AI领域深度内容与实战方法分享
我每天都在用 Claude、Codex、Cursor、Gemini、Copilot 或 Junie,但很长一段时间里,我依然说不清“聊天机器人”到底是从哪一行代码开始,变成了“agent”。
我也讲不明白,究竟是什么让它们具备了 agent 的行为特征。于是我决定自己从零写一个最朴素的版本,把这件事彻底拆开看。
对我来说,理解一个新概念最好的方式,就是亲手做一遍,然后把它讲明白。本文正好做这两件事:它既是一次实验记录,也是一个实操教程。
我们会从大约 50 行 Python 开始,接上 OpenAI,切换到 Ollama 本地模型,再做一个本地 orchestration + 云端 delegation 的混合模式,随后加入 tools、实现 MCP,最后再和 Claude CLI 做一个对照。读完之后,你会清楚看到 agent 在底层到底发生了什么。
没有 LangChain,没有 LangGraph,没有 CrewAI。只有 Python、一个 LLM,以及一个 while 循环。
在你动手写之前,先得给它下定义。
一个 AI agent,本质上是一个程序,它需要:
普通的 LLM 调用是一锤子买卖:给 prompt,拿回答,结束。而 agent 的不同点在于它会循环。它会拿着高层目标不断重复“思考、行动、观察、再决定”的过程,直到任务完成。
这套循环,才是语言模型变成 agent 的关键。
大多数 agent 会遵循一个常见模式,叫 ReAct(Reason + Act)。模型不是直接跳到最终答案,而是先形成一个“想法”,再选择一个“动作”(通常是工具调用),然后根据“观察到的结果”继续推进。
图 1: ReAct 循环。先 Reason,再 Act,观察结果后再推理,直到模型认为已经足够回答。
模型没有真正意义上的意识,也没有人类式反思。它有的,是完整的上下文窗口:它此前做过什么、看到了什么、工具返回了什么,全部保留在消息历史里。ReAct 把这一点组织成了一种“看起来像会自我修正”的行为模式。
每次循环都非常简单:
这就是最核心的 agent 架构。
第一步,我们用云端 API 当大脑。这里用 OpenAI,是因为它的 tool calling 接口最直观;但只要兼容 OpenAI 风格,Gemini、Anthropic 等都能用类似思路实现。
最核心的 agent 机制其实就是下面这段代码:
def run_agent(task: str, client: OpenAI, model: str = "gpt-4o-mini") -> str:
messages = [
{
"role": "system",
"content": (
"You are a helpful assistant. Use tools when needed. "
"When you have a final answer, respond without calling any tools."
),
},
{"role": "user", "content": task},
]
while True:
response = client.chat.completions.create(
model=model,
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
return message.content
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f" > calling {name}({args})")
fn = TOOL_FUNCTIONS.get(name)
result = fn(**args) if fn else f"Unknown tool: {name}"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})关键点就在这句:if not message.tool_calls。
如果模型返回的是普通文本,而没有请求调用工具,那就说明它认为自己已经拿到足够信息,可以直接回答。agent 于是退出并返回答案。
如果模型请求了工具,那 agent 就执行这些工具,把结果追加到 messages 里,再回到 while 循环顶部继续下一轮。
这里的 messages,其实就是 agent 的短期记忆。每次工具调用和结果都会被追加进去,因此当模型最终下判断时,它已经看见自己一路做过的所有事。
另外,system prompt 也很重要。它像方向盘一样,规定模型什么时候该用工具、什么时候该停止,以及最终答案应该长什么样。现实中的生产级 agent,system prompt 往往会比这个例子大得多。
为了让例子足够具体,我们先加三个最简单的工具:当前日期时间、计算器,以及一个天气 stub。真实项目里,你会把天气 stub 换成真实 API。
import json
import os
from datetime import datetime
from openai import OpenAI
def get_current_date() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def calculate(expression: str) -> str:
try:
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"Error: {e}"
def get_weather(city: str) -> str:
return f"Weather in {city}: 72°F, partly cloudy"
TOOL_FUNCTIONS = {
"get_current_date": get_current_date,
"calculate": calculate,
"get_weather": get_weather,
}工具 schema 则告诉 LLM:系统里有哪些工具、它们能干什么、需要什么参数。
TOOLS = [
{
"type": "function",
"function": {
"name": "get_current_date",
"description": "Returns the current date and time",
"parameters": {"type": "object", "properties": {}, "required": []},
},
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluates a math expression and returns the result",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "A Python math expression, e.g. '2 + 2' or '100 * 0.15'",
}
},
"required": ["expression"],
},
},
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Gets current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"],
},
},
},
]运行方式如下:
if __name__ == "__main__":
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
task = "What's today's date? Also, what is 15% of 847? And what's the weather in Tokyo?"
print(f"Task: {task}\n")
answer = run_agent(task, client)
print(f"\nAnswer: {answer}")输出会像这样:
Task: What's today's date? Also, what is 15% of 847? And what's the weather in Tokyo?
> calling get_current_date({})
> calling calculate({'expression': '847 * 0.15'})
> calling get_weather({'city': 'Tokyo'})
Answer: Today is 2026-04-30 09:14:22. 15% of 847 is 127.05.
The weather in Tokyo is 72°F and partly cloudy.可以看到,模型在第一轮就识别出自己需要哪三个工具,调用完之后再把结果整合成最终答案。
没有框架,没有编排平台,只有一个循环。
Ollama 提供 OpenAI-compatible API,这意味着上面的 agent 几乎不用改,只改一个 client 配置就能跑本地模型:
ollama_client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama",
)
answer = run_agent(task, ollama_client, model="qwen2.5")就这么简单。对 agent 代码而言,它根本不在乎对面是 OpenAI,还是你本机跑起来的 Ollama。
启动 Ollama 的方式一般是:
ollama pull qwen2.5
ollama serve之后,这个 agent 就可以完全离线运行。我自己很喜欢用这个模式测试新工具,因为不用烧 API 预算,敏感数据也不会离开本机。
这是很多人第一次做本地 agent 时会踩到的坑。
比如我一开始试的是 mistral(Mistral 7B)。程序能正常跑,但输出长这样:
Answer: I need to call get_current_date() to find today's date.
Let me use the calculate tool: calculate(expression="847 * 0.15")...也就是说,它只是用自然语言“描述”自己想调用工具,却没有真正产生结构化 tool calls。结果就是 response.tool_calls 始终为空,于是 agent 直接退出,把那段描述文本当成最终答案返回。
这不是 agent 代码有 bug,而是模型本身不支持 OpenAI 风格的函数调用格式。
很多模型会模仿出“像是工具调用”的文本,但不会真正输出可执行的结构化 JSON。当前通过 Ollama 对 function calling 支持更稳定的,通常是像 qwen2.5 这类模型。
如果你的 agent 一上来就不调工具,先怀疑模型,再怀疑代码。
还有一种非常实用的模式:让本地模型负责 orchestration,只在任务真的复杂时,再调用云端大模型。
也就是说,默认跑本地 agent,但给它一个能“请云端专家出手”的工具:
def ask_cloud_expert(question: str) -> str:
"""Delegate complex questions to a cloud model."""
cloud_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = cloud_client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": question}],
)
return response.choices[0].message.content把它加入 TOOL_FUNCTIONS 和 TOOLS 之后,本地模型就可以在自己不擅长时,主动把问题交给云端更强模型。
例如:
answer = run_agent(
task="What's 2+2? Also, explain the philosophical implications of the Ship of Theseus paradox.",
client=ollama_client,
model="qwen2.5"
)这时,本地模型可以自己处理 2+2,但在遇到“忒修斯之船”这种更复杂的哲学问题时,调用 ask_cloud_expert()。你付费的云端调用,就从“整个任务都在云上跑”,变成“只为真正复杂的那一步买单”。
接下来再加几个更现实一点的工具:web_search、read_file 和 write_file。
from pathlib import Path
def web_search(query: str) -> str:
return (
f"Search results for '{query}':\n"
f"1. Wikipedia: comprehensive overview\n"
f"2. Recent article: explained in 5 minutes\n"
f"3. Official docs"
)
def read_file(path: str) -> str:
return Path(path).read_text()
def write_file(path: str, content: str) -> str:
Path(path).write_text(content)
return f"wrote {len(content)} chars to '{path}'"
TOOL_FUNCTIONS = {
"get_current_date": get_current_date,
"calculate": calculate,
"get_weather": get_weather,
"web_search": web_search,
"read_file": read_file,
"write_file": write_file,
}这些 schema 也加进 TOOLS 后,agent 就不只是会算数和报天气,而是开始具备:
到这一步,它已经能做很多实际工作了。
但还有一个明显限制:所有工具都被硬编码在脚本里。也就是说,工具无法复用,别的 agent 也没法直接拿来用。
这正是 MCP(Model Context Protocol)要解决的问题。
MCP 由 Anthropic 在 2024 年底推出,本质上是一个标准协议,定义了 agent 如何从任意 server 发现并调用工具。这个 server 可以是你自己写的,也可以是第三方提供的 GitHub、Slack、Postgres、Google Drive 等服务。
图 2: MCP 架构。一个 client(你的 agent)可以连接多个 server,而每个 server 暴露自己的工具。
当你的 DIY agent 变成 MCP client 后,工具定义就不再需要全部手写在本地脚本里,而是可以通过协议向 server 发现。
从 agent 视角看,MCP 工具和本地函数没什么区别:它们同样会出现在 TOOLS 里,被模型选择,被调用,再返回结果。
真正改变的是,工具从“写死在脚本里”变成“可共享、可复用、可独立维护”。
反过来,如果你希望自己的工具能被任意 MCP-compatible agent 使用,那就需要写一个 MCP server。
下面是一个极简的例子,它暴露了两个工具:to_uppercase 和 count_words。
# mcp_server.py — a real MCP server in 10 lines
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("mini-tools")
@mcp.tool()
def to_uppercase(text: str) -> str:
"""Convert text to uppercase."""
return text.upper()
@mcp.tool()
def count_words(text: str) -> int:
"""Count the number of words in a string."""
return len(text.split())
if __name__ == "__main__":
mcp.run()它之所以有代表性,不是因为功能复杂,而是因为边界清晰:mcp_server.py 是一个独立进程。agent 通过 JSON-RPC 与它通信,工具调用和结果返回都遵循统一协议。
这意味着,你完全可以把它替换成远端服务,而 agent 代码本身几乎不用改。
任何 MCP-compatible agent 都能接上这个 server:Claude Desktop、Cursor、你自己写的 agent,或者其他支持 MCP 的客户端。
这正是 MCP 生态能扩展起来的原因。过去每个 agent 都要各自重写“调用 GitHub API”或“查询 Postgres”;现在,只要有人把能力封装成 MCP server,其他 agent 就能直接复用。
Claude Code 是生产级工具,而我写的这个 agent,更像学习工具。
它们的差别其实非常明确。
Claude Code 能做很多我这个 agent 做不到的事:
而我这个 agent,没有这些“安全护栏”和“成熟基础设施”。它只有几个工具、一个 messages 列表,以及一个循环。工具一旦抛异常,就可能直接崩。
但它的优势也很明确:
所以,如果我要交付稳定产品,我会用 Claude Code;但如果我要真正理解 agent 的底层机制,或者做一些我不想被框架束手束脚的实验,那我会从这个循环开始。
你不需要 LangGraph 才能理解 agent 是什么。但当 retries、checkpoints、approval gates 变成刚需时,框架就开始有意义了。
上面这个极简版本没有错误处理。工具抛错就崩,没有重试,没有人工审批,没有跨会话 memory,也没有多 agent 并行。
LangGraph 通过把 agent 建模成显式状态机,解决的是这些“工程化问题”:checkpointing、结构化错误处理、人类介入节点,以及更好的 observability。
CrewAI 和 AutoGen 更偏向多 agent 协作:你可以定义 research、writer、critic 等不同角色,让它们用不同 prompt 或模型协同工作。
Claude Agents SDK 和 OpenAI Assistants API 则是托管式运行时:你把状态管理、工具路由和线程管理交给平台,自己换取更快的交付速度。
那个 50 行版本,更像是一张草图。而 LangGraph 则是在这张草图之上,加上真正能承重的结构。
结论也很简单:
我最初只是想搞明白,AI agent 到底是怎么工作的。现在我明白了。
自己写这个东西之后,我终于有了完整的心智模型:
未来我还是会在很多项目里继续使用 LangGraph、Claude Agents SDK 这类框架,因为它们确实解决了真实问题。但我也会有一些项目,从这个极简版开始,因为我完全知道它在做什么,而且修改它时不需要先和一个庞大抽象层搏斗。
现在你也已经看到了同样的东西:并没有什么神秘魔法。模型只是看着会话历史,判断自己是否有足够信息回答,还是需要调用工具;然后不断重复,直到任务完成。
所谓 retry、human-in-the-loop、memory、multi-agent orchestration,都是在这条循环之上逐渐叠加出来的工程层。
先把最朴素的版本写出来。之后,你再决定自己到底需不需要框架。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-06-30
从文本到多模态:大模型非结构化数据加工与质量控制实践
2026-06-30
从Anthropic的B端战略,给迷茫中的扣子一些建议
2026-06-30
Claude最新:创始人实操手册:打造 AI 原生初创公司(中文版)
2026-06-30
本体+AI驱动的AI智能体工厂-从设计到实现
2026-06-30
微信AI,能避开豆包手机的窘境吗?
2026-06-30
LangAlpha是如何在架构上实现Harness 和 Loop Engineering
2026-06-30
Codex 权限 Profile:sandbox 不再一刀切
2026-06-30
Google 悄悄开闸:Gemini API 免费放量 1M TPM,OpenAI 和 Anthropic 开发者坐不住了
2026-04-15
2026-04-07
2026-04-07
2026-04-24
2026-04-17
2026-04-05
2026-04-02
2026-04-05
2026-04-14
2026-04-24
欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。
在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。
一、 定义
本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。
会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。
知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。
二、 账号注册与登录
登录方式:本网站支持以下登录方式,您可根据实际情况选择:
微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。
手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。
账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。
实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。
未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。
三、 服务内容与规范
知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。
服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。
禁止行为:您在使用服务时不得实施以下行为:
利用技术手段批量爬取、下载、转存知识库内容;
将知识库内容用于商业目的或未经授权地向第三方传播;
干扰本网站正常运行或侵犯其他用户合法权益;
发布违法违规信息或从事违反公序良俗的活动。
四、 知识产权声明
权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。
有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。
侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。
五、 个人信息保护
我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。
您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。
您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。
六、 免责声明
内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。
不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。
第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。
七、 违约责任
如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。
如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。
八、 法律适用与争议解决
本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。
因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。
九、 其他
本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。
本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。
我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。