从零实现一个能规划+执行的Agent框架,我踩了哪些坑
2026/5/8 16:06:01 网站建设 项目流程

上个月接了个需求:做一个能自动完成多步任务的 Agent——给一个目标,它能自己规划步骤、调用工具、处理中间结果,直到任务完成。

听起来不就是调个大模型 API 就行?我一开始也是这么想的。

结果真正上手才发现:让 LLM 输出"思考过程"容易,让它在真实环境中稳定地完成多步任务,中间的坑多到数不过来。

先搭一个最小框架

不扯概念,直接上代码。一个 Agent 框架最核心的就两个部分:思考引擎行动引擎

思考引擎:怎么决定下一步该干啥

我用的是 ReAct 范式——让模型"边想边做",每一步输出思考过程再决定下一步行动。

结构长这样:

用户输入:帮我把这个文件夹分类整理 → Thought: 用户想整理文件夹,我需要先看文件夹里有什么文件 → Action: read_folder(path="/home/user/downloads/") → Observation: [{name:"report.pdf",type:"file"}, {name:"photos",type:"folder"}, ...] → Thought: 有 PDF 文件和图片文件夹,需要按照类型分类 → Action: create_folder(path="/home/user/downloads/sorted/") → ... → Final Answer: 整理完成,已在 downloads 下创建 sorted 目录

核心代码其实不复杂:

classReActAgent:def__init__(self,llm,tools):self.llm=llm# 大模型self.tools=tools# 可用工具列表self.memory=[]# 历史记录defrun(self,task:str)->str:self.memory=[{"role":"user","content":task}]max_steps=10forstepinrange(max_steps):# 让模型思考下一步response=self.llm.chat(self.memory)ifself._is_final_answer(response):returnresponse["answer"]# 解析行动指令action=self._parse_action(response)result=self._execute_action(action)# 把观察结果加回记忆self.memory.append({"role":"assistant","content":response["text"]})self.memory.append({"role":"system","content":f"观察结果:{result}"})return"达到最大步数,任务结束"def_parse_action(self,response):# 从模型输出中提取 Action 指令# 格式: Action: tool_name(param1=value1)importrematch=re.search(r"Action:\s*(\w+)\((.+)\)",response["text"])ifnotmatch:raiseValueError("无法解析行动指令")return{"name":match.group(1),"params":self._parse_params(match.group(2))}def_execute_action(self,action):ifaction["name"]notinself.tools:returnf"错误:工具{action['name']}不存在"returnself.tools[action["name"]](**action["params"])

十几行就能跑通最基础的 Agent。我一开始觉得这就能交付了。

天真了。

踩坑记录

坑一:模型总是输出无效格式

最大的问题是:模型有时候不按套路出牌。说好了 “Action: tool()” 的格式,结果它输出 “Action: 我需要调用 read_folder 工具”——多了中文描述,导致 _parse_action 直接崩了。

解决方案:加 structured output。现在主流方案有两种:

  1. JSON mode:强制模型输出 JSON,解析更稳定
  2. Function Calling:原生支持,但有些模型不支持

我选的是 JSON mode + 自定义工具描述:

