☰
AI Agent工程化落地:七要素骨架与七个决策点全解析
2026/10/27 4:15:38 网站建设 项目流程

1. 为什么“七要素”和“七个决策点”值得单独拎出来讲

AI Agent 这个词在过去一年多里被反复提及,但真正落到工程实现层面,很多人还是停留在“调个 LLM API,加个 while 循环,塞几个工具函数”的阶段。我见过不少团队拿着 LangChain 或者 Dify 的工作流搭出一个能跑通 demo 的东西,结果一上生产就各种翻车:上下文越滚越长导致 token 爆炸、工具调用参数解析失败、循环停不下来、错误处理全靠 try-except 兜底。问题出在哪?不是模型不够强,而是对 Agent 的工程结构缺乏系统性的拆解。

我自己的经验是,一个能稳定跑起来的 Agent,本质上要回答两个层面的问题。第一个层面是“它由哪些部件组成”,这就是七要素要解决的——模型、指令、工具、记忆、上下文、循环、输出。第二个层面是“在运行过程中要做哪些关键判断”,这就是七个决策点——什么时候调工具、调哪个、参数怎么给、结果怎么处理、要不要继续循环、上下文怎么裁剪、什么时候终止。七要素是静态的骨架,七个决策点是动态的神经反射。两者合在一起,才构成一个完整的 Agent 工程实现。

这篇文章适合谁看?如果你已经用过大模型 API,写过简单的 function calling,但总觉得自己的 Agent 不够“聪明”或者不够“稳”,那这篇就是写给你的。我会尽量把每个要素和决策点背后的工程逻辑讲透,包括参数怎么算、坑在哪里、我实际踩过哪些雷。不会堆砌框架文档里的套话,而是按一个从业者的视角,把 Agent 从设计到落地的完整链路拆开来讲。

2. 七要素:Agent 的静态骨架到底长什么样

2.1 模型层:不是所有 LLM 都适合做 Agent 的大脑

模型是整个 Agent 的推理核心,但选模型这件事远比“哪个榜单分高就用哪个”复杂。Agent 场景对模型的要求和普通对话场景有本质区别。普通对话只需要模型能理解问题、生成流畅回答就行,但 Agent 需要模型具备三个额外能力:稳定的结构化输出、可靠的工具调用意图识别、以及多步推理时不跑偏。

我实测下来,模型在 Agent 场景的表现可以用三个维度来评估。第一是 function calling 的准确率,也就是给定工具定义和用户输入,模型能不能正确判断该不该调工具、调哪个工具。这个指标在各大榜单上很少单独列出来,但对 Agent 来说是最关键的。第二是长上下文下的指令遵循能力,当上下文滚到几万 token 的时候,模型还能不能记住 system prompt 里的约束。第三是推理链的稳定性,多步任务中模型会不会在中途“忘记”自己之前做了什么。

关于上下文长度,热词里提到的“32k 上下文”“1M 上下文”这些概念,在 Agent 场景下的意义需要重新理解。上下文窗口大不等于 Agent 的记忆能力强。我试过用 128k 上下文的模型跑一个多轮工具调用的任务,理论上把所有历史都塞进去完全没问题,但实际效果反而比手动裁剪到 8k 的效果差。原因是长上下文中的噪声会干扰模型的注意力,历史工具调用的返回结果里大量无关信息会稀释关键信号。所以选模型的时候,上下文窗口大小只是一个参考,更重要的是模型在“有效信息密度”高的上下文中的表现。

注意:不要盲目追求大上下文窗口。Agent 的上下文管理策略比窗口大小重要得多。一个精心设计的 8k 上下文,效果往往好过一个随意堆砌的 128k 上下文。

2.2 指令层:System Prompt 是 Agent 的操作手册

指令层就是 System Prompt,但 Agent 的 System Prompt 和普通对话的完全不是一个东西。普通对话的 System Prompt 可能就一句话“你是一个有用的助手”,但 Agent 的 System Prompt 需要包含:角色定义、能力边界、工具使用规则、输出格式约束、异常处理指引。

