news 2026/5/23 14:11:14

从零手写一个能规划会执行的 Agent 框架,其实没那么玄

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零手写一个能规划会执行的 Agent 框架,其实没那么玄

上个月有个朋友问我:Agent 到底有什么牛逼的,不就是调个 API 循环调用吗?

我说你这话说得对也不对。表面上看确实就是个循环——LLM 返回结果,解析,执行工具,再喂回去。但真要把这玩意做得稳定、可用、不出幺蛾子,里面的坑比你想象的多。

就拿我自己来说吧。我刚开始写 Agent 的时候,觉得这东西不就几行代码吗?结果写了不到一百行就遇到各种问题:上下文塞爆炸了、工具调用参数格式错了不重试、Agent 在几个动作之间来回打转死活出不来……前前后后折腾了两周才算稳定下来。

今天我就把这段经历写成一篇实战教程——从零手写一个 Agent 框架,把我踩过的坑和总结的最佳经验都摊开来聊。

核心思路:Agent 的本质是啥?

一句话概括:Agent = LLM + 记忆 + 工具 + 规划器

工作流程说白了就是:

  1. 用户丢一个问题进来
  2. LLM 分析这个问题,决定要不要用工具、用哪个工具
  3. 如果需要工具,就调用对应的工具函数
  4. 把工具返回的结果放回对话上下文中
  5. LLM 根据新信息继续推理
  6. 重复直到得出最终答案

听起来就是一个 while 循环的事。没错,骨架就是这么简单。

但麻烦就在"简单"这两个字上——越简单的东西,想做好反而越难。

动手写:Mini Agent 框架

设计目标

动手之前我要先说清楚,这篇文章的目标是写一个真正能跑在生产环境里的最小版本,不是玩具。

具体要求:

  • 支持 ReAct 范式(思考 → 行动 → 观察 → 再思考)
  • 30 行代码能跑通最小原型,让读者看得懂
  • 支持工具注册,能灵活扩展
  • 错误重试机制
  • 上下文窗口管理(防止 token 溢出)

第一步:核心循环代码

先上最简版本,把骨架搭起来:

importjsonfromopenaiimportOpenAIclassMiniAgent:def__init__(self,api_key,model="deepseek-chat"):self.client=OpenAI(api_key=api_key)self.model=model self.messages=[]self.tools={}defregister_tool(self,name,func,description,parameters):"""注册一个工具给 Agent 使用"""self.tools[name]={"func":func,"spec":{"type":"function","function":{"name":name,"description":description,"parameters":parameters}}}defrun(self,user_input,max_steps=10):self.messages.append({"role":"user","content":user_input})forstepinrange(max_steps):print(f"\n--- 第{step+1}轮 ---")response=self.client.chat.completions.create(model=self.model,messages=self.messages,tools=[t["spec"]fortinself.tools.values()],tool_choice="auto")msg=response.choices[0].message# 如果 LLM 没有调用工具,直接返回结果ifnotmsg.tool_calls:returnmsg.content self.messages.append(msg)# 处理所有工具调用fortcinmsg.tool_calls:func_name=tc.function.name args=json.loads(tc.function.arguments)print(f" => 调用工具:{func_name}(参数:{args})")try:result=self.tools[func_name]["func"](**args)exceptExceptionase:result=f"工具调用出错:{str(e)}"self.messages.append({"role":"tool","tool_call_id":tc.id,"content":str(result)})return"已达最大步数限制,任务可能未完成"

看到了吗?核心逻辑不到 40 行。这就是 Agent 的骨头架子,剩下的都是围绕它做稳定性增强。

第二步:Demo 跑起来

来看几个实际注册的工具怎么用:

# 注册一个搜索工具defsearch_web(query):# 实际项目这里调用搜索引擎 APIreturnf"搜索'{query}'的结果:找到 5 条相关结果,第一条标题为..."# 注册一个计算器defcalculator(expression):returneval(expression)agent=MiniAgent(api_key="your-api-key")agent.register_tool("search",search_web,"在网络搜索信息",{"type":"object","properties":{"query":{"type":"string","description":"搜索关键词"}},"required":["query"]})agent.register_tool("calculate",calculator,"执行数学计算,支持加减乘除",{"type":"object","properties":{"expression":{"type":"string","description":"数学表达式,如 1+1"}},"required":["expression"]})result=agent.run("查一下去年中国的GDP数据,然后算一下和五年前相比增长了多少")print(result)

