ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent-Reach实战:从零搭建AI Agent工具调用与ReAct循环框架

Agent-Reach实战:从零搭建AI Agent工具调用与ReAct循环框架 干过AI Agent项目的人应该都有同感模型能力再强只要它碰不到外部系统就永远只是个嘴强王者。Agent-Reach是我最近在做的一个最小可复用的AI Agent框架核心就一件事——把手脚真正接到大模型身上让Agent能够自主规划、调用外部工具、读取数据库、操作内部系统最终把一件完整的任务办完。这篇文章会把我从零搭建Agent-Reach的全过程掰开揉碎讲清楚包括核心架构、ReAct循环实现、工具注册设计、参数调优思路以及我踩过的几个坑。无论你是刚接触Agent开发还是已经在做企业内部Co-pilot方向这篇文章应该都能给你一些可以直接用的东西。Agent-Reach这个名字不难理解Reach就是触达半径。一个Agent到底能干多少事不取决于它有多聪明而取决于它能触达多少东西。我用这个项目回答了三个问题Agent怎么规划路径Agent怎么调用工具Agent怎么在犯错之后爬出来。1. 为什么需要Agent-Reach从会聊天到能办事很多人第一次接触Agent时会有一个错觉大模型能理解自然语言它肯定也能自己干活。实际一测就露馅了——模型确实是理解了你的需求但它除了吐一段文字建议之外什么也做不了。你想让它帮你查一下报表数据它告诉你请登录系统自行查看你想让它发一封邮件它把邮件正文写好但还是得你复制粘贴去发。问题就出在Agent没有触达能力。理解不等于行动推理不等于执行。大模型内部的参数里装着全世界的知识但它没有手、没有脚、没有接口权限对外部世界一无所知。Agent-Reach本质上就是在模型和外部世界之间补上这一层手和脚。1.1 大模型的天花板在于无法触达外部世界我把这个现象总结成一句话模型知道你所有的秘密但它一个也验证不了。它知道今天气温28度但它没办法打开手机上的天气App确认它知道你公司有个客户管理系统的数据迁移任务但它连数据库地址都不知道在哪。在生产环境中这意味着纯靠Prompt堆出来的Agent项目几乎都会卡在最后一公里——用户问了一个需要查系统才能回答的问题Agent就当场歇菜。这也是市面上大量Agent Demo看着很惊艳、一上线就翻车的根本原因。想打破这个天花板思路其实很朴素给Agent开一扇窗让它通过工具函数触达数据源和操作入口。模型负责想生成计划工具负责做执行动作模型再根据执行结果继续想形成闭环。这个闭环就是Agent-Reach的核心循环。1.2 Agent-Reach的核心思路把触达半径变成代理能力的度量衡我给Agent-Reach定了三条设计原则后面所有代码和参数都是围绕这三条原则展开的第一一切外部能力都是工具。查询天气是工具发邮件是工具查数据库是工具调用另一个AI服务也是工具。工具是Agent和外界唯一的握手协议统一了交互方式你就不需要为每种数据源写一套定制逻辑。第二行动必须可观测。Agent每调一次工具都会产生一个观察结果这个结果会回到模型手里成为下一次决策的依据。绝对不能容忍调了工具但不知道结果如何的情况。第三失败要有退路。Agent不是神它会选错工具、会传错参数、会连续两次做同一件事。这个框架必须有重试、降级和强制终止机制否则一个小错误就能让整个任务挂死。围绕这三条原则Agent的触达半径就变得可以量化了它能调多少工具、能处理多复杂的工具链、能在多少次错误内兜住底线这些指标直接决定了一个Agent在业务中能用多久、跑多远。1.3 这个项目适合谁参考如果你是以下三类人Agent-Reach的思路可以重点看看一是正在做企业内部助理、智能客服、知识库问答这类问答要变成操作的开发者。你们的业务场景天然需要Agent触达OA、CRM、ERP等内部系统Reach能力是刚需。二是想把大模型接入自动化流水线的后端工程师。你需要的不是又一个聊天界面而是一个能稳定调用API、执行任务、处理异常的代理服务Agent-Reach的工具注册机制能帮你很平滑地把现有接口变成Agent能力。三是我觉得最重要的——那些被网上Agent Demo搞得热血沸腾、又在实际落地时被各种边界情况折磨到怀疑人生的人。这篇文章讲的就是边界情况怎么处理。2. Agent-Reach的整体架构与设计取舍2.1 四大核心组件规划器、行动器、记忆、工具集Agent-Reach的架构并没有多花哨就是把Agent拆成四个职责清晰的模块规划器Planner、行动器Actor、记忆Memory和工具集Toolset。规划器是Agent的大脑皮层负责拆解任务。它接收用户的目标描述把一个大目标拆成一个个可执行的小步骤。比如用户说帮我整理上个季度的销售数据并生成摘要规划器会拆成连数据库→查销售表→聚合统计→生成摘要文本四步。在ReAct模式里规划器并不需要一次性给出完整计划它可以走一步看一步每一步都基于前一步的观察结果来修正路线。行动器是规划器的执行手。它把规划器输出的行动指令翻译成实际的函数调用。这个模块关键在参数映射——模型输出的是JSON或特定文本格式行动器要负责把里面的参数抽取出来、校验、再传给真实函数。我在实际开发中发现很多Agent效果差问题不出在模型不够聪明而是出在行动器对参数处理的鲁棒性太差。记忆是Agent的工作台。它既要装下用户对话的历史也要装下工具调用的中间结果。但上下文窗口是有限的记忆模块必须做取舍哪些信息留在当前窗口、哪些信息压缩成摘要、哪些信息直接丢弃。这是后面要详细讲的重点。工具集是Agent的外部世界接口。每一个工具都通过一份标准的Schema声明自己是什么、接受什么参数、返回什么结果。工具集还有一层重要职责——工具发现。Agent需要知道当前有哪些工具可用、每个工具是干什么的。工具太多时还需要动态选择这是后面会讲到的性能优化点。这个四个模块的分工对应到代码里就是四个Python类彼此之间通过消息对象沟通没有相互调用对方私有逻辑的紧耦合。后来我维护这个项目时最大的感受就是当初多花了一个小时把模块边界划清楚后面省下了无数个改一处崩三处的夜晚。2.2 为什么选择ReAct循环而非全链路编排在Agent架构选型上我其实纠结过一阵子。当时有两个方案摆在我面前方案A是全链路编排也叫Planner-Executor模式让模型一次性生成一个完整的分步计划然后用一个执行器按顺序执行每一步。这个方案的优点是控制感强、节奏稳定适合流程非常固定的场景缺点是一旦实际执行结果和模型预期的假设不一致几乎一定会不一致整个计划就得推翻重来灵活性很差。方案B是ReAct循环也就是Reasoning Acting交叉进行模型每走一步先思考Reasoning再行动Action然后观察结果Observation基于观察继续思考。这个方案的优点是灵活、容错率高行动中每一步都可以根据反馈修正缺点是循环次数多、参数控制不好容易死循环。我最终选了方案B。原因很简单Agent-Reach遇到的问题我无法提前穷举而ReAct模式天然允许Agent走错了再调整。这个选择可以用一个类比说明全链路编排就像你提前在手机上规划了一条从家到机场的路线然后闭着眼睛开车结果路上遇到修路就完全不知道怎么办了ReAct循环则像是你每到一个路口都看一眼导航、确认下路况再决定下一个动作虽然慢一点但基本不会迷路。ReAct循环也带来一个额外好处——可观测性。模型每一步的思考和行动都会落盘成结构化的日志在调试的时候我能清楚地看到它是怎么想的、为什么这么干、在哪里翻的车。相比之下全链路编排的中间推理过程很难追踪。2.3 工具注册把Agent与外界的交互收口工具注册是Agent-Reach里我认为最核心的一个设计。说白了就是把所有外部能力封装成统一格式的函数声明放进一个注册中心供Agent查询和调用。为什么非要注册而不是让开发者直接在代码里写一堆if-else我举一个实际踩过的坑。最早做Demo的时候我直接在行动器里写了一堆判断逻辑如果模型说要查天气就调用weather_api.get()如果模型说要发邮件就调用mail_sender.send()。在工具只有三五个、参数很简单的时候这个方式完全够用。等工具数量涨到十几个参数越来越复杂问题就来了模型经常输出一个我代码里没定义过的工具名我后来称之为工具幻觉或者参数名对不上行动器就开始抛异常。如果换成注册制问题就完全不同了。每个工具有一份标准化的Schema包含工具名称、描述、参数定义JSON Schema格式。Agent在决策前会拿到一张工具清单它只会从清单里选工具选错了也能立刻对照Schema校验出来。再往下注册制还能支撑工具发现和自动选择。工具多到几十个之后不可能每次都把全部工具的Schema塞给模型——那会把上下文撑爆。有了注册中心就可以在规划器前面加一个工具检索环节根据当前任务描述用向量相似度从注册中心里召回最相关的58个工具再让模型在这小范围内做选择。这个思路是从RAG系统里借过来的效果很好后面我会详细讲实现。3. 核心细节解析与实操要点3.1 工具Schema设计所有可靠性的根基如果把Agent-Reach比作一栋楼工具Schema就是地基。地基没打稳上面楼层再漂亮都是危房。一份合格的工具Schema长什么样子我以Agent-Reach里的查询销售报表工具为例说明{ name: query_sales_report, description: 查询指定时间范围内的销售汇总数据支持按区域、产品线维度筛选, parameters: { type: object, properties: { start_date: { type: string, format: date, description: 起始日期格式YYYY-MM-DD }, end_date: { type: string, format: date, description: 结束日期格式YYYY-MM-DD }, region: { type: string, enum: [华东, 华北, 华南, 西南], description: 区域维度缺省为全部 } }, required: [start_date, end_date] } }设计Schema时有三个细节极其重要都是我拿真实事故换来的经验第一description一定要写人话。很多开发者写工具描述时极度简略比如查询报表。但模型尤其是小参数模型根本没法从这么短的一句话里理解这个工具到底适不适用当前任务。你要像给新同事交代工作一样写描述这个工具是做什么的、在什么场景下调用、有什么边界条件。我实测下来把工具描述从一句话扩展到两到三句话后工具选择准确率能明显提升。第二能用枚举就尽量用枚举。像region这样的参数如果只写区域筛选而没有任何取值限制模型会自由发挥可能传进去一个东京或者2023 Q1这种完全奇怪的值。用enum把取值范围钉死参数校验直接变成选择题错误率大幅下降。第三required字段必须严格。可选参数和必填参数要分清楚。我见过很多Agent事故是模型猜了一个参数填进去结果把本来可选的筛选条件变成了硬性条件导致查询结果为空。把必填项收窄只留真正不可省略的字段能让模型的决策负担小很多。另外建议在工具函数本体里加一道防御性校验不要把校验全部交给模型。原因是模型的参数抽取即使有Schema约束也偶尔会传回类型不完全匹配的值比如字符串里带个空格、日期格式写成2023/1/1。在工具函数入口做一层clean_param()的处理把格式统一掉能省掉后面大量奇怪的Bug。3.2 ReAct循环的参数与上下文管理ReAct循环的核心伪代码很简单Anyone都能写出来while iteration max_iterations: thought, action model.plan(context) if action.type finish: return action.answer observation execute_tool(action) memory.append(observation) context memory.compile() iteration 1但写出来和能稳定跑之间隔着一片大海。我在调参过程中总结了几组关键参数逐个说明迭代上限max_iterations。我一开始天真地设成20觉得多跑几步总归能解决问题。结果就是Agent偶尔会陷入一个查一下→总结一下→再查一下→再总结一下的自我循环里无限烧钱。后来我根据任务复杂度动态设置简单任务查一条数据设4中等任务跨工具查询并汇总设8复杂任务多步骤数据处理设12。超过上限强制退出并返回当前已有信息。注意迭代上限不是越大越好。每多一轮就多一次模型调用成本而且多轮之后上下文越来越长模型出错概率反而升高。宁可让Agent办不完事情并承认办不完也别让它假装在努力地无限循环。temperature参数。ReAct循环里的模型是决策者不是创意写手所以我一直把temperature压得比较低设在0.2左右。高temperature会让模型产生更多发散但不可靠的工具选择比如明明有query_sales_report它偏要去调send_email。低temperature下模型更倾向于选择稳妥、直接的路径。observation截断。工具返回的结果可能是几万行数据不可能全部塞回上下文。我的策略是能汇总的先汇总比如COUNT(*)、SUM(sales)不能汇总的取前20行再配一个共X条记录已省略Y条的说明。这既给模型提供了足够信息又不至于把上下文挤压到模型记不住自己在干什么。课代表式总结一下ReAct循环的本质是来回传纸条。模型写好一张纸条行动计划递给工具执行工具把结果写回纸条递给模型看模型再写下一张。你要做的不是让纸条越传越长而是保证每张纸条上写的内容都是关键信息并且随时准备在第10张纸条时喊停。3.3 安全与权限控制的三级防线Agent有了工具调用能力之后第一件让人后背发凉的事就是它真的能执行操作了。一个能发邮件、能改数据库、能调接口的Agent如果权限边界没设置好就是一颗定时炸弹。Agent-Reach里我设置了三级防线逐级收口。第一级是白名单控制。所有工具必须显式注册没有注册的工具根本不在Agent的可见范围内。这是最基本的边界相当于给Agent划定了一个允许进入的房间列表。第二级是参数校验与操作确认。对于写类操作发送、删除、修改、提交我会要求Agent在正式执行前输出一个确认请求由调用方人或上层系统审核批准后再真正执行。这个在架构上叫 human-in-the-loop。我遇到过最惊险的一次是Agent为了完成用户发邮件的要求差点把同一封邮件群发给了通讯录里所有人。后来我在send_email工具里加了校验收件人数量超过20必须人工确认。第三级是审计日志。每一次工具调用的完整信息——谁发起的、哪个Agent、调用哪个工具、传了什么参数、返回了什么结果——全部落库。这个不直接防事故但在事故发生后能快速定位到问题环节非常值。安全这块我的建议是宁可让Agent因为权限不足而任务失败也不要让它在权限过宽的情况下侥幸成功。失败的Agent你可以通过加配置让它成功闯祸的Agent你需要花十倍的时间去善后。4. 实操过程从零实现一个最小可用的Agent-Reach这一节是全文最能直接抄作业的部分。我会从环境搭建开始带你完整体验一遍Agent-Reach最小版本从无到有的过程。技术栈我选择了 Python FastAPI OpenAI兼容接口这套组合目前生态最成熟、最容易跑通。4.1 环境准备与依赖选择先列环境。我本地的版本是Python 3.11openaiSDK 1.xFastAPI 0.115pydantic2.x。如果你是用国内可访问的大模型服务只要它提供OpenAI兼容的ChatCompletion接口同样适用因为Agent-Reach的模型接入层就是标准的接口调用。mkdir agent-reach cd agent-reach python -m venv .venv source .venv/bin/activate # Windows用 .venv\Scripts\activate pip install openai fastapi uvicorn pydantic python-dotenv初始化项目结构我习惯按模块分文件agent_reach/ ├── core/ │ ├── agent.py # ReAct主循环 │ ├── memory.py # 上下文记忆管理 │ ├── planner.py # 规划器模型调用封装 │ └── tool.py # 工具注册与执行 ├── tools/ │ ├── sales_report.py # 示例工具查销售报表 │ └── email_sender.py # 示例工具发邮件 ├── tests/ └── main.py # FastAPI入口配置环境变量新建.env文件MODEL_NAMEqwen-plus API_BASEhttps://your-api-endpoint API_KEYyour-key-here TEMPERATURE0.2 MAX_ITERATIONS8下面开始写核心代码。先看工具注册中心数据结构选字典名字为键工具的Schema和执行函数作为值。4.2 定义工具集我选两个最有代表性的工具来讲一个读类型查销售报表一个写类型发邮件。读写工具在安全策略上的处理很不一样正好都覆盖到。先实现tools/sales_report.pyfrom datetime import datetime def query_sales_report(start_date: str, end_date: str, region: str None) - str: 查询销售汇总数据模拟实现 # 真实环境中这里是数据库查询或外部API调用 # 为了演示返回一条模拟数据 result { start_date: start_date, end_date: end_date, region: region or 全部, total_sales: 1280000, order_count: 3420, generated_at: datetime.now().isoformat() } return str(result)然后实现tools/email_sender.py这个工具带人工确认逻辑PENDING_CONFIRMATIONS [] def send_email(recipients: list, subject: str, body: str) - str: 发送邮件。收件人超过20个时进入人工确认流程。 if len(recipients) 20: ticket_id fREVIEW-{len(PENDING_CONFIRMATIONS)1} PENDING_CONFIRMATIONS.append({ ticket_id: ticket_id, recipients: recipients, subject: subject, body: body }) return f收件人数量过多已进入人工审核工单号{ticket_id} # 真实发送逻辑在这里 return f邮件已发送至 {len(recipients)} 个收件人接下来在core/tool.py里实现注册中心class ToolRegistry: def __init__(self): self._tools {} def register(self, fn, name, description, parameters): self._tools[name] { schema: { name: name, description: description, parameters: parameters }, fn: fn } def list_tools(self) - list: return [t[schema] for t in self._tools.values()] def execute(self, name: str, arguments: dict) - str: if name not in self._tools: raise ValueError(f未知工具: {name}) return self._tools[name][fn](**arguments)4.3 实现ReAct主循环这是整个Agent-Reach最能体现技术含量的模块。我把主循环拆成core/agent.py设计得尽量简洁、可扩展import json from openai import OpenAI from .memory import ContextMemory from .tool import ToolRegistry class ReachAgent: def __init__(self, registry: ToolRegistry, model_name: str, temperature: float 0.2, max_iterations: int 8): self.registry registry self.model_name model_name self.temperature temperature self.max_iterations max_iterations self.memory ContextMemory(max_len20) self.client OpenAI() def run(self, task: str) - str: self.memory.clear() self.memory.append_user(task) activation_prompt self._build_system_prompt() iteration 0 while iteration self.max_iterations: messages [{role: system, content: activation_prompt}] messages.extend(self.memory.to_openai_messages()) response self.client.chat.completions.create( modelself.model_name, messagesmessages, temperatureself.temperature, max_tokens800 ) model_output response.choices[0].message.content self.memory.append_assistant(model_output) action self._parse_action(model_output) if action[type] finish: return action.get(answer, 任务完成) observation self.registry.execute(action[name], action[arguments]) # 观察结果摘要化防止上下文膨胀 observation self._summarize_observation(observation) self.memory.append_observation(observation) iteration 1 return f已达最大迭代次数({self.max_iterations})任务未完全完成。 def _build_system_prompt(self) - str: tools_info json.dumps(self.registry.list_tools(), ensure_asciiFalse) return ( 你是一个任务执行代理。你可以通过与用户对话的形式进行规划 但请在认为需要外部数据或操作时调用工具。\n f可用工具如下\n{tools_info}\n 请严格按照以下JSON格式输出你的决策\n {type: plan, tool: 工具名, arguments: {参数名: 参数值}}\n 当任务完成时输出{type: finish, answer: 最终回答}\n 注意只能调用上述工具列表中的工具不能编造不存在的工具。 ) def _parse_action(self, model_output: str) - dict: # 这里需要对模型输出做健壮解析支持从文本中提取JSON块 start model_output.find({) end model_output.rfind(}) 1 raw model_output[start:end] data json.loads(raw) if data.get(type) plan: return { type: action, name: data[tool], arguments: data.get(arguments, {}) } return data def _summarize_observation(self, observation: str, max_len: int 600) - str: if len(observation) max_len: return observation # 截断版本 return observation[:max_len] ...(结果过长已截断)记忆模块core/memory.py也很关键它确保对话历史不会无限膨胀class ContextMemory: def __init__(self, max_len: int 20): self._items [] self.max_len max_len def clear(self): self._items [] def append_user(self, content: str): self._items.append({role: user, content: content}) self._trim() def append_assistant(self, content: str): self._items.append({role: assistant, content: content}) self._trim() def append_observation(self, content: str): # 观察结果会以user消息形式返回给模型 self._items.append({role: user, content: f[工具结果] {content}}) self._trim() def to_openai_messages(self) - list: return [{role: item[role], content: item[content]} for item in self._items] def _trim(self): # 保留最近的N条最老的丢弃 if len(self._items) self.max_len: self._items self._items[-self.max_len:]要跑起来还需要一个入口main.py用FastAPI把Agent包成一个HTTP服务方便其他系统对接from fastapi import FastAPI from pydantic import BaseModel from core.agent import ReachAgent from core.tool import ToolRegistry from tools.sales_report import query_sales_report from tools.email_sender import send_email app FastAPI() registry ToolRegistry() registry.register(query_sales_report, query_sales_report, 查询销售汇总数据支持按时间范围、区域筛选, {type: object, properties: {start_date: {type: string}, end_date: {type: string}, region: {type: string, enum: [华东, 华北, 华南, 西南]}}, required: [start_date, end_date]}) registry.register(send_email, send_email, 发送邮件给指定收件人列表超过20人需人工审核, {type: object, properties: {recipients: {type: array, items: {type: string}}, subject: {type: string}, body: {type: string}}, required: [recipients, subject, body]}) agent ReachAgent(registryregistry, model_nameyour-model-name) class TaskRequest(BaseModel): task: str app.post(/agent/run) def run_task(req: TaskRequest): answer agent.run(req.task) return {answer: answer}4.4 效果验证与调参记录服务启动后用curl做了第一轮真实测试。任务是帮我把上个星期华东区的销售数据查出来然后再发一封邮件给sales团队标题是周报。第一次跑出来的结果让我哭笑不得。Agent非常正确地调用了query_sales_report返回了数据然后到了发邮件环节它直接编造了一个我根本没注册过的send_email_to_sales_team工具还自信地输出了邮件已发送。这个现象在Agent开发领域非常典型叫做工具幻觉。模型在训练数据里见过太多类似的工具名生成时就想当然地跳出了已知工具列表。我当时的处理方式是给系统提示词里加了一句硬性约束在上述工具列表之外不得虚构任何工具。如果认为需要新工具请输出finish并说明原因。同时把 agent 的解析逻辑加了一层工具名校验从模型输出里解析出工具名后先在注册中心里检查是否存在不存在直接抛给模型一个新观察工具不存在请从可用工具中选择。加了这层校验之后第二轮测试就顺利多了。Agent按查数据→取结果→发邮件的顺序依次执行且观察结果反馈正确最终返回了实际发送结果。调参过程中我记录了一张真实的参数效果对比表供参考参数初始值调优后现象说明temperature0.70.2高值时工具选择发散幻觉增多低值更稳定max_iterations208初始值过大导致死循环烧钱8轮足够覆盖大多数任务max_tokens1024800增量收益不大且过长的模型输出挤占上下文observation截断长度无截断600字符无截断时多轮后上下文迅速膨胀模型开始遗忘任务一个很重要的经验在调Agent效果之前先把日志加上。我在run()循环里加了一行print日志把每一轮的思考、行动、观察全部打出来调试效率提升了一个量级。看到Agent在想什么比猜它在想什么高效太多了。5. 常见问题与排查技巧实录很多东西不实际跑一遍是根本遇不到的。这一节我把Agent-Reach开发调试过程中踩到的坑整理成一份排查手册碰到类似问题可以直接照方抓药。5.1 工具幻觉模型调用了不存在的工具这是Agent开发里出现频率最高的错误没有之一。表现形式就是模型输出了一个注册中心里没有的工具名然后自信满满地假装执行成功了。排查步骤检查工具描述是否清晰。如果工具名相似、描述模糊模型非常容易张冠李戴。增加工具存在性校验。在registry.execute里加入未知工具的异常捕获并把这个异常作为新的观察信息返回给模型让它自我纠正。考虑缩减工具列表。工具越多选择越容易出错。如果一个任务只用到3个工具就不要把所有工具全部暴露给模型。我最终的方案是把未知工具异常返回给模型实测有80%的幻觉行为在第一轮纠正后就能恢复。5.2 死循环Agent在重复执行同一个动作有一类典型的死循环长这样Agent查询了天气发现是晴天然后又一次查询天气再次发现是晴天又查第三次……看起来像是模型忘记了之前已经查过。常见原因有两个。一是上下文被截断得太狠导致模型看不到前面几步的观察结果。二是模型在打转它其实不知道该干什么但又不愿意输出finish于是不断重复上一个成功的动作来维持工作状态。解决手段在记忆模块里加一个重复检测如果模型连续两轮选择了同一个工具且参数几乎相同就在观察里附加一条提醒你已经执行过相同操作请改变策略或结束任务。硬性设置迭代上限。这个前面提过是最后的兜底。在系统提示词里明确鼓励输出finish。如果当前任务已经完成或无法完成请直接输出finish不要重复执行无关操作。5.3 上下文爆炸与模型失忆多轮ReAct循环跑下来对话消息越来越多。当上下文接近模型窗口上限时会出现两个症状模型开始答非所问或者不断重复早期步骤。我用的策略是三层记忆第一层当前工作记忆只保留最近若干轮的关键消息也就是ContextMemory里的滑动窗口第二层过程摘要每经过几轮工具调用就调用模型把前面的过程压缩成一段摘要存在一个单独位置第三层持久化存储把完整日志写到数据库里不参与模型推理但可以回溯。这个方案有个关键点摘要本身也是用模型生成的会产生额外成本。我的做法是只在迭代数超过4轮时才启动摘要逻辑短任务直接用滑动窗口即可。5.4 参数传递的脏数据问题模型调用工具时传参经常不够干净比如日期带了时区后缀、枚举值多了空格、数值字段传成了字符串。大部分情况下模型自己检查不出来但传给你的工具函数轻则返回空数据重则直接抛异常。我的通用处理方案是在工具函数入口统一调用一个normalize_param()方法针对每个字段做类型强制转换、空白清理、枚举值模糊匹配。比如某个字段的合法值是华东模型传了华东地区东区模糊匹配可以把它归一到华东。此外工具函数本身尽量写成容忍型的不要因为一个字段的格式问题就抛出异常把整个Agent流程打断。返回一个参数异常请重新提供的友好提示比让Agent在异常里迷路要好得多。5.5 排查技巧速查表症状优先检查项常用修复手段调用了不存在的工具工具Schema描述、注册列表增加存在性校验并返回纠正信息功能正常但反复执行同一操作上下文滑动窗口、重复检测添加重复提醒、缩短窗口后期答非所问上下文长度启用过程摘要、截断观察结果传参总出错Schema的enum/description参数归一化、容忍型解析响应速度越来越慢工具数量、模型输出长度工具动态检索、降低max_tokens最后再分享一个小技巧开发期间一定要预留一个复现模式把真实的用户输入和Agent行为录成回放文件。这个在出问题的时候是救命的不然每次都要重新触发一次完整流程既费钱又费时间。我在Agent-Reach里把每一轮的输入输出都存成了JSONL日志排查问题时直接用它重建现场定位速度比靠猜快了一个量级。Agent-Reach这个项目做下来我最大的体会是AI Agent落地的难点从来不在把模型接进来而在接进来之后怎么让它别乱跑、别瞎调、别把上下文塞爆、别在细节上翻车。如果你也想做一个类似的Agent系统核心目标就一句话让模型的触达半径足够大大到能解决真实问题同时设立好围栏和兜底机制小到让它翻车也翻不出大乱子。这两者之间的平衡就是这个项目真正有趣的地方。
返回列表