我习惯把 Agent 的 System Prompt 分成四个区块来写。第一个区块是身份和职责,明确告诉模型它是谁、要完成什么类型的任务。第二个区块是工具使用规范,包括什么情况下该调工具、什么情况下不该调、工具调用的参数格式要求。第三个区块是输出格式,特别是当 Agent 的输出需要被下游程序解析的时候,格式约束必须极其明确。第四个区块是兜底策略,也就是当模型不确定该怎么做的时候,应该输出什么。

这里有个很容易被忽略的点:System Prompt 的长度和位置会影响模型对指令的遵循程度。我做过一个对比测试,同样的指令内容,放在 System Prompt 开头和放在结尾,模型遵循率差了将近 15%。原因是注意力机制对序列首尾位置的权重更高。所以关键约束建议放在 System Prompt 的开头和结尾各强调一次,中间放具体的工具定义和示例。

2.3 工具层:Function Calling 的工程细节远比想象中多

工具层是 Agent 和外部世界交互的接口。很多人觉得工具定义就是写个 JSON Schema 的事,但实际上工具层的工程细节非常多。工具的描述文本怎么写、参数怎么设计、返回值怎么格式化,每一个环节都会影响 Agent 的成功率。

先说工具描述。模型判断该不该调某个工具,完全依赖工具的名称和描述文本。我见过太多工具描述写得很随意的情况,比如一个查询天气的工具,描述只写了“查询天气”,模型根本不知道它支持哪些城市、返回什么格式的数据。好的工具描述应该包含:功能说明、适用场景、参数含义、返回值格式、以及一个调用示例。

再说参数设计。工具参数的类型和约束要尽可能明确。比如一个搜索工具,query 参数是字符串,但如果你不限制长度,模型可能会生成一个几百字的 query,导致搜索结果很差。我通常会在参数描述里加上“建议不超过 20 个字”这样的约束,实测下来模型会遵循这个建议。

返回值格式化是另一个关键点。工具返回的结果最终会进入上下文,如果返回的是一个大 JSON,里面大量字段对当前决策无关,那就是在浪费上下文空间。我的做法是在工具层做一次预处理,只把模型决策需要的信息提取出来,用简洁的文本格式返回。比如一个数据库查询工具,不要返回完整的行数据,而是返回“查询到 3 条记录,第一条的 name 是 xxx,status 是 active”这样的摘要。

2.4 记忆层:短期记忆和长期记忆要分开设计

Agent 的记忆分为短期记忆和长期记忆。短期记忆就是当前会话的上下文,长期记忆是跨会话持久化的信息。很多 Agent 实现只做了短期记忆,导致每次新会话都从零开始,用户体验很差。

短期记忆的管理核心是上下文裁剪策略。当对话轮次增多、工具调用结果累积,上下文会越来越长。我常用的裁剪策略有三种:滑动窗口、摘要压缩、关键信息提取。滑动窗口就是保留最近 N 轮对话,简单但会丢失早期信息。摘要压缩是用模型对早期对话生成摘要,保留语义但会损失细节。关键信息提取是从历史中抽取实体、决策、状态等结构化信息,压缩率最高但实现复杂度也最高。

长期记忆的实现通常需要一个向量数据库。每次会话结束后,把关键信息 embedding 后存入向量库。新会话开始时,根据用户输入检索相关记忆注入上下文。这里有个坑:检索回来的记忆可能和当前对话不相关,反而干扰模型。所以检索的相似度阈值要调好,宁可少召回也不要召回无关信息。

2.5 上下文层:Agent 工程中最容易被低估的部分

上下文层是七要素中最复杂、最容易出问题的部分。它不仅仅是“把历史消息拼起来”那么简单,而是涉及信息的组织、优先级排序、动态裁剪。