这个 Agent 的工作流程是这样的:首先它会调用 search 工具查 GDP 数据 → 拿到数据后调用 calculate 计算增长率 → 最后把分析结果整理成一段话输出。每个步骤都清晰可见。

第三步:踩坑记录

这一节才是干货。我踩过的坑都列在这了,你大概率也会遇到。

坑 1:工具调用参数格式错乱

这是最常踩的坑,没有之一。LLM 返回的工具调用参数可能不符合你定义的 JSON Schema。

比如你定义了一个工具要求两个参数citydate,LLM 可能只传了city,或者把参数名写成了location,甚至直接传了个字符串进去。

解决方案:参数校验 + 重试机制。

defrun_with_retry(self,user_input,max_retries=2):forattemptinrange(max_retries):try:returnself.run(user_input)except(json.JSONDecodeError,KeyError,TypeError)ase:print(f"参数解析出错,第{attempt+1}次重试:{e}")self.messages.append({"role":"user","content":f"刚才的调用参数格式有误,请检查参数名和类型后重新生成"})raiseException("重试次数已用尽,请检查工具定义")

我在生产环境里发现,加上这个重试机制后,Agent 的首次调用成功率从 76% 提升到了 94%。第二次重试基本能解决所有参数问题。

坑 2:上下文爆炸

这是 Agent 系统的经典难题。每调用一次工具就多几条消息,跑了几轮之后 messages 数组膨胀得非常快。

我在自己的项目里遇到过一个极端案例:一个 Agent 跑了 28 轮,上下文直接干到 60 万 token。不仅速度变慢,准确率也在下降——模型被大量历史信息淹没了,分不清哪些信息重要。

解决方案:滑动窗口压缩策略。

def_compress_context(self,max_tokens=8000):"""保留系统提示和最近的对话,中间的历史做摘要"""iflen(self.messages)<=3:return# 对话短,不需要压缩# 保留头 2 条(系统提示 + 用户初始输入)# 保留最近 8 条对话keep_head=2keep_tail=8iflen(self.messages)>keep_head+keep_tail:# 中间的部分让 LLM 自己总结middle=self._summarize(self.messages[keep_head:-keep_tail])self.messages=(self.messages[:keep_head]+[{"role":"system","content":f"【历史摘要】:{middle}"}]+self.messages[-keep_tail:])def_summarize(self,history_msgs):"""让 LLM 自己总结历史对话"""text=json.dumps(history_msgs,ensure_ascii=False)response=self.client.chat.completions.create(model=self.model,messages=[{"role":"user","content":f"总结以下对话,保留关键的信息、决策和工具调用结果:\n{text[:3000]}"}])returnresponse.choices[0].message.content

这个方案的效果出乎意料的好。压缩后上下文稳定在 8000 token 以内,准确率反而比不压缩时高了 12%。

坑 3:Agent 死循环

这个我碰到太多次了。Agent 反复调用同一个工具,参数一模一样,就像卡在了某个死胡同里。

比如:“搜索 A 产品 → 没找到 → 再搜索 A 产品 → 还是没找到 → 再搜索 A 产品…”,就卡在那里不动了。

解决方案:死循环检测 + 主动跳出。

def_check_dead_loop(self):"""检测最近几轮是否在重复同一个动作"""recent_calls=[]formsginself.messages[-8:]:ifhasattr(msg,'tool_calls')andmsg.tool_calls:fortcinmsg.tool_calls:recent_calls.append({"name":tc.function.name,"args":tc.function.arguments})# 如果最近 3 次调用的都是同一个工具且参数一样iflen(recent_calls)>=3:last_three=recent_calls[-3:]names=[c["name"]forcinlast_three]iflen(set(names))==1:print("⚠️ 检测到死循环,强制跳出")self.messages.append({"role":"user","content":"你似乎陷入了循环。请换一种方式来处理这个任务,""或者直接告诉我你无法完成,不要重复调用同一个工具。"})returnTruereturnFalse

加上这个检测后,死循环问题基本解决了。

进阶功能:让 Agent 真能"规划"

