ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI Native架构实战:从模型调用到Agent编排的工程化落地

AI Native架构实战:从模型调用到Agent编排的工程化落地 1. 为什么现在必须重新思考系统架构过去两年我参与过三个从零起步的 AI 项目也接手过两个“传统系统加个模型接口”的改造项目。踩过的坑让我越来越确信一件事把大模型当成一个外部 API 塞进现有架构和真正以 AI 为核心重新设计系统是两种完全不同的工程实践。前者像是在老房子里加装一台新电器后者则是从地基开始就为这台电器预留电路、散热和空间。AI Native 架构要解决的核心问题不是“怎么调用模型”而是“当模型成为系统的一等公民之后数据怎么流、状态怎么管、失败怎么兜、成本怎么控”。它适合正在做 AI 应用从 0 到 1 的开发者、需要把 AI 能力嵌入业务系统的架构师以及想理解 Agent 和 LLM 工程化落地路径的技术负责人。如果你只是偶尔调一下模型接口做个 Demo这篇文章的部分内容可能偏重但只要你打算让 AI 能力长期稳定地跑在生产环境里下面这些经验应该能帮你少走几个月弯路。我先把结论放在前面AI Native 架构的本质是把不确定性当作系统的基本属性来设计而不是当作异常来处理。传统后端追求确定性——同样的输入必须得到同样的输出AI 系统的输出天然带概率所以架构的每一层都要为“可能不一样”做好准备。这个思维转变是后面所有具体设计决策的源头。2. 核心思路拆解AI Native 到底新在哪里2.1 从“模型调用”到“模型编排”的认知升级很多人对 AI 架构的第一印象是前端发请求后端拼 Prompt调一次 LLM拿到结果返回。这个链路在 Demo 阶段没问题但一旦业务复杂起来就会崩。原因很简单——真实任务往往需要多步推理、外部工具调用、结果校验和重试单次调用根本覆盖不了。我习惯把 AI Native 架构分成三个层次来理解。最底层是模型层负责推理能力可能是云端 LLM也可能是本地小模型。中间层是编排层这是 AI Native 和传统架构最大的区别所在它管理 Prompt 模板、上下文组装、工具调用、多步流程和状态机。最上层是应用层面向具体业务场景比如客服、写作、数据分析。编排层为什么关键因为它是把“不确定的模型输出”转化为“可用的业务结果”的翻译器。举个实际例子用户问“帮我查一下上个月的销售异常”。传统做法是写死 SQL 查询逻辑AI Native 做法是让模型先理解意图决定调用哪个数据工具拿到数据后再让模型分析异常点最后生成自然语言解释。这中间每一步都可能失败或跑偏编排层的职责就是让这个流程可控、可观测、可恢复。2.2 为什么不能直接套用微服务那一套有微服务经验的同学容易犯一个错把每个 AI 能力拆成一个独立服务然后按传统 RPC 方式互相调用。我早期也这么干过结果发现两个问题。第一AI 服务的响应时间波动极大同一个接口可能 200ms 返回也可能 8 秒才返回传统的超时和重试策略完全不适用。第二AI 服务之间往往需要共享大量上下文拆得太细会导致上下文在服务间反复传递既浪费 token 又容易丢失信息。所以我的建议是AI Native 架构的拆分粒度应该按“任务边界”而不是“技术边界”来定。一个完整的 Agent 任务从意图识别到工具调用到结果生成最好放在同一个编排单元里内部用函数调用而不是网络调用来串联。只有那些真正独立、可复用的能力比如向量检索、文档解析才值得拆成单独服务。2.3 Agent 和普通 LLM 调用的本质区别热词里反复出现 Agent这里必须说清楚。普通 LLM 调用是“一问一答”Agent 是“给一个目标自己决定怎么一步步完成”。区别体现在三个地方Agent 有循环会反复思考-行动-观察直到任务完成Agent 有工具能主动调用外部能力Agent 有记忆会在多轮交互中维护状态。这个区别直接决定了架构复杂度。普通调用只需要一个请求-响应通道Agent 需要一个完整的运行时环境包括任务队列、状态存储、工具注册表和循环控制逻辑。我见过太多项目号称做 Agent实际上只是把 Prompt 写长了一点根本没有循环和工具调用这种本质上还是普通调用。3. 核心模块的详细设计与实操要点3.1 上下文管理AI 系统的“内存”上下文管理是我认为 AI Native 架构里最容易被低估、也最容易出问题的部分。LLM 的上下文窗口有限而真实业务往往需要注入大量信息系统指令、历史对话、检索到的文档、工具返回结果。怎么在有限窗口里塞进最有用的信息直接决定输出质量。我的做法是分层管理上下文。系统层放固定指令和角色设定这部分基本不变。会话层放最近几轮对话按时间倒序保留。检索层放从知识库召回的文档片段按相关度排序。工具层放工具调用结果用完即弃。每一层都有独立的预算比如系统层不超过 500 token检索层不超过 2000 token超出就按优先级裁剪。这里有个实操技巧不要等上下文满了才裁剪而是在组装阶段就做预算控制。我通常会给每层设一个硬上限组装时如果超出就触发压缩或丢弃。压缩可以用模型自己做摘要但要注意摘要本身也消耗 token所以只对长文档做摘要短内容直接截断更划算。注意上下文裁剪一定要保留“最近一轮用户输入”和“系统核心指令”这两部分丢了会导致答非所问或角色崩坏。我踩过一次坑裁剪逻辑把系统指令裁掉了结果模型开始用完全不同的语气回答排查了半天才发现是上下文问题。3.2 工具调用与函数注册的工程细节Agent 能干活靠的是工具。工具调用的工程实现有几个关键点。第一是工具描述的质量模型靠描述来决定调不调、怎么调。描述要写清楚功能、参数含义和适用场景我一般会写三段一句话功能概述、每个参数的说明、一个调用示例。第二是参数校验模型生成的参数经常有格式错误必须在执行前校验校验失败要把错误信息返回给模型让它重试。第三是工具执行的隔离。工具可能访问数据库、调用外部 API、执行代码这些操作必须沙箱化。我的做法是每个工具定义明确的权限边界比如只读工具不能有写操作外部调用必须设超时和熔断。第四是结果格式化工具返回的原始数据往往很长直接塞回上下文会爆窗口需要先做摘要或结构化提取。下面是一个工具注册的简化示例用 Python 字典描述工具元信息tools [ { name: query_sales, description: 查询指定时间范围的销售数据。用于回答销售相关问题时获取原始数据。, parameters: { start_date: 开始日期格式 YYYY-MM-DD, end_date: 结束日期格式 YYYY-MM-DD }, example: query_sales(start_date2024-01-01, end_date2024-01-31) } ]这个结构看起来简单但描述写得好不好直接决定模型调用准确率。我实测下来加了 example 字段之后参数格式错误率能降一半以上。3.3 状态存储与任务持久化Agent 任务可能跑很久中间还要等工具返回所以状态必须持久化。我用过两种方案一种是轻量的用 Redis 存任务状态适合短任务另一种是重量的用数据库存完整执行轨迹适合需要审计和回溯的场景。状态里要存什么至少包括任务 ID、当前步骤、已完成的步骤、每步的输入输出、当前上下文快照、错误信息。这样任务中断后能从断点恢复也方便排查问题。我特别建议存完整的执行轨迹因为 Agent 跑偏的时候只有回看每一步才能找到是哪一步开始错的。实操心得状态存储的写入频率要控制。每步都写数据库会很慢我的做法是内存里维护状态关键节点步骤完成、工具返回、出错才落盘。这样既保证可恢复又不拖慢执行。3.4 失败处理与重试策略AI 系统的失败模式比传统系统多得多模型超时、输出格式错误、工具调用失败、上下文超限、内容被安全策略拦截。每种失败的恢复方式都不一样不能用一个统一的重试逻辑。我的分类处理策略是这样的。超时类失败直接重试但要有退避第一次等 1 秒第二次等 3 秒最多重试 3 次。格式类失败把错误信息返回给模型让它重新生成通常一次就能修正。工具类失败要看工具类型只读工具可以重试写操作要谨慎可能需要人工介入。上下文超限要触发裁剪后重试。安全拦截不能重试要直接返回用户并记录。这里有个容易忽略的点重试要有全局预算。如果每个步骤都允许重试 3 次一个 5 步的任务最多可能跑 15 次模型调用成本和延迟都会失控。我一般设一个任务级的总调用次数上限比如 20 次超过就终止并返回部分结果。4. 完整实操流程从零搭一个最小可用架构4.1 环境准备与技术选型先说选型思路。模型层我建议初期直接用成熟的大模型 API不要一上来就自己部署除非有明确的数据合规要求。编排层可以用现成框架也可以自己写我的经验是如果团队对框架不熟自己写一个轻量编排器反而更可控核心逻辑也就几百行。存储层 Redis 加 PostgreSQL 基本够用Redis 管状态PostgreSQL 管轨迹和审计。开发环境需要准备的东西不多Python 3.10 以上、一个模型 API 的访问凭证、Redis 和 PostgreSQL 实例。我习惯用 Docker Compose 把依赖服务拉起来这样环境一致性好换机器不用重新配。# 启动依赖服务 docker compose up -d redis postgres4.2 编排器的核心循环实现编排器的核心是一个循环组装上下文、调用模型、解析输出、决定下一步。如果模型要求调用工具就执行工具并把结果加回上下文继续循环如果模型给出最终答案就结束。这个循环的关键在于终止条件要明确。我设了三个模型返回最终答案、达到最大步数、达到总调用预算。三个条件任一满足就退出。最大步数我一般设 10 步大部分任务 3 到 5 步就能完成10 步足够覆盖复杂情况又能防止死循环。def run_agent(task, max_steps10, max_calls20): context build_initial_context(task) calls 0 for step in range(max_steps): if calls max_calls: return {status: budget_exceeded, partial: context} response call_llm(context) calls 1 if response.is_final: return {status: done, answer: response.content} if response.tool_call: result execute_tool(response.tool_call) context append_tool_result(context, result) return {status: max_steps, partial: context}这段代码看起来简单但每一行背后都有设计考量。比如calls计数放在循环内而不是循环外是因为一次循环可能触发多次模型调用比如格式修正。partial返回是为了让上层能拿到中间结果不至于全丢。4.3 上下文组装的具体实现上下文组装我按前面说的分层来做。先放系统指令再放最近对话再放检索结果最后放当前用户输入。每层组装时检查 token 预算超了就裁剪。token 估算不用太精确按字符数除以 2 粗略估计就够用中文大概 1 个 token 对应 1.5 到 2 个字符。精确计算反而增加复杂度收益不大。裁剪时优先丢检索层里相关度最低的再丢会话层里最老的系统层和当前输入永远保留。注意不同模型对 token 的计算方式不一样切换模型时预算要重新校准。我有次从一个大窗口模型切到小窗口模型没改预算结果频繁触发裁剪输出质量明显下降。4.4 工具执行与结果回填工具执行要包一层统一的封装处理超时、异常和结果格式化。我的封装逻辑是先校验参数再执行执行时设超时捕获异常最后把结果转成模型能理解的格式。结果回填有个技巧不要把工具的原始返回直接塞回去而是做一层转换。比如数据库查询返回的是 JSON 数组我会转成“查询到 N 条记录前 3 条是……”这样的自然语言描述既省 token 又让模型更容易理解。如果结果确实很长就只回填摘要完整结果存在状态里需要时再取。4.5 可观测性建设AI 系统不埋点就是黑盒。我至少会记录这几类信息每次模型调用的输入输出和耗时、每次工具调用的参数和结果、每个任务的完整执行轨迹、错误和重试记录。这些数据一方面用于排查问题另一方面用于优化 Prompt 和工具描述。日志格式我建议结构化用 JSON 存方便后续分析。关键字段包括任务 ID、步骤序号、事件类型、耗时、token 消耗。有了这些数据你才能回答“这个月模型成本涨了多少”“哪类任务失败率最高”这类问题。5. 常见问题与排查技巧实录5.1 模型输出格式不稳定怎么办这是最高频的问题。模型有时候返回 JSON有时候返回带解释的文字解析经常失败。我的解决办法是双管齐下一是在 Prompt 里明确要求输出格式并给一个示例二是在解析层做容错先尝试直接解析失败就用正则提取再失败就把错误返回给模型让它重新生成。实测下来加了输出示例之后格式正确率能从 70% 提到 90% 以上。剩下 10% 靠解析容错兜底。如果某个场景对格式要求特别严可以考虑用支持结构化输出的模型接口但要注意这类接口通常灵活性会差一些。5.2 Agent 陷入死循环怎么破死循环的表现是模型反复调用同一个工具或者反复输出相似内容。原因通常是任务目标不清晰或者工具返回的结果让模型误以为没完成。排查时先看执行轨迹找到循环开始的那一步看模型当时的上下文是什么。解决手段有三个一是加最大步数限制这是兜底二是在 Prompt 里明确“如果已经获取到足够信息就给出答案”三是在工具返回里加状态标记比如“查询已完成共 N 条结果”让模型知道这步结束了。我一般三个一起用效果比较稳。5.3 成本失控的排查思路成本突然上涨通常是三个原因任务变复杂导致步数增加、上下文变长导致 token 增加、重试变多导致调用次数增加。排查时先看平均步数和平均 token 消耗的变化趋势定位到是哪类任务出了问题。优化手段包括精简系统 Prompt、压缩检索结果、减少不必要的工具调用、给不同任务设不同的预算上限。我还会定期分析哪些 Prompt 片段使用频率低直接删掉积少成多能省不少。5.4 常见问题速查表问题现象可能原因排查方向解决手段输出格式错误Prompt 不明确检查输出要求加示例、加解析容错死循环目标不清晰看执行轨迹加步数限制、加完成标记成本上涨步数或 token 增加看趋势数据精简 Prompt、设预算响应变慢模型或工具慢看耗时分布加超时、换模型、缓存答非所问上下文丢失检查组装逻辑保留系统指令和当前输入工具调用失败参数错误看工具日志加参数校验、返回错误重试5.5 几个容易忽略的坑第一个坑是并发下的状态污染。多个任务共享同一个上下文对象时如果不做隔离会出现 A 任务的数据跑到 B 任务里。我的做法是每个任务独立上下文绝不共享可变对象。第二个坑是模型版本切换。模型升级后行为可能变化Prompt 需要重新调优。我建议固定模型版本升级时先在测试环境跑一轮回归。第三个坑是安全策略误伤。正常业务内容有时会被安全策略拦截导致任务失败。排查时要区分是模型拒绝还是策略拦截前者调 Prompt后者要联系平台确认规则。第四个坑是工具描述过时。工具改了参数但描述没更新模型还在按老方式调用。我习惯把工具描述和工具实现放在同一个文件里改的时候一起改避免遗漏。6. 一些关于架构演进的个人体会这套架构我从第一个版本到现在改了大概七八轮最大的体会是不要一开始就追求完美先让最小闭环跑起来。我最初想设计一个支持多 Agent 协作、动态工具发现、自动 Prompt 优化的复杂系统结果两个月没跑通一个完整任务。后来退回到单 Agent 加固定工具一周就跑通了然后再逐步加功能。另一个体会是可观测性要早做。我早期没埋点出了问题只能靠猜排查一个上下文丢失的 bug 花了两天。后来补上日志类似问题半小时就能定位。埋点这件事早做早省心。还有就是成本意识要贯穿始终。AI 系统的边际成本是随调用量线性增长的不像传统系统那样边际成本趋近于零。所以每一个设计决策都要问一句这会不会增加调用次数或 token 消耗能省的地方一定要省比如缓存常见问题的答案、复用检索结果、精简 Prompt。最后分享一个我常用的调试技巧把每次任务的完整上下文和执行轨迹导出成一个文件出问题时直接看这个文件比在日志里翻要快得多。这个文件也是优化 Prompt 的最好素材看多了自然就知道哪里可以改。
返回列表