一个 Agent 的上下文通常包含:System Prompt、工具定义、对话历史、工具调用记录、当前用户输入、以及可能的长期记忆注入。这些信息的排列顺序和相对位置会显著影响模型的行为。我的经验是把最关键的约束放在最前面和最后面,中间放工具定义和历史记录。

上下文超长是 Agent 部署中最常见的问题之一。热词里提到的“dify 工作流上下文超长”就是这个问题的典型表现。解决方案不是简单地截断,而是要有策略地压缩。我通常会在上下文达到阈值(比如模型窗口的 70%)时触发压缩流程:先把工具调用的原始返回结果替换成摘要,如果还不够就压缩早期对话,最后才考虑截断。

2.6 循环层:Agent 的心跳机制

循环层是 Agent 区别于普通 LLM 调用的核心特征。普通 LLM 调用是一次性的:输入进去,输出出来,结束。Agent 是一个循环:思考、行动、观察、再思考,直到任务完成或触发终止条件。

循环机制的设计要考虑三个问题:循环的终止条件是什么、单次循环的最大步数是多少、循环过程中状态怎么维护。终止条件通常是模型输出中不再包含工具调用意图,或者模型明确表示任务完成。最大步数是防止无限循环的保险丝,我一般设置 10 到 15 步,具体取决于任务复杂度。状态维护则是确保每次循环都能拿到之前步骤的结果。

2.7 输出层:不只是把结果返回给用户

输出层要做的事情比想象中多。除了把最终结果返回给用户,还要处理中间过程的日志记录、错误信息的友好化、以及结构化输出的解析。

如果 Agent 的输出需要被下游系统消费,那输出格式必须是严格结构化的。我通常要求模型输出 JSON 格式,包含 status、result、error 三个字段。status 表示任务状态,result 是成功时的结果,error 是失败时的错误信息。这样下游系统可以根据 status 做不同的处理。

3. 七个决策点:Agent 运行时的关键判断

3.1 决策点一:该不该调用工具

这是 Agent 每次循环要做的第一个判断。模型需要根据当前上下文判断:用户的需求是否可以通过已有工具满足、当前信息是否足够直接回答、调用工具的成本是否值得。

这个判断的准确率直接影响 Agent 的效率和用户体验。如果该调工具的时候不调,Agent 就会胡编乱造;如果不该调的时候调了,就会浪费时间和 token。我实测下来,在 System Prompt 里明确写出“什么情况下必须调工具、什么情况下禁止调工具”能显著提升这个判断的准确率。

3.2 决策点二:选择哪个工具

当确定需要调用工具后,模型要从可用工具列表中选择最合适的一个。这个决策的难点在于工具之间可能存在功能重叠。比如同时有“搜索网页”和“查询知识库”两个工具,模型需要根据问题类型判断用哪个。

减少工具选择错误的一个有效方法是给工具起一个语义明确的名字,并在描述中写清楚适用场景和不适用场景。另外,工具数量不宜过多,我建议单个 Agent 的工具数量控制在 10 个以内。工具太多会导致模型选择困难,准确率下降。

3.3 决策点三:参数怎么填

工具选定后,模型需要生成调用参数。这是最容易出错的环节之一。常见问题包括:参数类型错误、必填参数缺失、参数值格式不符合要求。

我处理这个问题的经验是:在工具定义中把参数约束写到最细,包括类型、格式、取值范围、示例值。同时在 System Prompt 中强调“生成参数前先检查是否满足所有约束”。另外,在工具层做参数校验和自动修正,比如字符串类型的数字自动转换、缺失的可选参数填默认值。

3.4 决策点四:工具返回结果怎么处理

工具调用返回后,模型需要判断这个结果是否满足需求、是否需要进一步处理。如果返回结果为空或报错,模型需要决定是重试、换工具、还是告知用户。

这个决策点的关键是错误处理策略。我通常会在 System Prompt 中定义几种典型的错误场景和对应的处理方式。比如“如果工具返回空结果,尝试换一个关键词重新调用,最多重试两次”“如果工具报错,检查参数是否正确,修正后重试一次”。

3.5 决策点五:要不要继续循环

