1. 从七个零件到七个岔路口:AI Agent 工程实现的底层逻辑
聊 AI Agent 的人很多,但真正动手搭过一套能跑通、能维护、能扩展的 Agent 系统的人,往往会有一种共同的感受:这东西拆开看每个零件都不复杂,拼在一起却处处是坑。我自己从最早用脚本硬编码工具调用,到后来基于 LangGraph 做状态机编排,再到给团队做 Agent 架构评审,踩过的坑基本覆盖了从提示词设计到并发控制的全链路。这篇文章想做的事情很直接:把 AI Agent 的工程实现拆成七个核心要素,再顺着这七个要素推导出七个关键决策点,让你在动手之前就知道每个岔路口该往哪拐。
先给不太熟悉的朋友补一下背景。AI Agent 这个词现在被用得极泛,有人把套了一层提示词的聊天机器人叫 Agent,有人把带工具调用的 LLM 应用叫 Agent,还有人把多智能体协作系统也叫 Agent。但从工程实现的角度看,一个真正意义上的 Agent 至少需要具备自主决策、工具使用、记忆管理和循环执行这几项能力。它和普通的 LLM 调用最大的区别在于:普通调用是“一问一答”,Agent 是“给一个目标,自己想办法一步步逼近”。这个“自己想办法”的过程,就是工程实现里最复杂也最有意思的部分。
这篇文章适合三类人看。第一类是想从零搭建 Agent 应用的开发者,你需要知道哪些环节是必须自己控制的,哪些可以交给框架。第二类是在做 Agent 架构选型的技术负责人,你需要理解不同方案在并发、安全、可观测性上的取舍。第三类是已经用过 Coze、Dify 这类平台但想深入底层的人,你需要搞清楚平台帮你封装了什么,以及封装之外还有哪些决策要自己做。全文会围绕七个要素和七个决策点展开,每个决策点我都会给出具体的判断依据和实操建议,尽量做到看完就能用。
2. 七要素拆解:一个 Agent 到底由什么构成
2.1 模型层:LLM 是大脑,但大脑不止一种用法
Agent 的第一个要素是模型层,也就是 LLM 本身。很多人一上来就纠结选哪个模型,GPT-4o 还是 Claude,开源模型能不能用。但实际工程里更重要的问题不是“选哪个”,而是“怎么用”。同一个模型,在不同的 Agent 架构里扮演的角色完全不同。
最基础的用法是作为推理引擎,接收当前状态和工具描述,输出下一步动作。这种用法对模型的指令遵循能力要求很高,因为你需要它稳定地输出结构化的工具调用请求。另一种用法是作为规划器,先根据目标生成一个多步计划,再由执行器逐步落实。这种用法对模型的长程推理能力要求更高,但可以降低单步决策的复杂度。还有一种用法是作为评判者,对执行结果进行评估和反思,决定是否需要调整策略。这三种用法可以组合,也可以分开用不同的模型来承担。
我自己的经验是,在预算允许的情况下,规划层用能力最强的模型,执行层可以用稍弱但更快的模型,评判层则可以用中等模型加规则兜底。这样做的好处是成本可控,同时关键决策的质量有保障。如果全部用同一个模型,要么成本爆炸,要么关键环节质量不够。
提示:不要迷信模型榜单上的排名。Agent 场景下,模型的工具调用格式遵循能力和多轮对话中的状态保持能力,比单纯的推理 benchmark 分数重要得多。选模型时一定要用自己的真实工具集做一轮测试。
2.2 工具层:Agent 的手脚,也是最容易出事的地方
工具层是 Agent 与外部世界交互的接口。搜索、计算、读写文件、调用 API、操作数据库,这些都属于工具。工具层的设计直接决定了 Agent 的能力边界,也直接决定了系统的安全风险。
工具的定义通常包含三部分:名称、描述、参数 schema。名称要简洁明确,描述要写清楚这个工具做什么、什么时候用、有什么限制。参数 schema 要严格定义类型和必填项。这三部分看起来简单,但实际写起来非常讲究。描述写得太模糊,模型不知道该什么时候调用;写得太详细,又会占用大量上下文窗口。参数 schema 太宽松,模型容易传错格式;太严格,又可能因为模型输出的小偏差导致调用失败。
我在实际项目里总结出一个原则:工具描述要像写给一个新入职同事看的操作手册,既要说清楚功能,也要说清楚边界和注意事项。比如一个查询订单的工具,描述里要写明“仅支持按订单号精确查询,不支持模糊搜索,单次最多返回一条记录”。这样模型就不会试图用它来做批量查询。
工具层的另一个关键问题是错误处理。工具调用失败是常态,网络超时、参数错误、权限不足、返回格式异常,这些都会发生。Agent 需要能够识别错误类型并决定是重试、换工具还是放弃。如果错误处理做得不好,Agent 很容易陷入无限重试的死循环。
2.3 记忆层:短期靠上下文,长期靠检索
记忆层解决的是 Agent 的“记性”问题。短期记忆通常就是对话历史,直接放在上下文窗口里。长期记忆则需要外部存储和检索机制,常见的有向量数据库、知识图谱、结构化数据库等。
短期记忆的管理核心是上下文窗口的分配。一个 Agent 的上下文里通常包含系统提示词、工具描述、对话历史、工具调用结果、当前任务状态等。这些东西加起来很容易超出模型窗口限制。所以你需要决定哪些信息保留、哪些压缩、哪些丢弃。常见的策略有滑动窗口、摘要压缩、关键信息提取等。
长期记忆的管理核心是写入和检索的时机。什么时候把信息写入长期记忆?通常是任务完成、用户明确要求记住、或者系统判断某条信息有长期价值时。什么时候检索?通常是在任务开始时、遇到相关问题时、或者需要历史上下文时。检索的准确性直接决定了长期记忆的价值,所以嵌入模型的选择和检索策略的设计很关键。
我见过很多 Agent 项目在记忆层翻车,最常见的问题是“记了但不会用”。信息写进去了,但检索时要么召不回,要么召回一堆不相关的。解决这个问题的关键是做好记忆的结构化,不要什么都往向量库里塞。结构化的信息用结构化存储,非结构化的文本再用向量检索,两者结合效果最好。
2.4 编排层:决定 Agent 怎么“想”和怎么“做”
编排层是 Agent 的调度中心,决定了整个系统的控制流。最简单的编排是 ReAct 模式:思考、行动、观察,循环往复。复杂一点的有多智能体协作、分层规划、状态机驱动等。
编排层的设计直接影响 Agent 的可靠性和可维护性。ReAct 模式实现简单,但容易陷入局部最优,而且循环次数不好控制。状态机模式可控性强,但需要预先定义所有状态和转移条件,灵活性差一些。多智能体模式适合复杂任务分解,但通信开销和协调成本高。
选哪种编排方式,取决于你的任务特征。任务步骤相对固定、对可靠性要求高的,用状态机。任务开放性强、需要灵活应变的,用 ReAct 或更自由的模式。任务可以自然分解为多个子任务的,考虑多智能体。没有银弹,只有取舍。
2.5 循环控制层:什么时候停,比什么时候走更重要
循环控制是 Agent 工程里最容易被忽视但最致命的一环。Agent 的本质是一个循环:感知、决策、行动、再感知。但这个循环必须有终止条件,否则就是无限烧钱。
终止条件通常有几类:任务完成、达到最大步数、连续多次无进展、遇到不可恢复的错误、超出预算限制。这几类条件需要组合使用,不能只依赖其中一种。只靠任务完成判断,模型可能永远认为任务没完成。只靠最大步数,可能在任务快完成时被强行中断。
我在实际项目里通常会设置三层保护:单次任务最大步数、连续无进展步数上限、总 token 消耗上限。三层任意一层触发就终止,并返回当前最佳结果和终止原因。这样既能控制成本,也能避免死循环。
2.6 安全层:Agent 越能干,越需要缰绳
安全层在 Agent 系统里的重要性怎么强调都不过分。一个能调用工具、能读写数据、能执行代码的 Agent,如果被恶意输入操控,后果可能非常严重。常见的安全风险包括提示词注入、工具滥用、数据泄露、越权操作等。
提示词注入是最常见的攻击方式。用户在输入里嵌入指令,试图覆盖系统提示词或诱导 Agent 执行非预期操作。防御手段包括输入清洗、指令隔离、输出校验等。工具滥用则是 Agent 被诱导调用不该调用的工具,或者用错误的参数调用工具。防御手段包括工具权限分级、参数校验、敏感操作二次确认等。
安全层的设计原则是最小权限加纵深防御。Agent 只应该拥有完成任务所必需的最小权限,每个敏感操作都应该有独立的校验环节,不能指望单一防线挡住所有攻击。
2.7 可观测层:看不见的 Agent 没法调优
可观测层包括日志、追踪、指标、评估四个部分。Agent 的执行过程是一个多步决策链,如果每一步的输入输出、耗时、token 消耗、工具调用结果都没有记录,出了问题根本没法排查。
日志要记录每一步的完整上下文,包括模型输入、模型输出、工具调用请求和结果、状态变化等。追踪要把一个任务的完整执行链路串起来,方便定位问题出在哪一步。指标要关注成功率、平均步数、平均耗时、token 消耗、工具调用失败率等。评估则是对 Agent 的整体表现做定期评测,包括任务完成率、结果质量、安全性等。
可观测层做得好不好,直接决定了你能不能持续优化 Agent。没有可观测性,调优就是盲人摸象。
3. 七个决策点:每个岔路口该怎么选
3.1 决策点一:自研还是用框架
这是动手前的第一个决策。自研的好处是可控性强,每个环节都能按自己的需求定制。坏处是工作量大,很多基础设施要自己搭。用框架的好处是起步快,社区有现成方案。坏处是受框架约束,深度定制时可能遇到天花板。
我的建议是分阶段决策。原型验证阶段用框架快速跑通,验证核心思路。生产化阶段根据实际需求决定是继续用框架还是逐步替换关键模块。LangGraph、Spring AI Agent 这类框架在编排和工具调用上已经比较成熟,但如果你有特殊的并发要求或安全要求,可能需要在框架基础上做深度定制。
选框架时重点看几个方面:工具调用的灵活性、状态管理的可控性、并发模型是否满足需求、可观测性支持是否完善、社区活跃度和文档质量。不要只看 star 数,要看实际项目里的使用体验。
3.2 决策点二:单 Agent 还是多 Agent
单 Agent 架构简单,调试方便,适合任务边界清晰、步骤不太复杂的场景。多 Agent 架构适合任务可以自然分解、需要不同专长的场景,但引入了通信和协调的复杂度。
判断标准很简单:如果你的任务可以在一套提示词和工具集下完成,就用单 Agent。如果任务明显需要不同领域的知识或工具,且这些领域之间耦合度低,才考虑多 Agent。不要为了架构好看而强行多 Agent,协调成本往往超出预期。
多 Agent 的通信机制也需要决策。是共享内存、消息传递还是黑板模式?共享内存实现简单但容易冲突,消息传递解耦好但需要定义协议,黑板模式灵活但需要设计好数据结构。这些都要根据实际场景来选。
3.3 决策点三:工具调用的粒度和边界
工具粒度太粗,Agent 灵活性差,一个工具做太多事情,参数复杂,容易出错。粒度太细,工具数量爆炸,模型选择困难,调用次数增多,成本和延迟都上去了。
我的经验是,工具粒度应该和业务操作对齐。一个工具对应一个明确的业务动作,参数控制在三到五个以内。如果一个操作需要超过五个参数,考虑拆成多个步骤。如果多个操作总是连续出现,考虑合并成一个工具。
工具边界还要考虑权限和审计。敏感操作应该独立成工具,方便单独控制权限和记录审计日志。只读操作和写操作要分开,方便做权限分级。
3.4 决策点四:记忆的写入和检索策略
记忆策略的核心问题是:什么信息值得记,什么时候记,怎么记,怎么取。不是所有信息都值得写入长期记忆,写入太多会导致检索质量下降。写入太少又会导致 Agent 记不住关键信息。
我的做法是分层记忆。会话级的短期记忆保留完整对话历史,但做滑动窗口和摘要压缩。用户级的长期记忆只保留明确有长期价值的信息,比如用户偏好、常用配置、历史决策等。知识级的记忆则来自外部知识库,按需检索。
检索策略上,我倾向于混合检索:向量检索加关键词检索,再加结构化过滤。纯向量检索在精确匹配场景下表现不稳定,加上关键词和结构化条件可以显著提升准确率。
3.5 决策点五:循环终止和异常处理
循环终止条件前面提过,这里重点说异常处理。Agent 执行过程中会遇到各种异常:模型输出格式错误、工具调用失败、超时、权限不足、外部服务不可用等。每种异常的处理策略不同。
模型输出格式错误,通常重试一次就能解决,如果连续失败则需要降级到更简单的输出格式或人工介入。工具调用失败,要根据错误类型决定重试、换工具还是终止。超时和外部服务不可用,通常需要重试加退避。权限不足则应该直接终止并报告。
异常处理的关键是分类和分级。不是所有异常都需要同等对待,有些可以自动恢复,有些必须人工介入。把异常分类做好,处理策略自然就清晰了。
3.6 决策点六:并发模型和资源隔离
Agent 的并发需求来自两个方面:多个用户同时使用,以及单个任务内部的并行工具调用。并发模型的选择直接影响系统的吞吐量和稳定性。
常见的并发模型有同步阻塞、异步非阻塞、协程、线程池等。同步阻塞实现简单但吞吐量低。异步非阻塞吞吐量高但编程复杂度高。协程是折中方案,在 Python 和 Rust 里都有成熟支持。线程池适合 CPU 密集型任务,但 Agent 场景下 IO 等待居多,异步更合适。
资源隔离是并发场景下必须考虑的问题。不同用户的 Agent 实例应该隔离,避免相互影响。工具调用的资源消耗要有限制,避免单个任务耗尽系统资源。这些都需要在架构设计阶段就考虑进去。
3.7 决策点七:评估和迭代机制
Agent 上线不是终点,而是起点。没有评估机制,你无法知道 Agent 表现如何,也无法持续优化。评估机制包括离线评测和在线监控两部分。
离线评测需要构建测试集,覆盖典型场景和边界情况。评测指标包括任务完成率、结果准确率、平均步数、平均耗时、token 消耗等。测试集要定期更新,覆盖新出现的场景。
在线监控则关注生产环境的表现,包括成功率、用户反馈、异常率等。在线数据可以反哺离线评测,形成闭环。迭代机制的核心是快速实验和灰度发布,新版本先在小流量上验证,确认有效后再全量。
4. 实操落地:从零搭一个最小可用 Agent
4.1 环境准备和技术选型
假设我们要搭一个能查询天气、做简单计算、记录待办事项的 Agent。技术选型上,模型用支持工具调用的主流 LLM,编排用 LangGraph,工具用 Python 函数实现,记忆用 SQLite 加向量库,可观测用 LangSmith 或自建日志系统。
环境准备包括 Python 环境、依赖安装、API 密钥配置。依赖主要包括 langgraph、langchain、openai 或对应模型 SDK、向量库客户端等。API 密钥通过环境变量管理,不要硬编码在代码里。
pip install langgraph langchain openai chromadb python-dotenv配置环境变量:
export OPENAI_API_KEY="your-key-here" export WEATHER_API_KEY="your-weather-key"4.2 工具定义和注册
工具定义要遵循前面说的原则:名称简洁、描述清晰、参数严格。以天气查询为例:
from langchain.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市的当前天气。 参数: city: 城市名称,仅支持中文城市名,如"北京"、"上海"。 返回: 当前天气描述,包括温度和天气状况。 """ # 实际调用天气 API return f"{city}当前晴,温度 25 摄氏度"计算工具和待办工具类似定义。工具注册时要注意,每个工具的 description 会占用上下文窗口,所以要精简但完整。
4.3 状态管理和编排逻辑
LangGraph 的核心是状态图。我们需要定义状态结构、节点函数和边。
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages] step_count: int max_steps: int节点函数包括模型调用节点和工具执行节点。模型调用节点负责生成下一步动作,工具执行节点负责执行工具并返回结果。边则根据模型输出决定是继续循环还是终止。
def should_continue(state: AgentState) -> str: if state["step_count"] >= state["max_steps"]: return "end" last_message = state["messages"][-1] if hasattr(last_message, "tool_calls") and last_message.tool_calls: return "tools" return "end"4.4 循环控制和异常处理实现
循环控制通过 step_count 和 max_steps 实现。每次模型调用后 step_count 加一,达到上限则强制终止。异常处理在每个节点函数里用 try-except 包裹,记录错误信息并决定是否继续。
def call_model(state: AgentState): try: response = model.invoke(state["messages"]) return {"messages": [response], "step_count": state["step_count"] + 1} except Exception as e: # 记录错误,返回错误信息作为消息 error_msg = f"模型调用失败: {str(e)}" return {"messages": [error_msg], "step_count": state["step_count"] + 1}工具执行节点同样需要异常处理,并且要区分可重试错误和不可重试错误。
4.5 可观测性接入
可观测性最简单的实现是结构化日志。每一步的输入输出、耗时、token 消耗都记录到日志里。如果预算允许,接入 LangSmith 这类专业工具会更方便。
import logging import time logger = logging.getLogger("agent") def logged_invoke(model, messages): start = time.time() response = model.invoke(messages) elapsed = time.time() - start logger.info(f"model_invoke elapsed={elapsed:.2f}s tokens={response.usage_metadata}") return response日志要包含 trace_id,方便把同一个任务的多个步骤串起来。trace_id 可以在任务开始时生成,贯穿整个执行链路。
5. 常见问题与排查技巧实录
5.1 工具调用格式错误怎么排查
工具调用格式错误是最常见的问题之一。表现是模型输出的工具调用请求不符合 schema,导致解析失败。排查步骤是:先看模型原始输出,确认是模型没理解 schema 还是输出格式有偏差。如果是理解问题,优化工具描述和参数说明。如果是格式偏差,考虑在提示词里加示例,或者用支持结构化输出的模型接口。
我遇到过一个案例,模型总是把数字参数输出成字符串。后来在参数 schema 里加了明确的类型说明和示例,问题就解决了。还有一次是工具名称太长,模型总是拼错,改成短名称后正常。
5.2 Agent 陷入死循环怎么办
死循环的表现是 Agent 反复执行同样的动作,或者在不同动作之间来回切换但无进展。排查时先看日志,确认循环的模式。如果是反复调用同一个工具,可能是工具返回结果没有让模型认为任务完成。检查工具返回内容是否清晰,是否包含模型需要的完成信号。
如果是来回切换,可能是模型在多个选项之间犹豫。这时候需要检查提示词是否给了明确的决策依据,或者考虑用更确定性的编排方式替代自由决策。
防御死循环的根本手段还是前面说的三层保护:最大步数、连续无进展上限、token 预算上限。这三层保护必须在架构设计时就加上,不能等出了问题再补。
5.3 并发场景下的资源竞争怎么处理
并发场景下最常见的问题是共享资源竞争,比如多个 Agent 实例同时写同一个数据库、同时调用同一个限流 API。处理方式包括加锁、队列、隔离等。
加锁适合短临界区,但要注意死锁风险。队列适合异步处理,把并发请求排队串行化。隔离则是给每个实例独立的资源,成本高但最安全。实际项目里通常是组合使用,关键资源加锁,非关键资源用队列,敏感资源做隔离。
还有一个容易被忽视的问题是上下文窗口的并发消耗。多个任务同时运行时,token 消耗会叠加,可能触发模型的速率限制。这时候需要做全局的速率控制,而不是每个任务独立控制。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决建议 |
|---|---|---|---|
| 工具调用格式错误 | schema 不清晰或模型理解偏差 | 查看模型原始输出 | 优化描述,加示例,用结构化输出 |
| 死循环 | 终止条件缺失或结果信号不明确 | 分析日志中的循环模式 | 加三层保护,明确完成信号 |
| 并发资源竞争 | 共享资源无保护 | 定位竞争资源 | 加锁、队列或隔离 |
| 记忆检索不准 | 嵌入模型或检索策略问题 | 检查召回结果相关性 | 混合检索,结构化过滤 |
| 响应延迟高 | 模型调用或工具调用慢 | 分段计时 | 换更快模型,并行工具调用 |
| token 消耗超预期 | 上下文管理不当 | 统计各环节 token | 压缩历史,精简工具描述 |
5.5 几个踩坑心得
第一个心得是不要过早优化。原型阶段用最简单的方案跑通,确认核心价值后再优化性能和成本。我见过太多项目在原型阶段就纠结架构,结果核心逻辑还没验证。
第二个心得是日志要打全。Agent 的问题往往出在意想不到的地方,日志不全根本没法排查。宁可多打日志,后期再精简,也不要一开始就省。
第三个心得是工具描述要反复打磨。工具描述是模型理解工具的唯一途径,描述质量直接决定调用质量。我通常会找不熟悉项目的人看一遍工具描述,如果他能看懂,模型大概率也能看懂。
第四个心得是安全要从第一天就考虑。不要想着先上线再补安全,Agent 的权限一旦放开,补安全的成本远高于一开始就设计好。
6. 关于 Agent 工程实现的一些个人体会
做 Agent 这几年,我最大的感受是这个领域变化太快,但底层逻辑其实很稳定。七要素和七个决策点这套框架,我在不同项目里反复用过,基本能覆盖大部分工程问题。模型在变,框架在变,但这七个环节该做的决策一个都少不了。
另一个感受是,Agent 的工程实现本质上是在不确定性和可控性之间找平衡。LLM 的输出天然不确定,但工程系统需要可控。所有的架构设计、提示词工程、异常处理,都是在把不确定性收敛到可接受的范围内。理解这一点,很多设计取舍就变得清晰了。
最后分享一个实用建议:如果你刚开始做 Agent,先不要追求功能全面,选一个具体的、有价值的场景做深做透。一个能稳定完成单一任务的 Agent,比一个什么都能做但什么都不稳定的 Agent 有价值得多。把七要素和七个决策点在这个场景里走一遍,你对 Agent 工程的理解会比看十篇文章都深。