如果你最近在做 AI 应用开发,应该绕不开“Agentic”这个词。翻译成人话,它就是一套让大模型从“你问一句、它答一句”的问答工具,变成能自己拆任务、调工具、查资料、改方案的工作流方法论。我最近把一个叫 Ynuo Agent(依诺)的 Agent 框架里沉淀的 Agentic 设计模式,一条条拆开仔细看了一遍,说实话收获比想象中大。这套模式不是教你写一个 Python 脚本去调大模型,而是把工程上真正管用的那套东西——显式循环、状态管理、工具抽象、记忆分层、多智能体协作——全部揉进了系统设计里。
这篇博文我会讲明白三个层面的东西:第一,Agentic 设计模式到底在解决什么问题,为什么传统提示词工程不够用;第二,Ynuo Agent(依诺)的架构是怎么搭起来的,关键取舍是什么;第三,我会把几种核心模式拆开讲,最后给出一套可以照抄的实操方案和排查技巧。适合已经在做大模型应用、想从“调接口”进阶到“搭系统”的人,也适合刚接触 Agent 开发、想少走弯路的初学者。
1. Agentic 设计模式到底在解决什么问题
1.1 从问答到工作流,一次范式转变
传统的大模型调用方式,本质上是“一次性问答”:你把 prompt 丢进去,模型吐一段文字出来,任务就结束了。这个过程是单向的,没有反馈回路,模型说错了也不知道自己错,更不会想办法去修正。早期我用提示词工程做各种 Demo 的时候,经常被这种模式卡住。比如想让模型去查一个数据库,它只能“编”一个查询结果给你,因为它根本没有调用外部工具的能力,也没有办法验证自己给的答案到底对不对。
Agentic 模式解决的就是这个根本问题。它把“一次问答”变成了“一套工作流”,模型不再是一个人闷头答题,而是像被委托了任务的办事员:先看清楚目标,再决定第一步做什么,做完观察结果,根据结果决定下一步,发现问题还能掉头重来。用生活里的话说,传统模式是你在路边问路,人家给你指个方向就不管了,你走错了也没人告诉你;Agentic 模式是你雇了一个办事员,他会自己看地图、换乘、问人、调整路线,直到把事情办完再找你汇报。
这个转变表面看只是多了一个“循环”,实际上是把系统的控制权从开发者手里交还给了运行时。开发者不再需要把所有分支用代码写死,只需要定义好工具、约束好边界,让模型在循环里自主决策。这就是 Ynuo Agent(依诺)所依赖的核心逻辑:系统设计者的职责,从“写死每一步”变成了“设计好每一步的规则和工具”。
1.2 Agentic 的核心能力不是“会对话”,而是“会闭环”
我在拆解 Ynuo Agent(依诺)的过程中,发现它的能力模型可以清晰拆成五层,这五层缺一不可。
第一层是感知。Agent 必须能拿到外部信息,不管是工具返回的结果、数据库里的数据,还是用户补充的上下文。没有感知能力的 Agent 就是闭着眼睛办事,只能靠模型内部那点知识硬撑。第二层是决策,也就是规划怎么做、选哪个工具、参数填什么。这是模型推理能力的直接体现,也是提示词质量最影响的环节。第三层是执行,真正去调用外部系统,跑代码、查接口、写文件。第四层是记忆,保存中间结果和历史信息,不让前面的劳动白费。第五层是反思,也就是发现自己走错路之后能主动修正,而不是一条道走到黑。
把这五层放到一起看,“会对话”只是基础能力,“会闭环”才是 Agentic 的灵魂。所谓闭环,就是感知—决策—执行—观察—再决策这样的循环能真正转起来。我测试过很多号称 Agent 的项目,发现它们经常只是把对话历史堆在一起再丢给模型,看起来像在循环,实际上没有任何新信息进入上下文,模型每轮都在重复同样的判断。这种假循环没有任何意义,真正有效的闭环必须保证每一轮都有新的观察结果反馈进来。
| 对比维度 | 传统问答模式 | Agentic 模式 |
|---|---|---|
| 触发方式 | 单次输入输出 | 循环推进,直到完成 |
| 状态管理 | 无状态或仅对话历史 | 显式状态对象,可保存可恢复 |
| 外部工具 | 不支持 | 统一注册、动态调用 |
| 反馈回路 | 无 | 有观察结果回流 |
| 容错能力 | 出错就重来 | 可反思、可修正、可绕行 |
1.3 什么样的任务才值得上 Agentic
不是所有场景都适合 Agentic,这一点我想先说清楚。我见过一些开发者把翻译任务、写一段营销文案这种一次性生成任务,也硬塞进 Agent 框架里,结果就是白白增加了调用次数和延迟,效果还未必比单次问答好。这类任务的本质是“单次推理”,没有外部依赖,也不需要动态调整,直接用传统模式最划算。
真正该上 Agentic 的任务有几个特征:多步骤、跨系统、需要动态决策。举个例子,我之前帮一位开发者调过一个自动化周报系统,它需要先查各个数据源、汇总指标、生成分析段落、再发给指定邮箱,这中间每一步的结果都可能影响后续写法,而且涉及至少两三个外部系统,这种任务用传统流水线做会很僵硬,用 Agentic 做就非常顺。再比如知识库问答加溯源,需要先检索文档、再组织回答、最后附上出处,每一步都要根据上一步的结果调整,也是典型的 Agentic 场景。
反过来说,如果你的任务只是“把一段文本翻译成英文”或者“把这份 PDF 里的表格提取出来”,单次调用加一点后处理就够了,没必要上 Agent。这个判断在项目开始前一定要做好,不然你会被自己的架构拖累。
2. Ynuo Agent(依诺)的架构拆解:先有框架,再谈模式
2.1 核心模块有哪些
Ynuo Agent(依诺)虽然名字叫 Agent,但拆开看它更像一个“运行时系统”,而不是一个单体的模型封装。整个系统由四个核心模块组成,各管一摊。
第一个是 Agent Core,也就是主循环模块。它负责控制整个闭环的节奏:每一轮什么时候调用模型、什么时候执行工具、什么时候跳出循环。这个模块在代码上通常表现为一个 while 循环加一个状态机,是 Agent 的心脏。
第二个是工具注册表,英文习惯叫 Tool Registry。所有可被 Agent 调用的外部能力,都先注册到这里,统一暴露给模型。这个设计我在实操里觉得非常重要,它让“模型和工具之间”形成了一种松耦合关系:模型不需要知道工具内部怎么实现,只需要知道工具叫什么名字、参数是什么格式、能干什么事。
第三个是记忆系统,负责存取短期上下文和长期知识。第四个是编排器 Orchestrator,负责生成计划、委派任务、收集结果。在多 Agent 场景下,编排器的角色更重,它相当于一个“包工头”,自己不一定干活,但要清楚谁适合干什么活、怎么把结果拼回来。
这四个模块之间有清晰的数据流:Agent Core 发起决策,编排器生成计划,工具注册表负责执行动作,记忆系统在全程提供信息支持和结果沉淀。模块职责不重叠,是我看这套架构首先感受到的明确特征。
2.2 三个关键设计取舍
拆架构的时候,我尤其留意了三个设计取舍,这三个取舍决定了整个 Agent 系统是好用还是难用。
第一个取舍是显式循环而不是静态流水线。不少人做 Agent 时喜欢把所有流程写成一个链式调用:先检索,再生成,再校验,固定顺序。这种做法看起来简单,但一旦某个环节的结果不符合预期,整个流程就断了。Ynuo Agent(依诺)走的是另一条路:循环是动态的,模型在每一轮可以根据当前状态选择调用什么工具,甚至改变执行顺序。付出的代价是系统更难预测,但换来的好处是应对复杂任务的灵活性,这一点我认为是值得的。
第二个取舍是状态外置而不是藏在提示词里。很多早期方案喜欢把所有信息都塞进 prompt,让模型自己从长文本里找状态。这非常不可靠,因为模型会“忘记”前面提到过的重要内容,而且 token 消耗会急剧膨胀。好的做法是把任务目标、已完成步骤、当前步骤、相关数据放到一个显式的状态对象里,模型每一次决策都基于这个对象,而不是靠“阅读理解”。
第三个取舍是工具接口统一。Ynuo Agent(依诺)里每个工具都要转换成统一的 JSON Schema 描述,模型看到的不是十个不同风格的 API 文档,而是十份格式一致的“菜单”。这让模型更大概率生成格式正确的调用请求,也让新增工具变得更简单:逻辑还是你自己的,但对外接口完全标准化。
这三个取舍的背后是同一条原则:如果一个东西能被结构化、被显式管理,就不要依赖模型自己去“悟”。把状态、工具、循环都显式化,系统才能变得可观测、可控制、可调试。
3. 七种核心 Agentic 设计模式详解
3.1 ReAct 循环:感知-行动-观察的闭环
ReAct 是 Reasoning + Acting 的合称,它是我认为最基础也最重要的 Agentic 设计模式。Ynuo Agent(依诺)的单任务执行本质上就是一个 ReAct 循环。核心思路很简单:模型在每一步输出三样东西——Thought(思考)、Action(要执行的动作)、Observation(观察到的结果)。系统拿到 Action 后调用对应工具,把工具返回结果拼回上下文,模型再基于新的观察继续推理。
伪代码写出来大概是这样的逻辑:
state = initial_state while not state.done and state.step < max_steps: response = model.generate(state.messages) thought, action = parse_response(response) if action == "finish": break observation = execute_tool(action) state.messages.append(observation)我在实际跑的时候发现,ReAct 模式最容易出问题的不是推理,而是解析。模型返回的 Action 格式如果不统一,解析层根本没法稳定工作。所以我会在提示词里强约束输出格式,甚至用结构化输出模式,保证模型返回的一定是合法的 JSON,至少是可预见的文本模板。
ReAct 的适用场景是步骤不太多、每一步都能从环境中获得明确反馈的任务。比如让 Agent 依次调用三个 API 汇总数据,每调完一个都能看到真实返回,再决定下一步怎么走。但如果你一次要处理二十个子任务,ReAct 这种“走一步看一步”的方式就会显得没有全局观,容易顾此失彼。这时候就需要第二种模式。
3.2 计划-执行模式:先想清楚再动手
计划-执行模式,通常叫 Planner-Executor,是解决复杂任务的利器。它把 Agent 分成两个角色:一个 Planner 负责在开工前把任务拆成步骤清单,一个 Executor 负责逐条执行清单中的步骤。我在 Ynuo Agent(依诺)的多步任务配置里,看到最多的就是这种结构。
为什么要先规划再执行?因为 ReAct 虽然灵活,但每一轮都只看到当前这一步,没有全局视角。就好比你出门旅行,如果只知道下一站去哪,永远不知道全程要花几天。有了 Planner 之后,系统会先产出一个计划数组,比如“第一步检索资料;第二步分析数据;第三步生成报告;第四步发送邮件”,执行器按顺序跑,跑完一步反馈一步。
这个模式真正的关键在于动态修正。计划不是写死的一次性产物,每一步执行完,执行器把结果反馈给 Planner,Planner 判断原有计划是否还成立,必要时调整后续步骤。举个例子,如果第一步检索资料发现数据源不可用,Planner 就应该把“第二步分析数据”改为“先从备选数据源获取数据,再分析”。这种动态修正让系统既有全局眼光,又保留了应变能力。
实操中有一个坑要注意:计划越长,上下文占用越大。如果计划有二十步,光计划文本就可能吃掉几千个 token。我习惯在计划执行过程中做“已完成步骤压缩”,把已经完成的历史步骤从上下文中删掉或浓缩成一行小结,只保留当前步骤和剩余计划,这样能大幅降低上下文压力。
3.3 多 Agent 协作:把任务分给专业选手
多 Agent 协作模式,典型实现是 Orchestrator-Workers,也就是“一个主管加一群工人”。主管 Agent 负责理解任务、拆分任务、分配任务,再把工人返回的结果汇总成最终答案。工人 Agent 各自只负责一个子领域,拥有专门的提示词和工具集。
我在 Ynuo Agent(依诺)里真正意识到这个模式的威力,是处理一个包含“资料检索、数据分析、报告撰写”三部分的任务时。如果让一个 Agent 从头干到尾,它的注意力会被拉扯,检索阶段下的功夫不一定能体现在报告质量里。拆成三个工人 Agent 后,每个工人的上下文里只有自己负责的那部分内容,不需要关心别的环节,输出质量明显更干净。
多 Agent 模式需要特别关注消息协议。工人和工人之间不能直接通信,所有信息都要经过主管。这不是为了限制自由度,而是为了避免出现“两个 AI 互相抬杠”的局面。我在实验里见过一个失控场景:任务里有观点冲突时,两个 Direct 通信的 Agent 根本停不下来,来回反驳了十几轮都没结果。改成主管中转后,主管可以判断什么信息需要传递、什么信息需要过滤,整个系统的可控性立刻提高。
我还想强调一点:多 Agent 不是越多越好。有些开发者恨不得十个岗位十个 Agent,结果系统复杂到根本调试不动。我的经验是,很多任务单 Agent 加一套好工具就能解决,多 Agent 真正的价值是“上下文隔离”和“角色专业化”,不是“人海战术”。如果你每个 Agent 之间没有明确的信息边界,它们反而会互相污染。
3.4 反思模式:让 Agent 学会“批判自己”
反思模式,英文叫 Reflection,是提升 Agent 输出质量的一个低成本高收益模式。它的核心思想是:Agent 生成一个答案后不直接交差,而是让另一个角色对答案进行批判,再把批判意见带回给生成者,让它修改一遍。
实现思路非常简洁:
first_answer = generate(messages) critique = generate(system_prompt="你是挑剔的评审", messages=[first_answer]) revised = generate(system_prompt="根据评审意见修正", messages=[first_answer, critique])这里有两种做法:一种是让同一个 Agent 自己批评自己的答案,另一种是用两个独立角色一个生成、一个评审。我实测下来,两个角色的效果比自问自答稳定得多,因为同一个模型在自问自答时很难跳出自己的思维惯性,但换一个角色设定和提示词后,往往能挑出不少问题。
反思模式在代码生成、数据分析这类任务里效果尤其明显。有一次我让 Agent 写一段数据处理脚本,第一次输出看起来能跑,但用了反思模式后,评审角色指出边界条件没处理,修正后的代码在测试集上直接提升了十几个百分点的准确率。当然,代价是调用次数和延迟翻倍,所以它不适合所有任务。我的建议是:只有在结果正确性很重要、而且你有时间等待的任务上启用反思模式。
3.5 记忆分层:别让上下文变成杂物间
记忆系统是 Ynuo Agent(依诺)里容易被低估的一部分,它的核心方法论很简单:把记忆分层管理,而不是一股脑塞进上下文。我见过太多 Agent 项目,跑着跑着上下文越来越长,模型越来越“笨”,最后连简单的指令都执行不好。原因不是模型不行,而是它的“工作台”被杂物堆满了。
记忆至少要分三层。第一层是工作记忆,存当前这一步的中间结果,用完就丢,比如这一次调用的工具返回值,存到本地变量里,处理完就清掉。第二层是短期记忆,存当前任务的主要上下文,这部分留在模型窗口里,但需要控制总量。第三层是长期记忆,跨任务的信息统一存到外部向量库或数据库,比如用户偏好、历史结论、常见问题标准答案等,通过检索按需加载。
分层的好处是让模型每一轮看到的都是“精简但完整”的信息,既能理解当前任务,又不会被无关的旧历史淹没。我实践中的写入策略很简单:先判断信息是不是只对当前步骤有用,如果只影响当前步骤,就只放工作记忆;如果对后续步骤有影响,就放短期记忆;如果未来别的任务也要用,就写入长期记忆。检索策略则是“能不查就不查,查就查最相关的”,给检索结果设置一个上限,比如前五条,而不是把整个数据库的命中结果全塞进去。
3.6 人机协同模式:保留人工审批节点
人机协同模式,也就是 Human-in-the-loop,在 Agentic 设计里非常容易被忽略,但真正放到生产环境时它又至关重要。它的核心并不复杂:在敏感操作之前插入一个暂停点,Agent 必须等人工确认后才继续执行。
我在 Ynuo Agent(依诺)的实际配置里,至少会遇到三类必须上人工节点的操作:发邮件(尤其是发给外部客户时)、删除数据、执行涉及资金的调用。Agent 本身不是不能做这些事,但出错成本太高,不值得为了省几秒钟的人工确认而冒风险。
实现方式一般是:Agent 跑到一个需要审批的 Action 时,把当前状态挂起,然后对外发出一个审批请求,比如发到指定的审批接口或者钉钉群。收到“approve”信号后,Agent 从挂起点恢复执行;收到“reject”,则终止动作或者走备选方案。这里的关键是 Agent 状态必须能被持久化和恢复,不能审批完从零开始重新跑一遍,那样太浪费。
我个人的体会是:人工节点不是系统的负担,而是系统的保险。加了人工节点的 Agent 流程,在演示给业务方看的时候也更有说服力,因为对方知道关键动作还有人在把关。自动化不代表无人化,合理的“人在回路”反而能让自动化系统走得更远。
3.7 事件驱动与流式编排:面向生产的增强
最后一个模式是针对生产环境的通用增强:事件驱动与流式编排。前面几种模式基本是同步执行的,也就是 Agent 一路跑到底再返回结果。但真实业务里,任务可能很长,可能中途失败,可能依赖外部系统的异步回调,这时候同步模式就不够用了。
事件驱动的思路是把 Agent 流程拆成一个个事件状态:任务创建、工具调用完成、人工审批通过、超时触发重试等。每一个状态都记录下来,系统可以随时查看某个 Agent 任务卡在哪一步。某个工具调用超时了,不用整个 Agent 重启,而是从超时点单独重试一次,甚至换一个备选工具继续跑。Ynuo Agent(依诺)在生产级配置里,会为每个任务维护一个状态机,配合事件总线实现这种流式编排。
我建议所有准备上生产的 Agent 项目都尽早引入事件驱动。尽管它前期开发成本更高,但后期运维体验完全不一样。同步模式的 Agent 一旦中途出错,你很难判断它到底进度如何;事件驱动模式下,任务状态、进度、耗时全部清晰可见,出了问题还可以从故障点恢复,这才是能被业务长期依赖的形态。
| 设计模式 | 典型应用场景 | 实现复杂度 | 容错性 |
|---|---|---|---|
| ReAct 循环 | 中等步骤的任务、边做边观察 | 低 | 中 |
| 计划-执行 | 复杂多步任务,需要全局规划 | 中 | 中 |
| 多 Agent 协作 | 需要角色分工或上下文隔离的任务 | 高 | 中 |
| 反思模式 | 结果正确性要求高的任务 | 中 | 高 |
| 记忆分层 | 所有长流程任务 | 中 | 高 |
| 人机协同 | 涉及敏感操作的流程 | 低 | 高 |
| 事件驱动 | 生产环境长任务、异步任务 | 高 | 高 |
4. 实操:从零搭一个 Ynuo 风格 Agent
4.1 工具注册与统一接口
实操环节先从工具注册开始。工具注册的核心目标是让模型能“看到”有哪些工具、它们长什么样,而不是让开发者把每个工具的调用逻辑都写死在 prompt 里。我按最常见的实践,用一个装饰器把所有工具统一收进注册表,每注册一个工具就带上它的描述和参数格式。
这里给一段可以直接参考的代码:
import json from functools import wraps registry = {} def tool(name, description, parameters_schema): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) registry[name] = { "func": wrapper, "description": description, "parameters_schema": parameters_schema, } return wrapper return decorator @tool( "search_docs", "搜索本地文档库里与关键词相关的段落", { "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"], }, ) def search_docs(query: str): # 这里实际去向量库或全文检索里查 return "找到了相关内容:Agentic 设计模式核心是闭环。" def list_tools_for_model(): tool_list = [] for name, meta in registry.items(): tool_list.append({ "name": name, "description": meta["description"], "parameters_schema": meta["parameters_schema"], }) return json.dumps(tool_list)这样做的好处是,打印list_tools_for_model()就能拿到一份统一的工具菜单,拼到系统提示词里,模型就能像查菜单点菜一样选择工具。新增一个工具时,开发者只需写一个函数加一个装饰器,不用改任何其他代码。我实际做下来,这个设计让工具扩展效率至少翻了一倍。
这里有一个我踩过的坑:工具描述的质量直接决定模型选工具的正确率。同样是“搜索文档”,如果你写“搜索本地文档库里与关键词相关的段落”,模型就知道这个工具是用来做检索的;如果你只写“search_docs”,模型可能把它理解成搜索网页。描述越具体、参数说明越清楚,模型选错工具的概率就越低。
4.2 状态管理与上下文窗口控制
Agent 的状态管理是很多人忽视的环节。我见过不少新手把“状态”等同于“对话历史”,也就是把所有消息一股脑存在数组里。这种做法的问题是:模型看一遍历史需要处理大量 token,而且容易把早期的错误信息当成最新事实。正确做法是设计一个结构化的状态对象,把任务目标、已完成步骤、当前步骤、关键数据分开存。
一个可参考的设计是:
from dataclasses import dataclass, field from typing import List @dataclass class AgentState: goal: str completed_steps: List[str] = field(default_factory=list) current_step: str = "" pending_plan: List[str] = field(default_factory=list) observations: List[str] = field(default_factory=list)然后每一轮决策时,把状态对象的关键字段拼成一段简短的“状态摘要”传给模型,而不是把原始历史全部传进去。这样做的好处是模型每次看到的都是“当前最相关的信息”,以及“还剩哪些事没做”。如果 Agent 跑了很久,我还会把 completed_steps 压缩成一行摘要,例如“已完成:资料检索、数据提取”,而不是把每个步骤的详细结果堆进去。
上下文窗口控制也离不开 token 估算。我的粗略经验是:一个中文字符大约占 1 到 2 个 token,在设置软上限时,尽量只用到最大窗口的 70% 左右,留出余量给模型推理和工具调用。一旦接近上限,优先丢弃“最旧的工具输出”,因为这些输出往往已经被模型消化过了;如果还不够,就把早期关键信息生成一段摘要,替换掉原始内容。
4.3 循环控制与异常兜底
写 Agent 主循环时,有几个参数和分支是必须处理的。首先是最大迭代数,这个不加就是事故现场。模型在复杂任务里容易陷入重复调用同一个工具的循环,没有最大迭代数的话,它会一直空转直到 token 烧光。我一般根据任务复杂度设置在 5 到 15 次之间,宁可设少一点让 Agent 主动求救,也不要让它无限跑下去。
下面是一个我在实验里经常复用的小型 ReAct 循环骨架:
def run_agent(goal, max_iterations=8): state = AgentState(goal=goal) messages = [{"role": "system", "content": build_system_prompt()}] for step in range(max_iterations): state.current_step = f"step_{step}" response = model.generate(messages, tools=list_tools_for_model()) action = parse_action(response) if action["type"] == "finish": return action["answer"] try: observation = execute_tool(action) except Exception as ex: observation = f"工具执行失败:{ex}" state.completed_steps.append(action.get("tool", "")) state.observations.append(observation) messages.append({"role": "user", "content": f"观察结果:{observation}"}) return "已达最大迭代次数,任务未完成"注意我在工具执行外面套了一个try-except。工具调用一定会出错,网络超时、参数类型不对、权限不足,各种情况都有可能发生。如果不捕获异常,Agent 整个流程就直接崩了;捕获之后把异常信息当作观察结果返回给模型,模型反而能根据错误信息调整参数重新尝试。这一步是 Agent 能“活下来”的关键。
此外还要设置标准的停止条件:当模型返回 finish 标志或者达到最大迭代数时,循环终止。finish 标志需要解析层来识别,我建议在提示词里明确要求:只有当任务真正完成时才输出 finish,并且必须附带最终答案的文本。否则模型可能很早就想“交差”,让你拿不到想要的深度结果。
4.4 日志与调试
调试 Agent 和调试普通程序完全是两种体验。普通程序出错有堆栈、有报错行号,Agent 出错往往是你看着模型在那儿“一本正经地胡说八道”,不知道它哪一步开始跑偏的。所以日志设计必须从一开始就做好。
我的日志方案是:每一步都输出结构化信息,包括当前步骤编号、模型本轮产生的主要思考、解析出的动作、执行工具的名称和参数、工具返回的观察信息、这一步消耗的时间。举个例子,我用 Python 的日志库输出一段这样的记录:
[step 3] thought: 用户需要最新销售数据,应该优先查询销售接口 [step 3] action: call get_sales_data(start="2024-01-01", end="2024-12-31") [step 3] observation: 返回了 12 个月的汇总数据,总计 2300 万 [step 3] duration: 2.8s有了这样的轨迹,排查问题就变得很直接。比如发现 Agent 在第 3 步和第 5 步调用了完全相同的工具、传了完全相同的参数,说明它在绕圈子;发现某一步耗时特别长,说明工具本身或模型响应有问题,而不是在系统逻辑层。
开发环境里,我习惯把完整轨迹直接打印到控制台;生产环境则用异步日志加采样,因为 Agent 每跑一个任务都会产生大量日志,全量写下来会非常吃磁盘。采样率一般设置 10% 到 30%,既能保证可观测性,又不至于日志爆炸。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
我在几个模拟项目里折腾 Agent 的过程中,积累了一批高频问题,统一整理成速查表,方便你在踩坑时对照。
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| Agent 进入死循环,反复调用同一工具 | 最大迭代数没设;观察结果没有拼回上下文 | 检查循环终止条件;确认每轮有没有新信息进入 |
| 工具调用一直报参数错误 | 模型生成的参数类型和 JSON Schema 不一致 | 在工具执行前加一层参数校验;用结构化输出约束格式 |
| 上下文很快被撑爆 | 每次工具返回全文都塞进 history,没有清理 | 引入压缩策略,丢弃旧工具输出或生成摘要 |
| 模型“一本正经”地编结果 | 工具返回为空时仍被当作有效结果;缺少反馈 | 工具返回时带状态标志;空结果也要明确告诉模型 |
| 多 Agent 角色混乱 | 共享上下文导致 A 的思考被 B 看到 | 隔离每个 Agent 的上下文,通信统一走主管 |
| 输出解析不稳定 | 模型返回格式飘忽不定 | 改用结构化输出或强制 JSON 模式,附一个解析失败的兜底分支 |
这里尤其要说明“上下文被撑爆”这个问题。我见过一位开发者做的资料分析 Agent,任务完成时上下文达到了 8 万 token,不是因为他检索了太多资料,而是因为他每次检索都把前几名结果全文塞进历史,而且从不清理。模型在最后阶段根本记不住最开始的用户意图,因为中间已经被几千行文档淹没了。
5.2 几个独家避坑心得
第一个心得是让 Agent“慢下来”。听起来反直觉,但限制自由度往往比放任自由效果更好。我给每个动作加上边界条件,比如工具参数必须经过校验、非必要不调用高风险工具、每一步最多输出 500 字思考。这些限制让 Agent 更专注,也减少了模型“自由发挥”的空间。模型和人类一样,约束越少反而越容易跑偏。
第二个心得是坚持“先单 Agent,再聊多 Agent”。我一直建议从最小闭环开始:一个模型、两个工具、一个循环。把这个最小系统跑顺,再逐步加计划、反思、多 Agent 协作。我见过一上来就搭了五个 Agent 的项目,最后出了问题根本定位不到是哪一环的提示词写错了。单 Agent 能解决的问题,就永远不要用多 Agent 去复杂化。
第三个心得是把提示词工程的功力用在系统设计上。很多人在系统里堆提示词技巧:角色扮演、少样本示例、思维链,试图让模型“自觉”做好每一步。真正可靠的 Agent 系统,依赖的是结构化的约束:状态显式化、工具接口统一、参数强制校验。提示词当然重要,但它只是系统的一部分。一个好的工具接口设计,比在提示词里写一万遍“你要仔细检查参数”都管用。
第四个心得是从第一天就建立成本和日志观测。Agent 的 token 消耗比单次问答高一个量级,不观测根本不知道钱花哪了。我每个项目都会在第一步就接入一个日志系统,记录每次调用的 token 数、耗时、工具执行结果。后面优化起来,这些数据就是最直接的依据。
5.3 线上故障处理的一个实战复盘
这里分享一个我在模拟项目 X 里遇到的真实问题。当时一个客服问答 Agent 突然开始大量返回同一个错误答案,而且是在完全不同的客户提问下都返回同一句话。我先看日志,发现所有请求都走了同一个路径:检索工具没有返回任何结果,但 Agent 没有把这当作失败,而是直接基于“没有结果”这个空输入生成了一段“找不到相关信息”的固定回复。
问题出在工具的设计上:检索返回为空时,我的工具层返回了一个空字符串,而空字符串被后续逻辑当成“正常结果”处理了。修复很简单,让工具在无结果时返回一个明确的结构化标志,比如{"status": "empty", "message": "未找到相关内容"},并要求模型看到status=empty时不得直接生成答案,而是必须走备选路径,比如反问用户或换一个关键词重试。
这类问题在 Agent 系统里非常典型:不是模型不够聪明,而是工具层把“异常”伪装成了“正常”,导致模型接收到错误信号。排查方法也很简单,把日志里观察结果和最终生成结果对应起来看,几秒钟就能发现是哪一个环节传了错误信号。所以我一直强调,工具返回结果的设计要结构化、要带状态,这比追求模型本身的逻辑能力更重要。
回到最开始的话题,我觉得 Agentic 设计模式不是玄学,也不是某个框架的私有概念,它本质上就是一套工程约束。Ynuo Agent(依诺)这套框架给我的最大启发,不是某个具体的代码实现,而是它把模式体系化了,让我在设计 Agent 系统时能有章可循:该用循环的地方用循环,该加计划的地方加计划,该留人工审批的地方留审批,每一步都清清楚楚。这里再分享一个小技巧:给每个工具调用加上耗时统计,哪一步特别慢就直接优化它,这比换一个“更强的模型”有效得多。如果你今天就想动手,建议先用一个模型、两个工具、一个循环把最小闭环跑通,再加计划、反思这些模式,一步步来,你也会找到属于自己的那套节奏。