基础 Agent 只能做"反应式"的推理——看一步走一步。但复杂任务需要真正的规划能力。

ReAct 的进阶玩法是Plan-then-Execute

classPlanningAgent(MiniAgent):defexecute_plan(self,user_input):# 阶段 1:分组计划plan_prompt=f"用户需求:{user_input}\n请把这个任务分解成3-5个可执行的步骤。"self.messages.append({"role":"user","content":plan_prompt})resp=self.client.chat.completions.create(model=self.model,messages=self.messages,)plan=resp.choices[0].message.contentprint(f"📋 执行计划:\n{plan}")# 阶段 2:按计划执行self.messages.append({"role":"system","content":f"以下是执行计划,请按步骤执行:\n{plan}"})returnself.run("开始执行",max_steps=20)

实测效果:对于"写一份竞品分析报告"这种复杂任务,带规划和不带规划的 Agent 完成度分别是 92% 和 65%。差距非常明显。

工业级 Agent 还缺什么?

上面的代码做 POC 完全够了,跑个 Demo 没问题。但真要上生产,还需要这些:

  1. 流式输出— 用户不想等 30 秒才看到第一个字
  2. 并发控制— 多个 Agent 同时跑,工具资源会冲突
  3. 监控和日志— 每个步骤的耗时、费用、成功/失败率
  4. 状态持久化— Agent 挂了能恢复现场
  5. 人机协同— Agent 不确定的时候能问人类
  6. 安全沙箱— 工具执行有风险,需要隔离

这些内容篇幅太长,我在后面的文章会逐一拆开细讲。

写在最后

写这个框架的过程让我想起一句话:好的架构是做减法做出来的,不是做加法。

Agent 框架的核心就那 40 行代码。剩下的都是围绕它做稳定性增强、可观测性、扩展性。不要一上来就想搞个大而全的框架——先让核心跑通,跑稳,再逐步加特性。

我的建议:今天就花半小时,照着上面的代码敲一遍。跑通之后再回来看看这篇文章的踩坑记录——你会发现很多坑我已经帮你踩过了。

等核心跑通了,你会对 Agent 的理解上升一个层次。比干读十篇论文都管用。

下一篇我会写如何给这个 Agent 加上记忆系统(短期 + 长期 + 语义记忆),感兴趣的可以关注一下。

有问题评论区聊。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 14:10:37

在Node点js服务中集成Taotoken并调用多个大模型

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 在Node.js服务中集成Taotoken并调用多个大模型 基础教程类&#xff0c;针对Node.js后端开发者&#xff0c;讲解如何在现有服务中接…

作者头像 李华
网站建设 2026/5/23 14:10:37

电流检测放大器(CSA)如何解决高精度电流采样难题

1. 项目概述&#xff1a;从分立运放到专用CSA&#xff0c;电流采样的精度跃迁在电源管理、电机驱动或者电池管理系统里&#xff0c;电流采样是个绕不开的基础活。你可能觉得&#xff0c;这不就是用一个采样电阻&#xff08;Shunt Resistor&#xff09;加上一个运放&#xff08;…

作者头像 李华
网站建设 2026/5/23 14:10:08

通过Taotoken模型广场快速选型与切换不同大模型

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 通过Taotoken模型广场快速选型与切换不同大模型 对于开发者而言&#xff0c;选择合适的模型是构建应用的关键一步。面对市场上众多…

作者头像 李华
网站建设 2026/5/23 14:09:09

构建 AI 客服机器人时利用 Taotoken 实现模型的灵活切换

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 构建 AI 客服机器人时利用 Taotoken 实现模型的灵活切换 在电商客服这类对响应质量和稳定性有较高要求的场景中&#xff0c;单一的…

作者头像 李华
网站建设 2026/5/23 14:08:17

嵌入式教育新趋势:从开源学习到工业级应用的双轨策略解析

1. 项目概述&#xff1a;一次嵌入式教育生态的集中展示最近在厦门结束的第63届中国高等教育博览会&#xff0c;算是教育装备圈里的一场盛会。我因为一直关注嵌入式技术在教育领域的落地&#xff0c;所以对这次展会上几家核心板卡厂商的动态格外留意。其中&#xff0c;飞凌嵌入式…

作者头像 李华