tools_desc=[{"name":"read_folder","description":"读取文件夹内容","params":{"path":"string","recursive":{"type":"boolean","default":False}}},# ...]system_prompt=f""" 你是一个可以调用工具的AI助手。你的每一步输出必须是以下格式之一: 思考过程(可选): {{"thought": "..."}} 行动指令(必选): {{"action": "tool_name", "params": {{"param1": "value1"}}}} 可用工具:{json.dumps(tools_desc,ensure_ascii=False)}"""

切换 JSON 模式后,解析失败率从 30% 降到了 2% 以下。

坑二:规划做得好,执行跟不上

模型的第一步规划往往很漂亮——“我分三步完成”,但执行到第二步经常发现第一步的结果和预期不一样,后面的计划全废。

举个真实例子:让 Agent 分析一个 GitHub 仓库的代码质量。它第一步是"clone 仓库",但仓库有 500MB,clone 了 2 分钟还没完。第二步"分析代码结构"等不到第一步结果,直接报错。

解决方案:不做全局规划,只做 step-by-step 的贪心决策。每次只看当前状态,决定下一步。这其实就是 ReAct 模式优于 Plan-then-Execute 的地方——ReAct 不需要预判所有步骤。

我也加了一个观察时间上限:

def_execute_action(self,action):try:# 异步执行 + 超时控制result=execute_with_timeout(self.tools[action["name"]],action["params"],timeout=30# 30秒超时)returntruncate_result(result,max_chars=2000)# 结果太长也截断exceptTimeoutError:returnf"工具执行超时(30秒),建议简化操作或重试"exceptExceptionase:returnf"工具执行出错:{str(e)}"

坑三:上下文窗口爆炸

这是最大的工程坑。Agent 每走一步,就把 {thought, action, result} 全部塞进 memory。跑 10 步之后,对话历史动不动就上万 tokens。模型推理越来越慢,记不住前面的内容,开始重复执行已经完成的步骤。

解决方案

  1. 摘要压缩:每 5 步做一次上下文总结,替代原始对话历史
  2. 关键信息提取:只保留当前任务相关的观察结果
  3. 设置记忆窗口:最多保留最近 10 步的完整记录,更早的只保留摘要
classCompressedMemory:def__init__(self,llm,max_steps=10):self.llm=llm self.max_steps=max_steps self.steps=[]self.summary=""defadd_step(self,step):self.steps.append(step)iflen(self.steps)>self.max_steps:# 把最旧的几步压缩成摘要old_steps=self.steps[:-self.max_steps]self.summary=self._summarize(old_steps,self.summary)self.steps=self.steps[-self.max_steps:]defget_context(self):# 返回摘要 + 最近步骤returnself.summary+"\n".join(str(s)forsinself.steps)

坑四:错误恢复能力差

Agent 执行工具出错了,大部分模型的处理方式是"再试一次同样的操作"。如果三次重试都失败,它的默认行为就是崩掉。

我加了一个简单的错误处理机制:

error_handlers={"read_folder":{"retry_with_suffix":lambdaerr:"/"instr(err),# 路径问题就加 /"fallback":lambda:"文件夹不存在或没有权限"},"write_file":{"retry":True,"max_retries":2}}

然后提示模型:如果某个操作重试两次还失败,换一种方式实现目标。

优化后的框架架构

经过几轮迭代,最终框架长这样:

┌─────────────┐ │ 用户输入 │ └──────┬──────┘ ▼ ┌─────────────────────┐ │ Planner (ReAct) │ ← 思考、决策 │ - 当前状态分析 │ │ - 选择下一步行动 │ └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ Action Executor │ ← 执行 │ - 参数校验 │ │ - 超时控制 │ │ - 错误处理 │ │ - 结果截断 │ └──────────┬──────────┘ ▼ ┌─────────────────────┐ │ Memory Manager │ ← 记忆管理 │ - 上下文压缩 │ │ - 关键信息提取 │ │ - 窗口控制 │ └─────────────────────┘

跑了个完整的例子

最后让 Agent 做了一个真实任务:分析我本地的项目结构,找出超过 1000 行的文件,给出重构建议。

跑了 8 步,调用了 read_folder、read_file、count_lines、analyze_complexity 四个工具。整个过程花了大概 2 分钟,输出了一份 500 字的重构报告。虽然有些建议比较泛(“建议把函数拆小”),但整体是可用的——比完全没有结构分析强多了。

下一步:Multi-Agent

目前的单 Agent 架构最大问题是:规划、执行、记忆管理都挤在一个模型里,context 越大越混乱。

下一步打算拆成多 Agent——规划 Agent 只管规划,执行 Agent 只管干活,各自维护独立的上下文。这样每个 Agent 的 context 都很干净,不容易搞混。

已经在看 Google 的 A2A 协议了,等实践完了再写一篇分享。


有被 Agent 开发折磨过的同学,欢迎评论区交流踩坑经历。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询