AI Agent架构设计:模型负责判断,Runtime负责执行的工程实践 1. 从“全能模型”到“专业分工”一个架构思维的转变最近在和一些做AI应用落地的朋友聊天发现一个挺有意思的现象大家一提到“工具调用”Tool Use第一反应还是去调教大模型让它“学会”怎么调用API、怎么处理返回结果。这思路不能说错但总觉得有点拧巴像是让一个战略家去干排长的活儿既浪费了它的天赋又暴露了它的短板。我自己的项目里从早期让模型直接生成JSON去调接口到现在明确将“判断”和“执行”分离踩了不少坑也尝到了甜头。今天想聊的这个核心观点——“模型只负责判断Runtime才真正执行”听起来像是一句技术口号但背后其实是一整套关于如何构建可靠、高效、可维护的AI Agent或智能工作流的架构哲学。它不是某个框架的专利而是一种设计模式理解了它你再看LangChain、AutoGPT、甚至是各大云厂商的Agent框架会发现它们都在往这个方向靠拢。简单来说这个模式试图解决一个根本矛盾大语言模型LLM擅长的是理解、推理和规划但在精确执行、状态管理和错误处理上是个“糙汉子”而传统的程序运行时Runtime恰恰相反它执行力强悍、状态清晰、异常处理机制完善但缺乏“智能”。把这两者强行糅合在一起就像让诸葛亮去冲锋陷阵让张飞去运筹帷幄结果往往是诸葛亮被打得鼻青脸肿张飞气得七窍生烟。所以我们今天要拆解的就是如何让“诸葛亮”模型安心坐在中军帐里做判断、下指令而让“张飞们”Runtime去前线精准执行。这不仅仅是分工更是对AI能力边界的一次清醒认知和高效利用。2. 为什么“判断”与“执行”必须分离拆解三个核心痛点在深入技术实现之前我们必须先搞清楚为什么传统的“模型全包”思路会走不通。从我实际项目经验来看主要卡在三个地方可靠性、可控性和复杂性。这三个痛点恰恰是工程化落地的死穴。2.1 可靠性危机模型的“幻觉”与执行的不确定性让模型直接生成可执行代码如Python函数调用或结构化指令如API调用参数最怕的就是“幻觉”。模型可能生成一个根本不存在的函数名或者参数类型完全错误。更棘手的是这种错误是概率性的这次调用成功下次相同的输入可能就失败了。在需要高可靠性的生产环境比如金融交易、设备控制中这种不确定性是不可接受的。一个真实的踩坑案例早期我们做一个智能客服工单系统需要模型根据用户描述自动填写工单表单。我们让模型直接输出一个包含“问题类型”、“紧急程度”、“描述摘要”的JSON。结果发现模型经常把“紧急程度”输出成“高”、“中”、“低”这样的字符串但后端接口只接受数字代码1、2、3。虽然可以通过后处理字符串映射来修复但这引入了额外的复杂性和故障点。更糟糕的是当用户描述模糊时模型可能会“脑补”出一个不存在的“问题类型”枚举值导致整个提交失败。这里的根本问题是模型不擅长也不应该负责维护业务规则的精确性。业务规则如枚举值定义、参数格式、校验逻辑是确定的、严谨的应该由Runtime来保障。模型的角色应该是理解用户意图并从Runtime提供的、确定的选项中进行选择。2.2 可控性黑洞状态管理与流程的失序AI应用往往不是一次请求-响应就结束的它可能涉及多轮对话、多个工具的顺序或条件调用也就是一个工作流Workflow。如果让模型来管理这个流程状态比如记住上一步用了什么工具、返回了什么结果、下一步该做什么那将是一场灾难。模型本质上是无状态的stateless每次调用都是独立的。虽然可以通过在Prompt里拼接历史对话来模拟状态但这会迅速消耗上下文窗口并且历史信息的权重和准确性无法保证。模型可能会“忘记”关键步骤或者在复杂的条件分支中迷失。Runtime的天然优势真正的运行时环境Runtime天生就是为状态管理而生的。它可以维护一个会话级的上下文Context精确地记录每一步的工具调用记录、输入输出、执行状态成功、失败、进行中。基于这个清晰的状态Runtime可以决定是继续调用下一个工具还是回退到上一步或是向用户请求澄清。这个决策逻辑本身可以是规则引擎也可以是一个更轻量、更专注的小模型比如专门做流程控制的模型但绝不是那个负责理解用户原始意图的大模型。2.3 复杂性陷阱安全、权限与资源管理的缺失工具调用往往涉及对系统或外部资源的操作比如读写数据库、发送邮件、调用内部API。这些操作直接关联着安全、权限和资源管理。安全你能放心让一个可能产生“幻觉”的模型去拼接SQL查询语句吗哪怕只有千分之一的概率生成“DROP TABLE”后果也是灾难性的。权限用户A可能只有查询权限用户B可能有写入权限。这个权限校验逻辑应该放在哪里显然不应该依赖模型去“判断”用户有没有权限这必须是Runtime在调用具体工具前根据用户令牌Token和访问控制列表ACL进行的强制校验。资源管理调用一个耗时的外部API需要设置超时调用一个付费API需要计量和限流。这些资源管控策略是Runtime的职责范畴。如果把这些责任都推给模型就等于在系统核心安全层开了一个概率性的后门其复杂度和风险是呈指数级增长的。3. “模型判断层”的设计从意图识别到结构化决策明确了分工的必要性我们来看看“模型判断层”具体该做什么。它的核心输入是用户请求和当前上下文由Runtime提供输出是一个明确的、结构化的“决策指令”。这个指令不是可执行代码而是一个告诉Runtime“做什么”和“用什么参数”的声明式描述。3.1 定义清晰的工具契约Tool Contract这是整个架构的基石。Runtime必须向模型“公布”它能做什么。这个公布不是简单的文本描述而是一份结构化的“工具清单”或“技能菜单”。每项工具需要明确工具名称name唯一标识符如get_weather。工具描述description用自然语言清晰说明工具的功能和用途这是模型进行匹配的主要依据。描述要准确避免歧义。例如“获取指定城市当前天气”就比“查询天气”要好。参数模式parameters定义工具所需的输入参数包括参数名、类型string, number, boolean等、描述以及是否必需。这本质上是一个JSON Schema。返回描述returns简要说明工具执行成功后的返回结果是什么有助于模型理解后续操作。{ tools: [ { name: search_products, description: 根据产品名称关键词在公司产品数据库中搜索相关产品返回产品列表包含产品ID、名称和简介。, parameters: { type: object, properties: { query: { type: string, description: 用于搜索产品的关键词 }, max_results: { type: integer, description: 返回的最大结果数量默认值为5, default: 5 } }, required: [query] } }, { name: get_product_details, description: 根据产品ID获取该产品的详细规格、价格和库存状态。, parameters: { type: object, properties: { product_id: { type: string, description: 产品的唯一标识符 } }, required: [product_id] } } ] }关键心得工具描述description的撰写质量直接决定了模型判断的准确率。要站在模型的角度思考用它能理解的、区分度高的语言。避免使用“处理数据”、“操作文件”这种过于宽泛的描述要具体到“读取CSV文件并计算某列平均值”这个粒度。3.2 设计模型的决策输出格式模型在收到用户请求和工具清单后需要输出一个结构化决策。目前主流有两种方式方式一函数调用Function Calling格式这是OpenAI等厂商推广的模式。模型输出一个或多个tool_calls对象每个对象包含选中的工具ID对应工具名称和参数arguments。{ tool_calls: [ { id: call_abc123, type: function, function: { name: search_products, arguments: {\query\: \无线蓝牙耳机\, \max_results\: 3} } } ] }方式二结构化JSON模式JSON Mode直接要求模型输出一个固定的JSON结构。这种方式更灵活可以包含更丰富的决策信息比如置信度、备选方案等。{ action: call_tool, tool_name: search_products, parameters: { query: 无线蓝牙耳机, max_results: 3 }, reasoning: 用户想找无线蓝牙耳机应使用产品搜索工具。 }实操对比Function Calling格式与模型集成更紧密使用方便但扩展性稍弱比如很难让模型一次性输出一个并行工具调用计划。结构化JSON模式需要更精细的Prompt工程来约束输出但控制力更强适合复杂决策场景。我的经验是简单场景用Function Calling追求稳定复杂、自定义要求高的场景用JSON Mode自己定义决策schema。3.3 构建有效的系统提示词System Prompt系统提示词是引导模型正确扮演“判断者”角色的关键。它需要清晰地告诉模型你的角色你是一个调度员或决策者负责分析用户需求并选择合适工具。你的能力边界你只能使用提供的工具列表。如果用户请求无法用现有工具完成你必须如实告知而不是尝试编造或调用不存在的工具。你的输出要求你必须严格按照指定的格式如Function Calling或自定义JSON输出。上下文的使用如果Runtime提供了之前的对话历史或工具执行结果你应基于这些信息做出下一步决策。一个有效的系统提示词模板如下你是一个智能助手可以通过调用工具来帮助用户。你无法直接执行任何操作所有操作都必须通过调用下方提供的工具来完成。 ## 可用工具 以下是你可以调用的工具列表每个工具都有名称、描述和所需的参数 此处插入格式化的工具契约列表 ## 输出格式 你必须且只能以以下JSON格式回应 { tool_calls: [ { id: 一个随机生成的唯一调用ID, type: function, function: { name: 工具名称必须来自可用工具列表, arguments: 一个严格的JSON字符串包含该工具所需的所有参数 } } ] } 如果用户请求无法通过任何工具满足或者已经完成则输出{tool_calls: []} ## 当前上下文 此处由Runtime插入之前的对话历史或工具执行结果 现在请基于用户的最后一次输入决定是否需要调用工具以及调用哪个工具。 用户输入用户当前请求4. “Runtime执行层”的构建从指令到可靠动作当模型输出了结构化的决策指令后重任就完全落在了Runtime执行层。这里是确保整个系统稳定、安全、高效的核心。Runtime需要像一个老练的指挥官接收清晰的指令然后组织步兵、炮兵、后勤精准协同。4.1 指令解析与验证Runtime首先需要解析模型传来的指令。这里的第一步不是盲目执行而是验证。工具存在性校验指令中的tool_name是否在注册的工具清单中如果不存在立即终止并反馈错误“请求了不存在的工具”。参数模式校验使用定义好的JSON Schema对arguments进行验证。检查参数类型是否正确字符串、数字、布尔值必填参数是否缺失数值是否在约定范围内比如max_results不能超过100。这一步能拦截绝大部分因模型“幻觉”或用户输入歧义导致的结构化错误。业务规则预校验有些校验需要结合业务状态。例如cancel_order工具可能需要校验订单是否处于“可取消”状态。这部分轻量级的业务规则校验可以由Runtime在调用具体工具实现前完成。验证失败的处置验证失败时Runtime不应直接向用户抛出一个技术错误而是应该将“参数验证失败”这个事实连同具体的错误信息如“product_id参数应为字符串但收到了数字”作为新的上下文重新交给模型去判断。模型可能会基于这个反馈修正它的决策或者向用户提问以澄清。这就形成了一个自我修正的闭环。4.2 安全沙箱与权限控制在调用实际工具代码前必须经过安全关卡。权限检查根据当前用户身份从会话Token解析和要调用的工具查询访问控制策略。例如只有“经理”角色的用户才能调用generate_financial_report工具。这一步必须在Runtime层面强制完成逻辑清晰没有模糊空间。输入净化与防注入特别是对于涉及数据库查询、系统命令的工具Runtime需要对输入参数进行严格的转义和过滤。即使工具描述是“执行SQL查询”Runtime传递给底层数据库驱动程序的也应该是一个参数化查询Prepared Statement而不是拼接的SQL字符串。资源访问隔离可以考虑为工具的执行提供一个受限的运行环境沙箱限制其网络访问、文件系统读写和CPU/内存使用。这对于执行用户提供的代码或不可信第三方工具尤为重要。4.3 工具执行与状态管理验证和安全检查通过后Runtime调用具体的工具实现。这个“工具”可以是一个本地函数、一个远程API调用、一个脚本甚至是对另一个AI模型的调用。执行封装每个工具在Runtime中都被封装成一个统一的接口例如一个execute(params, context)方法。Runtime负责传入参数和当前上下文并捕获输出或异常。状态记录在调用前后Runtime需要更新会话状态。记录工具调用开始时间、结束时间、输入参数、输出结果或错误信息。这个状态日志对于调试、审计和后续的流程决策至关重要。超时与重试对于可能耗时的或网络调用Runtime需要设置超时机制。对于因临时网络故障导致的失败可以配置重试策略如最多重试3次指数退避。这些韧性Resilience模式是生产级Runtime的标配。4.4 结果处理与路由决策工具执行完毕后会返回结果或抛出异常。Runtime需要处理这些结果。成功结果处理工具返回的结果可能需要被格式化、裁剪或丰富然后再交给模型。例如一个数据库查询工具可能返回20条记录但Runtime可以只取前5条放入上下文避免上下文窗口爆炸。异常处理执行过程中可能发生各种错误网络超时、API返回错误码、业务逻辑异常等。Runtime需要捕获这些异常并将其转化为一种结构化的错误信息如{error: API_TIMEOUT, message: 支付网关响应超时}然后同样地将这个错误作为新上下文反馈给模型。由模型来决定是重试、换一种方式还是向用户道歉并说明情况。流程路由这是Runtime最“智能”的部分之一。基于当前工具执行的结果和会话状态Runtime需要决定下一步做什么。是继续调用下一个工具串行工作流还是根据结果分支if-else逻辑这个决策逻辑可以是硬编码的规则“当A工具成功且返回结果0则调用B工具”也可以由一个更轻量、更快速的“流程控制器”小模型来驱动。关键是这个决策逻辑是确定性的或受控的而不是由那个大型的、不稳定的主模型来负责。5. 实战架构模式串联、并联与循环理论说完了我们来看几个具体的架构模式理解“判断”与“执行”如何在不同场景下协同。5.1 基础串联模式问答-执行-再问答这是最简单的模式也是ChatGPT插件的基础。适用于目标明确、步骤线性的任务。用户“帮我查一下北京明天天气然后推荐一件适合穿的衣服。”模型判断分析请求发现需要两个工具get_weather(城市北京日期明天) 和recommend_clothing(依赖天气结果)。模型输出先调用get_weather。Runtime执行调用天气API获取结果“明天北京晴5-15°C”。Runtime将结果反馈给模型作为新的上下文连同用户原始请求再次请求模型判断。模型二次判断基于天气结果决定调用recommend_clothing(天气晴温度5-15°C)。Runtime二次执行调用穿衣推荐服务得到结果“建议穿夹克或薄毛衣”。Runtime整合回复将两次结果整合回复用户“北京明天晴5到15度。建议穿夹克或薄毛衣。”在这个模式中Runtime像一个尽职的秘书严格执行老板模型的每一个指令并把每个指令的结果准确汇报给老板等待下一个指示。5.2 并行与规划模式一次判断多个执行对于复杂的、包含多个独立子任务的需求让模型一次性做出完整规划然后由Runtime并行或按序执行效率更高。用户“我想对比一下iPhone 15、三星S24和小米14这三款手机的电池、屏幕和价格。”模型判断识别出需要获取三款手机在三个维度电池、屏幕、价格的信息。这可以分解为9个独立的查询子任务3款手机 × 3个属性。模型可以输出一个包含多个tool_calls的决策或者输出一个更复杂的“计划”指令。模型输出规划模式{ plan: [ {tool: get_phone_spec, params: {model: iPhone 15, spec: battery}}, {tool: get_phone_spec, params: {model: iPhone 15, spec: screen}}, {tool: get_phone_spec, params: {model: iPhone 15, spec: price}}, // ... 三星S24和小米14的6个任务 ] }Runtime执行Runtime接收到这个“计划”后可以分析任务间的依赖关系这里没有依赖然后利用线程池或异步任务队列并行执行这9个查询任务。结果聚合所有任务执行完毕后Runtime将所有结果收集、整合成一个结构化的表格或摘要。最终回复Runtime将聚合后的结果表格交给模型让模型生成一段给用户的对比总结或者直接由Runtime格式化后输出。这个模式下模型的“判断”上升到了“规划”层次而Runtime则展现了其强大的任务调度和并发执行能力。5.3 自主循环模式Runtime驱动的复杂工作流对于一些标准化程度高、逻辑固定的复杂流程可以进一步将控制权从模型移向Runtime。模型只在关键决策点通常需要理解自然语言介入。以智能订餐机器人为例用户“帮我订一份披萨。”模型判断识别意图为“订餐”触发“订餐工作流”。Runtime接管Runtime启动一个预定义的“订餐”状态机。Runtime执行步骤1调用list_restaurants(品类披萨)获取列表并主动提问用户“附近有A、B、C三家披萨店您选哪家”用户“选A店。”Runtime执行步骤2调用get_menu(餐厅A)获取菜单并提问“A店有玛格丽特、夏威夷、海鲜披萨您要哪种”用户“海鲜披萨大份。”Runtime执行步骤3调用checkout(商品海鲜披萨大份 餐厅A)。此时可能需要用户确认地址、支付等这些都可以由Runtime通过标准化的子流程处理。在整个流程中模型只在最开始被调用了一次用于识别初始意图“订餐”。后续的所有步骤选择哪家店、哪种披萨、多大份都是通过Runtime的标准交互按钮、列表选择、表单填写或简单的关键词匹配来完成只有在用户输入非常规、无法匹配时如用户说“要那个有菠萝和火腿的”Runtime才再次请求模型进行语义理解。这种模式将模型的“智能”用在刀刃上处理模糊、非常规输入而将流程的推进、状态维护、标准化交互交给高效、可靠的Runtime极大地提升了复杂任务的完成率和用户体验。6. 避坑指南从设计到部署的实战经验纸上得来终觉浅绝知此事要躬行。在实际项目中落地这套架构有几个坑是大概率会遇到的。6.1 工具描述的“描述性”与“精确性”平衡工具描述写得太模糊模型无法准确匹配写得太详细、太技术化模型又可能不理解。我的经验是用模型能懂的语言描述中多使用“用户”、“获取”、“查询”、“计算”、“发送”这类动词开头明确动作对象。例如“发送一封电子邮件给指定的收件人”就比“执行邮件发送协议”要好。突出区别性特征如果有两个相似工具一定要在描述中强调它们的核心区别。比如search_web从互联网搜索和search_internal_wiki从内部知识库搜索描述里就要明确点出“互联网”和“内部”这个关键差异。参数描述要具体对于date参数描述成“日期格式为YYYY-MM-DD”比“日期”要好得多。这能有效减少模型输出格式错误的概率。6.2 上下文管理的艺术避免窗口爆炸与信息丢失多轮工具调用后上下文会越来越长。如何管理摘要Summarization对于冗长的工具执行结果如一篇长文章、一份数据列表可以让Runtime调用一个文本摘要模型或让主模型自己摘要将关键信息浓缩后再放入上下文。这比直接截断更有效。选择性记忆不是所有历史信息都需要。Runtime可以维护一个“关键事实”存储器只提取对后续决策至关重要的信息如用户选择的商品ID、确认的日期等放入上下文。分窗策略采用类似“滑动窗口”的机制始终保留最近的N轮交互和最开始的系统提示中间部分根据重要性进行摘要或丢弃。这需要精细的设计。6.3 错误处理与用户体验让失败变得优雅工具调用失败是常态。如何处理失败直接影响用户体验。分级错误处理可重试错误如网络超时Runtime自动重试无需打扰用户和模型。需澄清错误如参数模糊“查一下天气”——哪个城市Runtime将错误“需要城市参数”反馈给模型由模型生成一个澄清性问题问用户。硬性失败如权限不足、资源不存在Runtime应生成明确的错误信息并反馈给模型让模型以友好的方式告知用户无法完成并可能提供替代方案。永远要有兜底策略即使所有工具都失败Runtime也应保证能返回一个响应而不是让请求挂起或崩溃。例如可以有一个最终的fallback_response工具它总是返回“抱歉我现在无法处理这个请求请稍后再试或尝试其他问题。”6.4 性能与成本考量模型调用是最贵的环节每一次模型判断都是一次API调用有延迟和成本。要尽量减少不必要的模型调用。在上述“自主循环模式”中就大量减少了模型调用。工具执行的异步化对于耗时长的工具如生成报告、处理视频Runtime应该将其提交到异步任务队列立即返回一个“任务已接收”的响应然后通过其他渠道如WebSocket、邮件通知用户结果。而不是让用户同步等待。缓存策略对于相同参数的工具调用如查询天气、获取股票价格Runtime可以引入缓存层在短期内直接返回缓存结果避免重复调用外部API和模型。7. 主流框架是如何实践这一理念的理解了核心思想我们再回头看市面上主流的AI应用框架会发现它们不约而同地采用了类似的架构。LangChain / LangGraph其AgentToolsExecutor的架构就是经典体现。Agent通常是LLM负责“判断”和生成AgentAction决定使用哪个Tool参数是什么ExecutorRuntime负责调用对应的Tool并管理执行循环AgentFinish或继续。LangGraph更进一步用图Graph来显式地定义工作流的状态流转将Runtime的控制逻辑图形化、模块化。AutoGPT / BabyAGI这些早期自主Agent项目其核心循环也是“分析目标 - 计划任务 - 执行任务 - 评估结果”。其中“执行任务”这一步就是由Runtime调用具体的工具如读写文件、网页搜索来完成。云厂商的AI Agent服务如Azure AI Agents、Google Vertex AI Agent Builder它们提供了托管的Runtime环境让你可以专注于定义工具技能和设计对话流程底层的状态管理、工具调度、错误处理和安全隔离都由平台负责。这大大降低了构建可靠Agent的门槛。这些框架的成功从侧面印证了“模型判断Runtime执行”这一分工模式的合理性与必要性。它不是一个临时方案而是AI应用走向工程化、产品化的必然架构选择。走到最后我的体会是构建AI应用尤其是涉及工具调用的复杂应用心态上要从“让AI学会一切”转变为“让AI在它擅长的领域做决策让传统软件在它们擅长的领域做执行”。这就像组建一个团队你要让创意人员去发想让工程师去实现让项目经理去跟进而不是指望一个人全包全揽。认清并尊重每个组件的边界设计好它们之间的协作契约整个系统才会变得清晰、健壮和高效。当你把大模型从繁琐的执行细节中解放出来它才能真正发挥其“理解”与“推理”的惊人潜力而Runtime则确保了这份潜力能够安全、可靠地落地创造出实实在在的用户价值。这个分工协作的思维或许才是Tool Use乃至更广泛的AI工程化道路上最值得打磨的核心能力。