简介:本文以构建一个可运行、可观测、可控制成本的智能体为目标,系统拆解从模型调用、工具接入、多步骤执行到上下文管理、测试评估和多智能体协作的完整过程。文章适合开发者、运维人员及技术负责人,用于理解Agent开发的真实难点,并建立可复用的工程方法。
很多人第一次接触Agent时,会得出一个直观结论:Agent不就是调用大模型接口,再调用几个业务接口吗?
从代码结构看,这个判断并没有错。一个最小Agent确实可以由“模型调用、工具调用、循环判断”组成。但从实验代码走向真实系统后,问题会迅速增加:工具参数可能错误,外部服务可能超时,多步骤任务可能不断放大误差,历史上下文可能越来越长,成本可能失控,甚至无法准确判断一次执行到底成功还是失败。
本教程将围绕“如何从零构建一个可靠的Agent执行框架”展开,帮助读者完成以下目标:
适合阅读本文的人员包括应用开发者、后端工程师、运维人员、技术负责人,以及需要评估智能体落地可行性的企业用户。
传统的大模型应用通常采用以下模式:
用户输入问题↓调用模型↓返回文本
这种方式适合问答、摘要、改写、分类等一次性任务。模型只需要根据输入生成结果,不需要真正改变外部系统状态。
Agent则增加了一个持续执行的闭环:
用户目标↓模型分析当前状态↓判断是否需要调用工具↓执行工具并获取结果↓把结果写回上下文↓模型继续判断下一步↓完成任务或触发终止条件
可以将Agent抽象为:
while 未达到终止条件:读取用户目标和当前上下文调用模型生成下一步决策如果模型返回最终答案:输出结果并结束如果模型请求调用工具:校验工具名称和参数执行工具记录工具结果将结果加入上下文
因此,Agent的核心并不只是“会不会调接口”,而是能否让整个循环在复杂、不确定、可能失败的环境中稳定运行。
在写代码之前,首先要明确Agent到底要完成什么任务。
例如,“帮助用户处理报销”是一个过于宽泛的目标,至少可以拆成:
每一步的职责、输入、输出和失败方式都应该提前定义。
建议为任务建立一份最小规格:
任务名称:费用申请处理输入:申请人信息、费用明细、附件信息允许调用的工具:查询规则、校验材料、计算金额、提交申请成功标准:返回处理结论和申请编号失败标准:缺少关键材料、工具不可用、权限不足最大执行步数:不超过预设上限是否允许修改外部数据:提交前必须二次确认
这一步的作用是防止Agent无限扩展任务范围。没有成功标准,模型可能一直“继续分析”;没有失败标准,系统也无法给出明确的错误反馈。
建议先使用一个简单、确定性强的工具验证执行框架,而不是一开始就连接复杂业务系统。
工具可以抽象为以下结构:
{"name": "query_rule","description": "根据费用类型查询处理规则","input_schema": {"type": "object","properties": {"expense_type": {"type": "string"}},"required": ["expense_type"]}}
模型每次返回的结果可以分成两类:
{"type": "tool_call","tool_name": "query_rule","arguments": {"expense_type": "交通"}}
或者:
{"type": "final","content": "根据当前材料,申请可以继续处理。"}
通用伪代码如下:
def run_agent(user_input, tools, max_steps=8):messages = [{"role": "system", "content": "你是一个受约束的任务执行助手"},{"role": "user", "content": user_input}]for step in range(max_steps):decision = call_model(messages, tools)if decision.type == "final":return {"status": "success","answer": decision.content,"steps": step + 1}if decision.type != "tool_call":return {"status": "failed","reason": "模型返回了无法识别的结果"}tool = tools.get(decision.tool_name)if tool is None:return {"status": "failed","reason": "模型请求了未授权工具"}valid, error = validate_arguments(decision.arguments,tool.input_schema)if not valid:messages.append({"role": "tool","content": "参数校验失败:" + error})continueresult = execute_tool(tool,decision.arguments,timeout_seconds=10)messages.append({"role": "assistant","content": decision.raw_output})messages.append({"role": "tool","content": normalize_result(result)})return {"status": "failed","reason": "超过最大执行步数"}
这个版本虽然简单,但已经体现了Agent的基本骨架:决策、校验、执行、反馈、继续决策。
接入真实业务接口后,Agent开发的难点才真正出现。真实接口可能返回超时、空数据、权限错误、字段变化、重复提交或部分成功。
因此,工具层不能只是简单执行请求,而应该统一处理以下问题:
不同业务接口的返回结构通常不一致。建议在工具层进行标准化,将结果转换成Agent容易理解的结构:
{"success": true,"data": {"amount": 128.5,"currency": "CNY"},"error": null,"retryable": false}
失败时也应返回结构化信息:
{"success": false,"data": null,"error": {"code": "TIMEOUT","message": "外部服务响应超时"},"retryable": true}
模型不应该直接面对大量底层异常堆栈,而应看到简洁、稳定、可操作的错误信息。
网络超时、临时不可用、限流等问题通常可以重试;权限不足、参数非法、资源不存在等问题重试没有意义。
建议为每类错误定义处理策略:
可重试错误:- 网络超时- 临时服务不可用- 短时间限流不可重试错误:- 参数格式错误- 权限不足- 业务规则不允许- 资源不存在
重试次数必须设置上限,并使用逐步延迟策略,避免多个Agent同时重试导致故障扩大。
查询类工具一般风险较低,但提交、删除、转账、修改配置等操作会产生真实影响。
这类工具应至少增加以下保护:
不能因为模型判断“用户大概率同意”,就直接执行高风险操作。
Agent最容易出现的问题之一,是第一步的小错误被带入后续步骤,最终形成完全错误的结论。
例如:
识别金额错误↓计算结果错误↓规则判断错误↓提交错误申请
解决这类问题,不能只依赖模型自我修正,而应在流程层增加检查点。
将任务拆成有限状态:
INIT↓INPUT_VALIDATED↓DATA_COLLECTED↓RULE_CHECKED↓RESULT_GENERATED↓WAITING_CONFIRMATION↓COMPLETED
每次状态转换都要检查前置条件。例如,未完成材料校验时,不允许进入提交状态。
适合由程序校验的内容,不要完全交给模型:
模型适合做理解、归纳和决策建议,程序适合做精确计算和硬约束执行。两者应明确分工。
Agent的上下文通常包括:
如果所有内容都原样保留,上下文会越来越长,带来三个问题:
建议将上下文分成三类:
保存当前任务中最近几轮对话和工具结果,适合支持连续执行。
使用结构化字段保存关键状态,例如:
{"task_id": "内部任务编号","current_step": "RULE_CHECKED","user_id": "内部用户标识","validated_items": ["交通费", "住宿费"],"pending_confirmation": false}
任务状态不应只埋在自然语言中,否则模型容易遗漏。
只有确实需要跨任务保留的信息,才进入长期存储,例如用户偏好、组织规则或历史配置。敏感信息应进行权限控制和脱敏处理。
上下文压缩时,不能简单截断最前面的内容。更合理的方式是:
一个Agent失败时,单看最终回答通常无法判断问题出在哪里。必须记录完整执行链路。
建议每次任务生成唯一任务编号,并记录:
任务编号用户请求摘要模型调用次数每次调用耗时输入输出长度工具名称工具参数摘要工具耗时工具返回状态重试次数最终状态失败原因估算成本
日志中不要直接记录完整隐私数据、身份凭证或敏感业务内容。可以采用脱敏、摘要和哈希等方式。
除了日志,还需要关注指标:
只有建立这些指标,才能判断瓶颈究竟来自模型、工具、上下文,还是流程设计。
普通接口测试通常关注输入和输出是否一致,但Agent存在路径不确定性,同一个任务可能经过不同工具调用序列。因此,测试需要覆盖多个层次。
验证工具自身的参数校验、异常处理和结果转换,不依赖模型。
模拟模型返回不同决策,检查Agent是否能正确执行、重试、终止和回滚。
准备正常、缺字段、错误参数、工具超时、权限不足、重复请求等场景,观察最终结果。
测试以下情况:
每次修改系统指令、工具描述、流程策略或模型版本后,都应重新运行固定测试集,防止新增能力破坏原有功能。
测试结果不能只记录“回答看起来不错”,而要判断:
是否调用了正确工具参数是否符合约束是否遵守执行顺序是否在失败后正确终止是否触发了必要确认最终状态是否准确
Agent的成本往往不是一次模型调用,而是多轮模型调用、多个工具调用、重试和长上下文共同叠加的结果。
可以从以下方面控制:
为每个任务设置最大循环次数,超过后转人工或返回可解释失败信息。
为整个任务设置总超时时间,而不是只限制单个工具。这样可以避免某个任务长期占用资源。
对历史记录进行摘要和裁剪,避免每轮把全部内容重复发送。
简单任务可以使用较轻量的处理路径,复杂任务才进入完整规划流程。
当单任务调用次数、工具次数或资源消耗超过阈值时,暂停执行并记录原因。
成本控制并不是单纯减少模型调用,而是在保证任务质量的前提下,减少无效循环、重复查询和错误重试。
多Agent并不等于更先进。很多任务使用一个Agent加多个工具就可以完成,贸然拆分反而会增加通信、调度和状态同步成本。
适合引入多Agent的情况通常包括:
一种常见的分工方式是:
协调Agent:拆解任务、分配工作、汇总结果资料Agent:检索和整理信息执行Agent:调用业务工具审查Agent:检查结果和风险
但多Agent系统必须额外处理:
如果这些问题没有解决,多Agent通常只会把单Agent的问题放大。
可能原因是工具描述不清晰、工具名称相似,或系统没有明确授权范围。
排查方法:
可能原因是输入结构过于复杂,或缺少字段示例和范围说明。
排查方法:
可能是缺少明确终止条件,或模型无法判断工具结果是否已经足够。
排查方法:
可能是错误结果被原样写入上下文,并在后续被当成事实使用。
排查方法:
通常是测试样本过于理想,未覆盖超时、空数据、权限变化和恶意输入。
排查时应补充真实失败场景,并对执行链路进行采样分析,而不是只看最终文本质量。
为了降低开发风险,可以按照以下顺序实施:
不要一开始就追求复杂规划、长期记忆或多角色协作。先让一个小任务稳定闭环,再逐步扩展,通常比直接搭建庞大框架更容易成功。
Agent开发的起点确实是调用接口,但真正困难的部分在于:如何让模型、工具、状态、权限、异常和用户确认共同组成一个可控系统。
一个可靠Agent至少需要具备以下能力:
因此,判断Agent开发水平,不能只看是否成功调通模型接口,而要看它能否在真实环境中持续、稳定、可解释地完成任务。接口调用只是起点,工程化闭环才是Agent真正的开发难点。