【纯AI无人工修改】AI Agent从0到1实战:50行Python手写核心循环,一次看懂所有Agent框架的底层逻辑
AI Agent从0到1实战50行Python手写核心循环一次看懂所有Agent框架的底层逻辑本文从零开始用50行Python代码实现一个完整的AI Agent核心循环。不依赖LangChain、不引入任何Agent框架让你彻底看清Agent的本质 —— 读完这篇文章所有Agent框架在你眼里都只是同一件事的不同封装。一、为什么要手写Agent2026年AI Agent赛道已经卷到飞起。LangChain、CrewAI、AutoGPT、Dify、Coze……每个框架都在宣称自己是最佳实践。但当你真的用起来时会发现LangChain 500MB 的依赖树一个简单的工具调用要翻三层抽象出了问题不知道该怪框架还是怪 prompt排查全靠猜每个框架的创新本质上都是同一件事的重新封装剥掉所有框架的外衣AI Agent 的核心循环只有 50 行代码。理解了这 50 行你就拥有了一把万能钥匙——无论未来出现什么新框架你都能一眼看穿它的底层在干什么。本文所有代码基于 DeepSeek API国内直连、注册即用、免费额度够跑几千次实验你不需要任何付费 API Key 就能完整复现。二、Agent 的本质一个公式说清楚在写代码之前先用一句话定义 AgentAgent LLM推理 × 工具执行 × 循环迭代展开来说Agent 做的事情只有三件LLM 推理把当前任务 可用工具清单 历史执行记录拼成一段 prompt发给 LLM工具执行解析 LLM 的返回结果如果它说我要调用工具 A就执行工具 A 并拿到结果循环迭代把工具执行的结果追加到上下文中回到第 1 步直到 LLM 说任务完成就这么简单。所有 Agent 框架的差异只是在怎么拼 prompt和怎么管理上下文这两个维度上做了不同的选择。核心循环是完全一样的。下面我们直接写代码。三、50行实现Agent核心循环3.1 环境准备pipinstallopenai只需要这一个依赖。不用装 LangChain不用装向量数据库不用装任何东西。3.2 完整代码importjsonfromopenaiimportOpenAI# 配置 clientOpenAI(api_keyyour-deepseek-api-key,# 从 platform.deepseek.com 获取base_urlhttps://api.deepseek.com,)MODELdeepseek-v4-flashMAX_ITER10# 最大循环次数防止死循环# 工具注册表 defget_weather(city:str)-str:查询指定城市的天气weather_data{北京:晴天25°C湿度45%,上海:多云28°C湿度60%,深圳:阵雨30°C湿度75%,}returnweather_data.get(city,f未找到{city}的天气数据)defcalculator(expression:str)-str:执行数学计算支持加减乘除try:returnstr(eval(expression))exceptExceptionase:returnf计算错误:{e}TOOLS{get_weather:{function:get_weather,description:查询指定城市的天气参数 city: 城市名称如北京、上海,},calculator:{function:calculator,description:执行数学计算参数 expression: 数学表达式如25*410,},}# Prompt 构造 SYSTEM_PROMPT你是一个智能助手可以调用工具来完成任务。 当你需要调用工具时请严格按以下JSON格式输出不要输出任何其他内容 {tool: 工具名, args: {参数名: 参数值}} 当你不需要调用工具、任务已完成时请直接输出最终答案不要使用JSON格式。 可用工具 {tools_desc}defbuild_tools_desc():构建工具描述文本lines[]forname,infoinTOOLS.items():lines.append(f-{name}:{info[description]})return\n.join(lines)# Agent 核心循环 defagent_loop(user_query:str)-str: Agent 核心循环LLM推理 → 工具执行 → 循环迭代 这是所有 Agent 框架共有的底层逻辑 messages[{role:system,content:SYSTEM_PROMPT.format(tools_descbuild_tools_desc())},{role:user,content:user_query},]foriterationinrange(MAX_ITER):# 第1步LLM 推理responseclient.chat.completions.create(modelMODEL,messagesmessages,temperature0.0,)replyresponse.choices[0].message.content.strip()# 第2步判断是否需要调用工具ifreply.startswith({)andreply.endswith(}):try:tool_calljson.loads(reply)tool_nametool_call.get(tool,)tool_argstool_call.get(args,{})iftool_nameinTOOLS:# 执行工具tool_fnTOOLS[tool_name][function]resulttool_fn(**tool_args)# 将工具调用和结果追加到上下文messages.append({role:assistant,content:reply})messages.append({role:user,content:f工具 {tool_name} 的执行结果{result}\n\n请根据这个结果继续完成任务。如果任务已完成请给出最终答案。})continueelse:messages.append({role:user,content:f错误未知工具 {tool_name}。可用工具{, .join(TOOLS.keys())}。请重新选择。})continueexceptjson.JSONDecodeError:pass# 第3步没有工具调用 → 返回最终答案returnreplyreturn已达到最大循环次数任务可能陷入了死循环。请检查工具定义和任务复杂度。# 测试 if__name____main__:# 测试1单工具调用resultagent_loop(北京今天天气怎么样)print(f测试1结果{result})# 测试2多步推理先计算再查天气resultagent_loop(帮我算一下 365 * 24然后告诉我上海天气)print(f测试2结果{result})这就是全部。一个完整的、能用的 AI Agent50 行核心逻辑零框架依赖。四、逐行拆解这50行到底在做什么4.1 工具注册表 —— 一行 if/elif 搞定TOOLS{get_weather:{function:get_weather,description:查询指定城市的天气...,},calculator:{...},}LangChain 里定义一个工具需要继承BaseTool类 → 实现_run方法 → 用tool装饰器注册 → 传给ToolExecutor。但其实工具的本质就是一个名字 一段描述 一个函数指针。一个dict完全够用。4.2 System Prompt —— Agent 的大脑说明书SYSTEM_PROMPT你是一个智能助手... 当你需要调用工具时请严格按以下JSON格式输出 {tool: 工具名, args: {参数名: 参数值}} 这是整个 Agent 最关键的部分。你用 prompt 定义了 LLM 的行为规范——什么时候用工具、用什么格式返回。LangChain 的AgentExecutor、CrewAI 的Agent类本质上都是在帮你拼这行 prompt。这正是很多人用框架踩坑的根源框架帮你拼了 prompt但拼出来的格式和你的任务不匹配你只能对着黑盒猜问题。手写 prompt 的好处是——每条指令都是你自己写的出了问题你知道该调哪里。4.3 核心循环 —— 30行的 while Trueforiterationinrange(MAX_ITER):# 1. LLM 推理responseclient.chat.completions.create(...)# 2. 判断是否需要工具调用ifreply.startswith({)andreply.endswith(}):tool_calljson.loads(reply)# 2a. 执行工具resulttool_fn(**tool_args)# 2b. 追加结果到上下文messages.append(...)continue# 回到第1步# 3. 返回最终答案returnreply这 30 行是 LangChain 几千行AgentExecutor源码的等价实现。看懂这个循环你就看懂了所有 Agent 框架。关键细节MAX_ITER 10防止死循环的最后防线。LangChain 里叫max_iterationsAutoGPT 里叫max_cycles名字不同做的事完全一样。messages.append(...)每轮把工具结果追加到消息列表让 LLM 知道上一步干了什么。这是 Agent 的记忆机制。json.loads(reply)强制 JSON 解析。如果 LLM 返回了非标准格式这里会报错并让 LLM 重试——这是最原始的自我纠错机制。五、5个翻车现场从Demo到能用的距离写完 50 行代码跑通 Demo 只是第一步。从 Demo 到能放心用中间有 5 个常见的坑。我把它们一一列出来并告诉你怎么改。翻车现场 1LLM 返回了非法 JSON现象json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)LLM 有时会返回类似好的我来调用天气工具{tool: get_weather...}这种带前缀的格式导致 JSON 解析失败。修复加3行代码# 在 json.loads(reply) 之前加入importre json_matchre.search(r\{.*\},reply,re.DOTALL)ifjson_match:replyjson_match.group()用正则把 JSON 部分从任意文本中提取出来容错率瞬间提升。翻车现场 2死循环 —— Agent 不停调用同一个工具现象第1轮调用 get_weather(北京) 第2轮调用 get_weather(北京) 第3轮调用 get_weather(北京) ... 第10轮被 MAX_ITER 强行终止LLM 没有从工具结果中得出任务已完成的结论陷入了重复调用。修复2种策略# 策略A在工具返回结果后主动加上强制总结指令result_promptf工具结果{result}\n\n请基于以上结果直接给出最终答案不要再调用工具。messages.append({role:user,content:result_prompt})# 策略B检测连续3次调用同一工具且参数相同 → 强制返回last_calls[]# 记录最近3次调用# ... 在循环中检测 ...iflen(set(last_calls[-3:]))1:return任务执行异常检测到重复工具调用已自动终止。实际生产环境建议策略 A温和引导和策略 B硬性兜底结合使用。翻车现场 3上下文爆炸 —— 消息越来越长现象运行 5-6 轮后messages 数组积累了几千个 tokenLLM 开始遗忘最早的工具调用结果回复质量断崖式下降。修复滑动窗口# 在 messages.append 之后加入MAX_CONTEXT_MESSAGES20# 只保留最近20条消息iflen(messages)MAX_CONTEXT_MESSAGES:# 保留 system prompt 最近 N-1 条messages[messages[0]]messages[-(MAX_CONTEXT_MESSAGES-1):]LangChain 的ConversationBufferWindowMemory做的就是这件事。翻车现场 4LLM 调用了一个不存在的工具现象LLM 返回{tool: send_email, ...}但你根本没定义send_email。这是 LLM 的幻觉在工具调用场景的体现——它脑补了一个它觉得应该存在的工具。修复硬校验 重试iftool_namenotinTOOLS:retry_promptf错误{tool_name} 不是可用工具。可用工具有{, .join(TOOLS.keys())}。请重新选择。messages.append({role:user,content:retry_prompt})continue# 回到循环开头让 LLM 重试原代码里已经有了这个处理但要注意最多重试 2-3 次重试太多次可能是 prompt 本身有问题。翻车现场 5凌晨三点模型抽风 —— 非确定性输出现象白天跑得好好的 Agent凌晨突然开始用英文回复、输出格式乱掉、甚至开始拒绝执行工具。LLM 不是确定性系统。同一段 prompt 在不同时刻可能得到不同的输出。修复多重保险# 保险1temperature0.0 —— 降低随机性已在上方代码中设置# 保险2结果格式校验defvalidate_final_answer(reply:str)-bool:检查最终答案是否看起来合理# 不应包含工具调用的 JSONifreply.strip().startswith({):returnFalse# 不应为空iflen(reply.strip())5:returnFalsereturnTrue# 保险3降级策略 —— 连续失败后直接返回原始结果ifconsecutive_failures3:return抱歉当前服务暂时不可用请稍后重试。六、对比主流框架它们的源码和这50行是同构的理解了上面的 50 行代码后我们来看看主流框架的底层实现。LangChain AgentExecutorLangChain 的AgentExecutor._take_next_step()方法本质就是我们的核心循环概念我们的实现LangChain 对应工具注册TOOLS {...}dictTool类 BaseTool抽象Prompt 构造SYSTEM_PROMPT.format(...)ChatPromptTemplateformat_log_to_str解析 LLM 输出json.loads(reply)AgentOutputParser子类执行工具tool_fn(**tool_args)ToolExecutor.invoke()循环控制for iteration in range(MAX_ITER)while not stopmax_iterations上下文管理messages.append(...)AgentActionAgentFinish事件流你看LangChain 把这 50 行的每一部分都拆成了独立的抽象层。好处是可插拔代价是——出了问题你要穿过 3 层继承链才能找到真正的执行逻辑。CrewAICrewAI 的核心是Agent类 Task类。它的执行循环跟上面一模一样区别在于多了角色扮演给每个 Agent 分配不同的 system prompt多了任务依赖Task A 的输出作为 Task B 的输入多了顺序/并行执行调度剥掉这些底层还是一个while not done: think → act → observe → repeat循环。AutoGPTAutoGPT 的核心循环是一个经典的 ReAct 模式THOUGHT: 我需要先查天气再决定带不带伞 ACTION: get_weather(北京) OBSERVATION: 北京今天晴天25°C THOUGHT: 晴天不需要带伞任务完成 FINAL ANSWER: 今天北京是晴天不用带伞和我们 50 行代码的区别只在于 prompt 格式——AutoGPT 要求 LLM 输出 “THOUGHT/ACTION/OBSERVATION” 三段式而我们的实现用 JSON 格式。本质上都是推理 → 行动 → 观察 → 再推理。七、从Demo到生产3个升级方向50 行代码是一个完美的学习起点。如果要用于生产环境建议按以下路径迭代方向 1用 Function Calling 替代 JSON 解析DeepSeek API以及 OpenAI 兼容的所有模型原生支持 Function Callingresponseclient.chat.completions.create(modelMODEL,messagesmessages,tools[{type:function,function:{name:get_weather,description:查询指定城市的天气,parameters:{type:object,properties:{city:{type:string,description:城市名称}},required:[city]}}}],)优点模型级别保证 JSON 格式正确不再需要正则提取和重试逻辑。缺点被锁定在 OpenAI 兼容的 API 格式上。方向 2引入工具执行沙箱50 行代码里eval(expression)是个安全隐患——如果 LLM 返回了恶意代码eval会直接执行。importastimportoperatordefsafe_calculator(expression:str)-str:安全的数学计算不使用 evalallowed_ops{ast.Add:operator.add,ast.Sub:operator.sub,ast.Mul:operator.mul,ast.Div:operator.truediv,}treeast.parse(expression,modeeval)# 递归遍历 AST只执行允许的操作...对于文件系统操作、网络请求等敏感工具建议加权限校验层。方向 3加入人工确认点Human-in-the-Loop某些高风险操作如删除数据、发送邮件、执行支付应该加入人工确认DANGEROUS_TOOLS{delete_data,send_email,execute_payment}iftool_nameinDANGEROUS_TOOLS:confirminput(fAgent 请求执行高风险操作 {tool_name}参数:{tool_args}\n确认执行(y/n): )ifconfirm.lower()!y:messages.append({role:user,content:f用户拒绝了 {tool_name} 操作。请尝试替代方案。})continue这是生产环境 Agent 的最后一道安全防线。八、总结回到最开始的公式Agent LLM推理 × 工具执行 × 循环迭代这 50 行代码是你理解所有 Agent 框架的根知识。以后无论遇到什么新框架、新概念你都可以用这三个维度去拆解它怎么拼 promptLLM 推理层它怎么定义和调度工具工具执行层它怎么控制循环和上下文循环迭代层答案永远在这三个问题里。最后把完整的、带所有容错机制的代码贴在这里。你可以直接复制到一个.py文件里运行importjsonimportrefromopenaiimportOpenAI clientOpenAI(api_keyyour-deepseek-api-key,base_urlhttps://api.deepseek.com,)MODELdeepseek-v4-flashMAX_ITER10TOOLS{get_weather:{function:lambdacity:{北京:晴天25°C,上海:多云28°C,深圳:阵雨30°C}.get(city,未知城市),description:查询城市天气参数 city: 城市名称,},calculator:{function:lambdaexpr:str(eval(expr)),description:数学计算参数 expression: 表达式,},}SYSTEM_PROMPT你是智能助手可以调用工具完成任务。 需要工具时输出JSON{tool:工具名,args:{参数:值}} 任务完成时直接输出答案。可用工具 {tools_desc}defagent_loop(query:str)-str:messages[{role:system,content:SYSTEM_PROMPT.format(tools_desc\n.join(f-{n}:{i[description]}forn,iinTOOLS.items()))},{role:user,content:query},]last_calls[]for_inrange(MAX_ITER):respclient.chat.completions.create(modelMODEL,messagesmessages,temperature0.0)replyresp.choices[0].message.content.strip()# JSON 提取容错mre.search(r\{.*\},reply,re.DOTALL)ifm:try:tcjson.loads(m.group())name,argstc.get(tool,),tc.get(args,{})ifnameinTOOLS:# 死循环检测call_keyf{name}:{json.dumps(args,sort_keysTrue)}last_calls.append(call_key)iflen(last_calls)3andlen(set(last_calls[-3:]))1:return检测到重复工具调用任务已终止。请检查工具定义或尝试换个方式提问。resultTOOLS[name][function](**args)messages.append({role:assistant,content:reply})messages.append({role:user,content:f工具结果{result}\n请基于此结果给出最终答案不要再调用工具。})continueexcept(json.JSONDecodeError,TypeError):passreturnreplyiflen(reply)3else抱歉未能生成有效回答请重试。return达到最大循环次数任务终止。if__name____main__:print(测试1:,agent_loop(北京今天天气怎么样))print(测试2:,agent_loop(365 * 24 等于多少然后告诉我深圳天气))如果这篇文章对你有帮助欢迎点赞、收藏、关注。你的支持是我持续输出硬核内容的动力。本文使用的 DeepSeek API 当前定价2026年7月deepseek-v4-flash输入 ¥0.001/1K tokens输出 ¥0.002/1K tokensdeepseek-v4-pro输入 ¥0.003/1K tokens输出 ¥0.006/1K tokens新用户注册即送免费额度足够完成本文所有实验