每次循环结束时,模型需要判断任务是否完成。如果完成了,就生成最终输出;如果没完成,就继续下一轮循环。

这个判断的难点在于“部分完成”的情况。比如用户要求“查一下明天的天气并帮我写一封提醒邮件”,模型可能查到了天气但还没写邮件。这时候需要模型能识别出任务清单中哪些已完成、哪些未完成。

3.6 决策点六:上下文怎么裁剪

当上下文接近模型窗口限制时,需要触发裁剪。裁剪策略的选择本身也是一个决策点:是压缩历史对话、还是丢弃早期工具调用记录、还是对长期记忆做摘要。

我的做法是设置一个优先级:工具调用的原始返回结果最先被压缩成摘要,然后是超过 5 轮的早期对话被摘要,最后才考虑丢弃。System Prompt 和最近 3 轮对话永远不裁剪。

3.7 决策点七:什么时候终止

终止条件有两个:正常终止和异常终止。正常终止是任务完成,异常终止是达到最大循环步数或遇到不可恢复的错误。

异常终止时的输出很重要。不能简单地返回一个“任务失败”,而是要告诉用户:完成了哪些部分、卡在了哪里、建议怎么处理。这样用户至少知道 Agent 做了什么,而不是面对一个黑盒。

4. 从零搭建一个最小可用 Agent 的实操过程

4.1 环境准备和技术选型

我用 Python 来演示,因为生态最成熟。核心依赖就两个:一个 LLM SDK 和一个 HTTP 客户端。不需要上 LangChain 这种重框架,对于理解 Agent 原理来说,手写一遍反而更清晰。

# 核心依赖 # pip install openai httpx pydantic

模型选择上,我建议用支持 function calling 的模型。如果只是本地实验,可以用 Ollama 跑一个 7B 级别的模型,但要注意小模型在工具调用上的准确率会明显下降。

4.2 定义工具和工具注册机制

先定义两个最简单的工具:一个查天气,一个算数学。

import json from pydantic import BaseModel class WeatherParams(BaseModel): city: str def get_weather(city: str) -> str: # 模拟实现 data = {"北京": "晴,25度", "上海": "多云,28度"} return data.get(city, "未找到该城市天气数据") class CalcParams(BaseModel): expression: str def calculate(expression: str) -> str: try: result = eval(expression) return str(result) except Exception as e: return f"计算错误: {e}" TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "查询指定城市的当前天气。适用于用户询问天气情况时。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如'北京'、'上海',不超过10个字" } }, "required": ["city"] } } }, { "type": "function", "function": { "name": "calculate", "description": "计算数学表达式。适用于需要精确计算的场景。", "parameters": { "type": "object", "properties": { "expression": { "type": "string", "description": "数学表达式,如'2+3*4'" } }, "required": ["expression"] } } } ]

4.3 实现核心循环

这是 Agent 的心脏部分。每一轮循环做四件事:调模型、解析输出、执行工具、更新上下文。

import openai client = openai.OpenAI() SYSTEM_PROMPT = """你是一个助手,可以使用工具来帮助用户。 规则: 1. 需要实时信息或精确计算时,必须调用工具 2. 调用工具前确认参数完整且格式正确 3. 工具返回结果后,基于结果回答用户 4. 如果工具返回错误,尝试修正参数后重试一次 5. 最多重试两次,仍失败则告知用户 """ def run_agent(user_input: str, max_steps: int = 10): messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input} ] for step in range(max_steps): response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=TOOLS, tool_choice="auto" ) msg = response.choices[0].message messages.append(msg) # 没有工具调用,任务完成 if not msg.tool_calls: return msg.content # 执行工具调用 for tool_call in msg.tool_calls: func_name = tool_call.function.name args = json.loads(tool_call.function.arguments) if func_name == "get_weather": result = get_weather(**args) elif func_name == "calculate": result = calculate(**args) else: result = f"未知工具: {func_name}" messages.append({ "role": "tool", "tool_call_id": tool_call.id, "content": result }) return "达到最大步数限制,任务未完成"

