1. 从七个零件到七次抉择:AI Agent 工程实现的底层逻辑
很多人第一次接触 AI Agent 这个概念,脑子里浮现的画面大概是科幻电影里那种能自己思考、自己行动的数字生命。但真到了动手搭建的阶段,你会发现它其实更像一台精密的自动化流水线——每个环节都有明确的输入输出,每个决策点都有可量化的判断依据。我前后参与过几个 Agent 项目的落地,从最初用 LangChain 拼凑原型,到后来自己写编排逻辑,踩过的坑足够填满一个中型会议室。这篇文章想做的事情很简单:把 Agent 拆开,看看它到底由哪些核心部件组成,以及在工程实现中,你需要做出哪些关键决策。
先说结论层面的认知:一个能跑起来的 AI Agent,本质上是在LLM 的推理能力之上,叠加了记忆管理、工具调用、任务规划、循环控制这几层工程结构。LLM 是大脑,但光有大脑没有手脚和记忆,它只能做单轮问答。Agent 要解决的核心问题是:让模型能够根据目标自主决定下一步做什么,执行之后根据结果调整策略,直到任务完成或触发终止条件。
这个定义听起来简单,但落到代码层面,每一个环节都有大量的工程细节需要处理。比如工具调用的参数校验怎么做?循环什么时候该停?记忆存什么、怎么存、什么时候取?这些问题没有标准答案,但有一些经过验证的实践模式可以参考。
这篇文章适合两类人:一类是已经用过 ChatGPT 但还没动手搭过 Agent 的开发者,另一类是在 Agent 开发中遇到了瓶颈、想系统梳理架构思路的工程师。我会尽量用大白话把每个环节讲清楚,同时给出可以直接参考的代码结构和参数配置。文章不会涉及任何特定平台的部署细节,重点放在通用的工程实现思路上。
2. 七要素拆解:一个 Agent 到底由什么构成
2.1 核心要素一:LLM 推理引擎的选择与约束
LLM 是 Agent 的推理核心,这个没什么争议。但选哪个模型、怎么用,这里面有讲究。我见过不少项目一上来就追求最强模型,结果 token 成本爆炸,响应速度还慢。实际工程中,模型选型要考虑三个维度:推理能力、响应延迟、调用成本。
推理能力决定了 Agent 能不能正确处理复杂任务。比如需要多步推理的数学问题、需要理解代码逻辑的编程任务,对模型能力要求就高。但如果你的 Agent 只是做简单的信息提取和格式化输出,一个小参数量的模型可能就够了。我一般会建议先用中等能力的模型跑通流程,然后在关键节点上替换更强模型做兜底。
响应延迟直接影响用户体验。Agent 的循环机制意味着一次任务可能需要多次 LLM 调用,如果每次调用都要等好几秒,整个任务下来用户可能已经失去耐心了。实测下来,单次调用延迟控制在 2 秒以内是比较理想的,超过 5 秒就需要考虑流式输出或者异步处理了。
调用成本是很多团队容易忽略的点。Agent 的循环特性意味着 token 消耗是普通对话的数倍甚至数十倍。一个简单的任务可能触发 5 到 10 次 LLM 调用,每次调用都包含系统提示词、历史对话、工具定义等内容。如果不做优化,成本会迅速失控。
实操心得:在系统提示词中明确约束模型的输出格式,比如要求它用 JSON 格式返回决策结果。这样可以减少解析失败的概率,也能让后续的循环控制逻辑更稳定。我习惯在提示词里加一句“只输出 JSON,不要包含任何其他文字”,实测能显著降低格式错误率。
2.2 核心要素二:记忆系统的分层设计
Agent 的记忆不是简单的对话历史堆砌。一个设计良好的记忆系统至少应该包含三层:短期记忆、长期记忆、工作记忆。
短期记忆就是当前任务的对话上下文,通常直接放在 prompt 里传给模型。这部分的特点是容量有限,受限于模型的上下文窗口。我一般会把最近 5 到 10 轮交互放在短期记忆里,更早的内容就压缩或者丢弃。
长期记忆是跨任务持久化的信息,比如用户的偏好、历史任务的结论、领域知识等。这部分通常存在外部数据库或向量存储中,需要的时候通过检索召回。长期记忆的关键在于写入策略和召回策略——什么时候存、存什么、什么时候取、取多少,这些都需要根据业务场景来设计。
工作记忆是 Agent 在执行当前任务时的临时状态,比如已经完成了哪些子任务、当前正在处理哪个步骤、中间结果是什么。这部分我习惯用一个结构化的 JSON 对象来维护,每次循环开始时注入到 prompt 中,循环结束后更新。
三层记忆的配合方式决定了 Agent 的“聪明程度”。短期记忆保证对话连贯,长期记忆提供背景知识,工作记忆跟踪任务进度。三者缺一不可,但也不是越多越好。记忆太多会挤占上下文窗口,反而影响模型推理质量。
2.3 核心要素三:工具调用的注册与路由
工具调用是 Agent 从“能说”到“能做”的关键跨越。没有工具,Agent 只能输出文本;有了工具,它才能查数据库、调 API、操作文件、发送请求。
工具调用的工程实现包含几个环节:工具注册、工具描述生成、调用参数解析、执行与结果返回。每个环节都有坑。
工具注册就是把你希望 Agent 能用的函数整理成一个列表,每个函数包含名称、描述、参数 schema。描述写得好不好,直接决定了模型能不能正确选择工具。我见过太多项目因为工具描述太模糊,导致模型该调 A 工具的时候调了 B 工具。
参数解析是另一个高频出错点。模型生成的参数格式可能不符合预期,比如该传数字的时候传了字符串,该传数组的时候传了单个值。所以参数校验和类型转换是必须的。我通常会在工具执行前加一层校验逻辑,对参数做类型检查和默认值填充。
工具路由是指当有多个工具可选时,如何让模型做出正确选择。一个实用的技巧是在工具描述中明确写出适用场景和不适用场景。比如“查询天气”工具的描述可以写成“当用户询问某地当前或未来的天气状况时使用此工具,不要用于查询历史天气数据”。
2.4 核心要素四:任务规划与分解策略
任务规划是 Agent 智能程度的重要体现。面对一个复杂目标,Agent 需要把它拆解成可执行的子任务,然后按顺序或并行执行。
规划策略大致分为两类:静态规划和动态规划。静态规划是在任务开始前就生成完整的执行计划,然后按计划逐步执行。这种方式适合流程固定的场景,比如“先查数据、再分析、最后生成报告”。动态规划是每执行一步就根据当前结果决定下一步做什么,更适合不确定性高的任务。
实际工程中,我倾向于混合使用:先用静态规划生成一个粗略的步骤列表,然后在每一步执行时允许动态调整。这样既有全局视野,又有局部灵活性。
规划的质量很大程度上取决于提示词的设计。我通常会在系统提示词中要求模型“先思考再行动”,也就是在输出工具调用之前,先用一段文字说明当前的推理过程和下一步计划。这个技巧来自 ReAct 模式的启发,实测能显著提升多步任务的完成率。
2.5 核心要素五:循环机制与终止条件
循环机制是 Agent 区别于单轮问答的核心特征。Agent 需要不断重复“观察-思考-行动”的循环,直到任务完成或触发终止条件。
循环控制的关键在于终止条件的设计。常见的终止条件包括:模型输出中明确表示任务完成、达到最大循环次数、连续多次没有有效进展、触发预设的异常处理逻辑。
最大循环次数是一个硬性保护措施。我一般会设置 10 到 15 次作为上限,超过就强制终止并返回当前结果。这个数字不是拍脑袋定的,而是根据任务复杂度和成本预算综合权衡的结果。太少了任务做不完,太多了成本失控。
连续无进展检测是另一个实用技巧。如果 Agent 连续两三次循环都在做类似的事情,或者工具调用返回的结果没有带来新信息,就应该考虑终止或切换策略。我通常会在循环控制逻辑中维护一个“进展分数”,每次循环后更新,低于阈值就触发终止。
2.6 核心要素六:错误处理与容错机制
Agent 在执行过程中会遇到各种错误:工具调用失败、参数格式错误、模型输出不符合预期、外部服务超时等等。一个健壮的 Agent 必须有完善的错误处理机制。
错误处理的核心思路是分类处理和优雅降级。工具调用失败时,先判断是暂时性错误还是永久性错误。暂时性错误可以重试,永久性错误则需要调整策略或终止任务。模型输出格式错误时,可以尝试重新提示或者用规则解析兜底。
我习惯在 Agent 的每个关键节点都加上 try-catch 逻辑,并且把错误信息结构化记录下来。这样不仅方便排查问题,也能在后续循环中把错误信息作为上下文传给模型,让它知道之前哪里出了问题。
容错机制还包括超时控制和资源限制。每个工具调用都应该设置超时时间,避免因为某个外部服务卡住导致整个 Agent 挂起。资源限制主要是 token 消耗和 API 调用次数的控制,防止意外情况下的成本失控。
2.7 核心要素七:输出解析与结果组装
Agent 的最终输出需要从模型的自然语言响应中提取结构化信息,然后组装成用户可用的结果。这个环节看似简单,实际上很容易出问题。
输出解析的难点在于模型输出的不确定性。即使你在提示词中明确要求了格式,模型也可能因为各种原因输出不符合预期的内容。所以解析逻辑必须足够健壮,能够处理各种边界情况。
我通常采用多级解析策略:先尝试用 JSON 解析器解析,失败则用正则表达式提取关键信息,再失败则回退到人工规则或返回原始文本。每一级解析都有对应的日志记录,方便后续优化提示词。
结果组装则是把 Agent 执行过程中的中间结果、工具调用记录、最终结论整合成一个完整的输出。这部分的设计要考虑用户的使用场景——是直接展示给用户看,还是作为下游系统的输入。不同的场景对格式和内容的要求不同。
3. 七个决策点:工程实现中的关键抉择
3.1 决策点一:单 Agent 还是多 Agent 协作
这是架构层面最先要回答的问题。单 Agent 结构简单、调试方便、成本可控,适合任务边界清晰、流程相对固定的场景。多 Agent 协作则适合复杂任务,可以把不同职责分配给不同的 Agent,比如一个负责规划、一个负责执行、一个负责审核。
但多 Agent 不是没有代价的。Agent 之间的通信成本、状态同步、错误传播都是需要解决的问题。我见过一些项目为了追求架构先进性,硬是把简单任务拆成多个 Agent,结果复杂度上去了,效果反而没提升。
我的建议是:先用单 Agent 跑通核心流程,遇到明确的瓶颈再考虑拆分。拆分的依据应该是职责边界是否清晰、单个 Agent 的提示词是否过于臃肿、不同子任务是否需要不同的模型或工具集。
3.2 决策点二:提示词工程 vs 微调
提示词工程和微调是提升 Agent 能力的两种路径。提示词工程灵活、成本低、迭代快,适合大多数场景。微调则适合有大量标注数据、对特定领域表现要求极高的场景。
实际项目中,我几乎总是从提示词工程开始。通过精心设计的系统提示词、少样本示例、思维链引导,大部分任务都能达到可用的效果。只有当提示词优化到极限仍然无法满足要求时,才会考虑微调。
微调的成本不只是训练费用,还包括数据准备、模型部署、版本管理的开销。而且微调后的模型可能在其他任务上表现下降,需要仔细评估。所以这个决策点要慎重。
3.3 决策点三:同步执行还是异步执行
Agent 的执行模式直接影响用户体验和系统吞吐量。同步执行是用户发起请求后等待 Agent 完成所有循环再返回结果,实现简单但用户体验差。异步执行是用户发起请求后立即返回一个任务 ID,Agent 在后台执行,用户通过轮询或推送获取结果。
对于执行时间较长的任务,异步模式几乎是必须的。但异步也带来了新的问题:任务状态管理、结果存储、超时处理、并发控制。这些都需要额外的工程投入。
我的经验是:如果 Agent 的预期执行时间超过 10 秒,就应该考虑异步模式。如果任务可以拆分成多个独立的子任务,还可以考虑并行执行来缩短总时间。
3.4 决策点四:工具调用的粒度设计
工具调用的粒度是一个容易被忽视但影响很大的决策。粒度太粗,一个工具做太多事情,模型难以正确使用;粒度太细,工具数量过多,模型选择困难,而且调用次数增加导致成本上升。
我通常遵循的原则是:一个工具只做一件事,但这件事要有明确的业务含义。比如“查询用户信息”是一个合适的粒度,“查询用户基本信息”和“查询用户订单信息”拆成两个工具可能就太细了。
另一个考虑是工具的复用性。如果某个功能在多个任务中都会用到,就应该封装成独立工具。如果只是某个特定流程的中间步骤,可以考虑内联到提示词或代码逻辑中。
3.5 决策点五:记忆存储的技术选型
记忆存储的选型取决于数据量、查询模式和性能要求。小规模场景可以用内存或本地文件,中等规模可以用关系型数据库,大规模或需要语义检索的场景则需要向量数据库。
向量数据库的选择要考虑维度、索引类型、查询性能、运维成本。我一般会先用简单的方案验证需求,比如用 SQLite 存结构化记忆、用 FAISS 做本地向量检索。等数据量和查询复杂度上来了,再考虑迁移到更专业的方案。
记忆的写入策略也很关键。是每次交互都写入,还是定期批量写入?是存原始文本,还是存摘要或嵌入向量?这些决策会影响存储成本和检索效果。
3.6 决策点六:循环控制的策略选择
循环控制策略决定了 Agent 什么时候继续、什么时候停止、什么时候切换策略。常见的策略包括:固定最大轮数、基于模型输出的完成信号、基于进展检测的动态终止。
我通常会把多种策略组合使用:设置一个硬性最大轮数作为兜底,同时检测模型是否输出了完成信号,再辅以进展检测来提前终止无效循环。这样既能保证任务有机会完成,又能避免资源浪费。
循环控制还需要考虑异常情况的处理。比如工具连续失败、模型输出格式持续错误、外部服务不可用等,这些情况应该有专门的终止逻辑和错误上报机制。
3.7 决策点七:可观测性与调试支持
Agent 的调试比普通程序复杂得多,因为它的行为具有不确定性。同一个输入可能因为模型采样的随机性产生不同的执行路径。所以可观测性建设是必须的。
我通常会在 Agent 的每个关键节点记录日志:输入是什么、模型输出了什么、选择了哪个工具、参数是什么、执行结果是什么、耗时多少、token 消耗多少。这些日志不仅用于调试,也用于后续的优化和成本分析。
除了日志,我还会维护一个执行轨迹的可视化工具。把 Agent 的每一步决策和工具调用按时间线展示出来,可以直观地看到它在哪个环节出了问题。这个工具在排查复杂任务时特别有用。
4. 实操过程:从零搭建一个可用的 Agent
4.1 环境准备与基础依赖
搭建 Agent 的第一步是准备开发环境。我通常用 Python 作为主要语言,因为它有丰富的 LLM 生态和工具库。核心依赖包括:LLM 调用库、HTTP 请求库、数据校验库、日志库。
# 基础依赖示例 # pip install openai pydantic requests structlog import json import logging from typing import Any, Callable from pydantic import BaseModel, ValidationError # 配置结构化日志 import structlog logger = structlog.get_logger()如果你选择用现成的 Agent 框架,比如 LangChain 或类似的编排工具,可以省去一些基础工作。但我的建议是:先用原生方式实现一遍核心逻辑,理解每个环节在做什么,然后再决定是否引入框架。框架能提升开发效率,但也可能带来黑盒问题和调试困难。
4.2 定义工具注册与参数校验
工具注册是 Agent 能力的基础。每个工具需要定义名称、描述、参数 schema 和执行函数。我用 Pydantic 来做参数校验,因为它能自动生成 JSON Schema,也方便做类型转换。
class ToolParameter(BaseModel): name: str type: str description: str required: bool = True class ToolDefinition(BaseModel): name: str description: str parameters: list[ToolParameter] handler: Callable # 示例工具:查询天气 def get_weather(city: str, unit: str = "celsius") -> dict: # 实际实现会调用外部 API return {"city": city, "temperature": 25, "unit": unit} weather_tool = ToolDefinition( name="get_weather", description="查询指定城市的当前天气。当用户询问天气状况时使用此工具。", parameters=[ ToolParameter(name="city", type="string", description="城市名称"), ToolParameter(name="unit", type="string", description="温度单位,celsius 或 fahrenheit", required=False) ], handler=get_weather )工具描述的质量直接影响模型的选择准确率。我通常会在描述中明确写出使用场景和不适用场景,并且给出参数的具体格式要求。比如日期格式统一用 YYYY-MM-DD,避免模型生成各种奇怪的格式。
4.3 构建提示词模板与系统消息
系统提示词是 Agent 的“行为准则”。一个好的系统提示词应该包含:角色定义、能力边界、输出格式要求、工具使用规范、错误处理指引。
SYSTEM_PROMPT = """你是一个任务执行助手。你的职责是根据用户目标,选择合适的工具并执行,直到任务完成。 ## 可用工具 {tool_descriptions} ## 输出格式 每次响应必须是一个 JSON 对象,包含以下字段: - thought: 你的推理过程,说明当前状态和下一步计划 - action: 要执行的动作,可以是工具名称或 "finish" - action_input: 工具参数,如果 action 是 "finish" 则为最终答案 ## 规则 1. 先思考再行动,thought 字段必须填写 2. 每次只执行一个动作 3. 工具调用失败时,分析原因并尝试其他方式 4. 任务完成时,action 设为 "finish",action_input 为最终结果 5. 如果连续三次无法推进任务,返回错误信息并终止 ## 当前任务 {task} ## 执行历史 {history} """提示词的长度需要控制。太短了模型理解不充分,太长了挤占上下文窗口还增加成本。我一般会把系统提示词控制在 500 到 1000 token 之间,根据任务复杂度调整。
4.4 实现核心循环逻辑
循环逻辑是 Agent 的引擎。它负责调用模型、解析输出、执行工具、更新状态、判断终止。
class AgentLoop: def __init__(self, llm_client, tools: list[ToolDefinition], max_iterations: int = 10): self.llm = llm_client self.tools = {t.name: t for t in tools} self.max_iterations = max_iterations self.history = [] def run(self, task: str) -> dict: for i in range(self.max_iterations): # 构建提示词 prompt = self._build_prompt(task) # 调用模型 response = self.llm.chat(prompt) # 解析输出 try: parsed = self._parse_response(response) except ParseError as e: logger.error("parse_failed", error=str(e), raw=response) self.history.append({"error": "输出格式错误,请重新生成"}) continue # 记录思考过程 self.history.append({"thought": parsed["thought"]}) # 判断是否完成 if parsed["action"] == "finish": return {"status": "success", "result": parsed["action_input"]} # 执行工具 tool_result = self._execute_tool(parsed["action"], parsed["action_input"]) self.history.append({"action": parsed["action"], "result": tool_result}) return {"status": "max_iterations_reached", "history": self.history} def _execute_tool(self, tool_name: str, params: dict) -> Any: if tool_name not in self.tools: return {"error": f"未知工具: {tool_name}"} tool = self.tools[tool_name] try: # 参数校验 validated = self._validate_params(tool, params) # 执行 result = tool.handler(**validated) return {"success": True, "data": result} except ValidationError as e: return {"error": f"参数校验失败: {e}"} except Exception as e: return {"error": f"工具执行失败: {str(e)}"}这个循环结构看起来简单,但每个环节都有优化空间。比如提示词构建时如何压缩历史、工具执行时如何做超时控制、解析失败时如何重试,这些都需要根据实际情况调整。
4.5 记忆管理与上下文压缩
随着循环次数增加,历史记录会越来越长,最终超出模型的上下文窗口。所以需要做上下文压缩。
我的做法是:保留最近 N 轮完整记录,更早的记录做摘要。摘要可以用模型生成,也可以用规则提取关键信息。摘要的内容包括:完成了哪些步骤、得到了什么关键结果、遇到了什么问题。
def compress_history(history: list, keep_recent: int = 5) -> str: if len(history) <= keep_recent: return format_history(history) old = history[:-keep_recent] recent = history[-keep_recent:] # 对旧记录做摘要 summary = summarize(old) # 可以用 LLM 或规则实现 return f"## 历史摘要\n{summary}\n\n## 最近执行\n{format_history(recent)}"上下文压缩的时机也很重要。我一般会在历史记录超过一定长度时触发压缩,而不是每轮都压缩。压缩太频繁会丢失细节,压缩太少又起不到作用。
4.6 错误处理与重试机制
错误处理要覆盖几个层面:LLM 调用失败、输出解析失败、工具执行失败、参数校验失败。
对于 LLM 调用失败,我通常做指数退避重试,最多重试 3 次。如果还是失败,就返回错误信息给用户。
对于输出解析失败,我会把解析错误信息追加到下一轮的提示词中,让模型知道上次输出格式有问题,引导它修正。
对于工具执行失败,我会把错误信息作为工具结果返回给模型,让它决定是重试、换工具还是终止。
def call_llm_with_retry(client, prompt, max_retries=3): for attempt in range(max_retries): try: return client.chat(prompt) except RateLimitError: time.sleep(2 ** attempt) except Exception as e: if attempt == max_retries - 1: raise logger.warning("llm_call_failed", attempt=attempt, error=str(e)) raise LLMCallError("达到最大重试次数")4.7 结果输出与日志记录
Agent 执行完成后,需要把结果整理成用户可用的格式。我通常会把最终答案、执行轨迹、工具调用记录、耗时和 token 消耗都记录下来。
def format_result(agent_result: dict) -> dict: return { "answer": agent_result.get("result"), "status": agent_result.get("status"), "steps": len(agent_result.get("history", [])), "trace": agent_result.get("history", []), "metadata": { "total_tokens": agent_result.get("total_tokens", 0), "duration_ms": agent_result.get("duration_ms", 0) } }日志记录要结构化,方便后续查询和分析。我用 structlog 输出 JSON 格式的日志,每条日志包含时间戳、事件类型、关键参数。这样可以直接导入日志分析系统做可视化。
5. 常见问题与排查技巧实录
5.1 模型不调用工具或调用错误工具
这是最常见的问题之一。表现是模型在应该调用工具的时候直接输出文本答案,或者选择了不相关的工具。
排查思路:先检查工具描述是否清晰。工具描述要包含使用场景、参数格式、返回值说明。如果描述太模糊,模型就不知道什么时候该用。其次检查系统提示词是否明确要求了工具调用格式。如果提示词里没有强调“必须用 JSON 格式输出 action”,模型可能就自由发挥了。
解决技巧:在工具描述中加入反例。比如“不要用此工具查询历史数据”,这样能减少误用。另外可以在提示词中加少样本示例,展示正确的工具调用格式。
5.2 循环无法终止或过早终止
循环无法终止通常是因为模型一直在做类似的事情,没有意识到任务已经完成或者无法完成。过早终止则是模型在任务还没完成时就输出了 finish。
排查思路:检查终止条件是否合理。最大轮数设置是否足够?完成信号的定义是否清晰?进展检测的逻辑是否正确?
解决技巧:在提示词中明确写出终止条件,比如“当所有子任务都已完成时,输出 finish”。同时增加进展检测,如果连续两轮没有新的工具调用或工具返回结果没有变化,就强制终止。
5.3 工具调用参数格式错误
模型生成的参数格式不符合预期,比如该传数字传了字符串、该传数组传了单个值、缺少必填参数。
排查思路:检查参数 schema 是否清晰。参数描述要写明类型和格式要求。检查是否有参数校验和类型转换逻辑。
解决技巧:在参数校验层做类型转换和默认值填充。比如字符串 “25” 自动转成数字 25,缺少可选参数时用默认值。如果必填参数缺失,把错误信息返回给模型让它重新生成。
5.4 上下文超长导致模型性能下降
随着循环次数增加,提示词越来越长,模型开始出现“遗忘”或“混淆”的情况。
排查思路:检查历史记录是否做了压缩。检查提示词中是否有冗余信息。
解决技巧:实现上下文压缩,保留最近几轮完整记录,更早的做摘要。同时精简系统提示词,去掉不必要的说明。如果任务特别复杂,可以考虑把部分信息存到外部存储,需要时再检索。
5.5 工具执行超时或外部服务不可用
工具调用外部 API 时遇到超时或服务不可用,导致 Agent 卡住。
排查思路:检查工具执行是否设置了超时。检查是否有重试机制。检查错误信息是否正确返回给了模型。
解决技巧:每个工具调用都设置超时时间,一般 10 到 30 秒。超时后返回错误信息给模型,让它决定是重试还是换策略。对于关键工具,可以实现降级方案,比如缓存数据或备用接口。
5.6 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决技巧 |
|---|---|---|---|
| 模型不调用工具 | 工具描述模糊、提示词未强调格式 | 检查工具描述和系统提示词 | 加入使用场景说明和少样本示例 |
| 循环不终止 | 终止条件不明确、进展检测缺失 | 检查终止逻辑和进展分数 | 设置最大轮数、增加无进展检测 |
| 参数格式错误 | schema 不清晰、缺少校验 | 检查参数定义和校验逻辑 | 类型转换、默认值填充、错误回传 |
| 上下文超长 | 历史未压缩、提示词冗余 | 检查历史记录长度 | 实现摘要压缩、精简提示词 |
| 工具超时 | 未设超时、外部服务不稳定 | 检查工具执行逻辑 | 设置超时、重试、降级方案 |
| 输出解析失败 | 模型输出格式不稳定 | 检查解析逻辑 | 多级解析、格式错误回传引导 |
5.7 独家避坑技巧
第一个技巧是在提示词中要求模型先输出思考过程再输出行动。这个简单的改动能显著提升多步任务的完成率,因为模型在写 thought 的时候实际上在做推理,相当于给它一个“草稿纸”。
第二个技巧是给工具调用结果加上时间戳和来源标记。这样模型在后续循环中能区分哪些信息是新的、哪些是旧的,避免重复调用同一个工具。
第三个技巧是在循环控制中维护一个“已尝试策略”列表。如果模型反复尝试同一个失败的工具,就把这个工具暂时禁用,引导它换其他方式。
第四个技巧是对关键任务设置人工审核节点。Agent 执行到某些敏感操作前暂停,等待人工确认后再继续。这在涉及数据修改或外部请求的场景中特别重要。
6. 工具选型与框架对比:用原生还是用现成的
6.1 原生实现的优势与代价
原生实现意味着你自己写循环逻辑、工具注册、记忆管理、错误处理。优势是完全可控,没有黑盒,调试方便,可以根据需求灵活调整。代价是开发工作量大,需要处理很多细节问题。
我一般建议在以下情况选择原生实现:任务逻辑比较特殊,现成框架难以满足;对性能和成本有严格要求,需要精细控制;团队有足够的技术能力维护自己的实现。
6.2 常见框架的适用场景
LangChain 是最知名的 Agent 框架之一,生态丰富,工具集成多。适合快速原型验证和常见场景。但它的抽象层比较厚,遇到问题时排查起来可能比较麻烦。
Dify 偏向低代码平台,适合非技术背景的用户快速搭建 Agent 应用。它的可视化编排能力比较强,但灵活性相对受限。
CrewAI 专注于多 Agent 协作场景,适合需要多个角色分工的复杂任务。它的抽象模型比较清晰,但学习曲线略陡。
我的建议是:先用原生方式理解核心原理,再根据项目需求选择合适的框架。不要一上来就套框架,否则遇到问题都不知道从哪里排查。
6.3 选型决策参考表
| 考量维度 | 原生实现 | LangChain | Dify | CrewAI |
|---|---|---|---|---|
| 灵活性 | 最高 | 中等 | 较低 | 中等 |
| 开发速度 | 慢 | 快 | 最快 | 中等 |
| 调试难度 | 低 | 中等 | 高 | 中等 |
| 多 Agent 支持 | 需自建 | 有限 | 有限 | 原生支持 |
| 适合场景 | 定制化需求 | 通用场景 | 快速验证 | 协作任务 |
选型没有绝对的对错,关键是匹配项目需求和团队能力。我见过用原生实现跑得很好的项目,也见过用框架快速上线的案例。重要的是理解每个选择背后的权衡。
7. 从能跑到好用:Agent 优化的几个方向
7.1 提示词优化:从模糊到精确
提示词优化是一个持续迭代的过程。我通常会从模糊的指令开始,然后根据实际执行结果逐步细化。比如最初写“帮我查一下数据”,发现模型不知道该查什么,就改成“查询指定城市指定日期的天气数据,日期格式为 YYYY-MM-DD”。
优化的依据来自执行日志。每次 Agent 执行失败或效果不好,就分析是哪个环节的提示词不够清晰,然后针对性修改。这个过程可能需要多轮迭代,但每轮都能看到明显的提升。
7.2 工具优化:从能用 to 好用
工具优化的方向包括:减少工具数量、合并相似工具、优化工具描述、增加参数校验、改进错误信息。
我习惯定期回顾工具的使用情况。哪些工具从来没被调用过?哪些工具经常被误用?哪些工具的错误率特别高?根据这些数据做针对性优化。
7.3 成本优化:从粗放到精细
Agent 的成本主要来自 LLM 调用。优化方向包括:减少循环次数、压缩提示词长度、选择合适的模型、缓存重复调用。
一个实用的技巧是分级使用模型。简单任务用便宜的小模型,复杂任务用强模型。可以在循环控制中根据任务复杂度动态切换模型。
7.4 效果优化:从可用到可靠
效果优化是一个系统工程,涉及提示词、工具、记忆、循环控制等多个方面。我通常会用一组标准测试用例来评估 Agent 的表现,每次优化后跑一遍测试,看指标是否有提升。
测试用例要覆盖正常场景和边界场景。正常场景验证基本功能,边界场景验证容错能力。只有两者都通过,才能说 Agent 是可靠的。
8. 我个人在实际操作中的体会
做了几个 Agent 项目之后,我最大的体会是:Agent 的工程实现,难点不在 LLM 本身,而在 LLM 之外的那些“脏活累活”。工具调用的参数校验、循环的终止判断、上下文的压缩管理、错误的分类处理,这些看起来不起眼的地方,恰恰决定了 Agent 能不能真正跑起来、跑得稳。
另一个体会是:不要追求一步到位。我见过太多项目一开始就想做通用 Agent,结果复杂度失控,连基本任务都跑不通。正确的做法是先聚焦一个具体场景,把单点做透,然后再逐步扩展能力边界。
还有一个实用的经验:日志和可观测性要尽早建设。Agent 的行为具有不确定性,没有详细的执行日志,排查问题就像盲人摸象。我现在的习惯是,在写第一行业务逻辑之前,先把日志框架搭好。
最后分享一个小技巧:在系统提示词的最后加一句“如果你不确定该怎么做,就输出你的困惑并终止”。这句话能有效减少模型在不确定情况下的胡乱尝试,让问题更早暴露出来,而不是等到执行了一堆错误操作之后才发现。