4.4 上下文管理策略的实现

上面的代码有个明显问题:messages 会无限增长。加一个裁剪函数。

def trim_context(messages, max_tokens=6000): # 粗略估算:1 token 约等于 1.5 个中文字符 def estimate_tokens(msgs): total = 0 for m in msgs: content = m.get("content", "") or "" total += len(content) / 1.5 return total if estimate_tokens(messages) <= max_tokens: return messages # 保留 system prompt 和最近的消息 system_msg = messages[0] recent = messages[-6:] # 中间部分做摘要 middle = messages[1:-6] if middle: summary = f"[历史对话摘要:共{len(middle)}条消息,包含工具调用记录]" return [system_msg, {"role": "system", "content": summary}] + recent return [system_msg] + recent

4.5 参数计算与阈值选择

max_steps 设多少合适?我的经验值是:简单问答类任务 3 到 5 步,多工具协作任务 8 到 12 步,复杂规划类任务 15 到 20 步。超过 20 步还没完成,大概率是模型陷入了某种循环,继续下去也是浪费。

上下文阈值怎么定?假设模型窗口是 8k token,我通常把裁剪阈值设在 6k,留 2k 给模型输出。如果模型窗口是 32k,阈值设在 24k 左右。核心原则是给输出留足空间,因为 Agent 的输出可能包含工具调用参数,比普通对话的输出要长。

5. 常见问题与排查技巧实录

5.1 工具调用参数解析失败

这是最高频的问题。模型生成的参数 JSON 可能包含多余字段、类型错误、或者格式不合法。排查步骤:先打印模型返回的原始 arguments 字符串,看是不是 JSON 格式问题;如果是类型问题,在工具层做类型转换;如果是多余字段,在参数校验时忽略未知字段。

5.2 循环停不下来

模型反复调用同一个工具,或者在不同工具之间来回切换但任务没有进展。解决方法:在 System Prompt 中明确“同一个工具连续调用超过两次仍未获得有效结果时,停止调用并告知用户”;在代码层设置工具调用次数上限。

5.3 上下文超长导致模型“失忆”

当上下文接近窗口限制时,模型可能忘记 System Prompt 中的约束。解决方法:在每轮循环的用户消息中重复关键约束,或者使用支持更长上下文的模型。

5.4 工具返回结果格式不一致

不同工具返回的数据格式不同,模型处理起来容易出错。解决方法:在工具层统一返回格式,都用{"status": "success/error", "data": "...", "message": "..."}这样的结构。

问题类型典型表现排查方向解决方案
参数解析失败JSON 解析报错检查模型原始输出工具层做容错解析
循环不终止重复调用同一工具检查终止条件设置步数上限和重复检测
上下文超长模型忽略指令检查 token 数实现上下文裁剪策略
工具选择错误调用了不相关的工具检查工具描述优化工具描述和命名
输出格式错误下游解析失败检查输出约束强化格式指令和示例

6. 一些实战中攒下来的经验

Agent 的工程实现没有银弹,很多问题需要在具体场景中反复调试。我最大的体会是:不要试图一次性设计一个完美的 Agent,而是先跑通最小闭环,然后根据实际失败案例逐步优化。每次失败都是一次改进 System Prompt 或工具定义的机会。

另一个经验是关于日志的。Agent 的运行过程一定要有完整的日志记录,包括每次模型调用的输入输出、工具调用的参数和结果、上下文的变化。没有日志,排查问题就是盲人摸象。我通常会把日志写到文件里,按会话 ID 分目录存储,方便回溯。

最后说一个关于模型选择的实际感受。贵的不一定好,但太便宜的往往在工具调用上翻车。我试过用一些低价模型跑 Agent,单次调用成本确实低,但因为工具调用准确率差,需要更多轮循环才能完成任务,总成本反而更高。所以选模型的时候要算总账,不能只看单次调用的价格。

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

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

立即咨询