1. 为什么我们需要重新审视 AI 应用开发的工程化底座
做 AI 应用开发的人都有一个共同的感受:模型能力越来越强,但把模型能力变成稳定可用的产品,中间的鸿沟反而越来越大。我最早做 AI 应用的时候,一个简单的问答机器人,代码里硬编码几个提示词,调一个模型接口,前端套个对话框就上线了。那时候觉得挺简单。但后来需求开始膨胀——要接多个模型供应商做容灾和成本优化,要让 AI 能调用外部工具查数据、执行操作,要让它能读取企业内部的文档知识库,还要能对复杂的任务做多步骤的规划和编排。这时候你会发现,原来那套“硬编码 + 单接口”的做法完全撑不住了。
XXL-AI 这个项目标题里提到的几个关键词——Agent 编排、多供应商、MCP + SKILL + RAG 扩展、工程化底座——恰好覆盖了当前 AI 应用开发从“玩具”走向“产品”过程中最核心的四个工程化命题。这篇文章我会围绕这四个方向,把每个环节的设计思路、技术选型理由、实操要点和踩坑经验都拆开来讲。不管你是刚接触 AI 应用开发的新手,还是已经在做企业级 AI 平台的工程师,应该都能从中找到可以直接参考的东西。
先说清楚这篇文章适合谁看。如果你只是想让模型帮你写写文案、做做翻译,那直接用现成的对话产品就够了,不需要看这些。但如果你需要把 AI 能力集成到自己的业务系统里,需要让 AI 按照你定义的流程去执行任务,需要对接多个模型供应商来保证可用性和成本可控,需要让 AI 能够访问你的私有数据和工具——那这篇文章讨论的内容就是你必须面对的。我会尽量用通俗的方式解释每个概念,同时给出可以直接落地的方案和参数。
2. Agent 编排:从单次对话到多步骤任务执行的核心设计
2.1 Agent 编排到底在编排什么
很多人第一次听到“Agent 编排”这个词会觉得抽象。我用一个实际场景来解释。假设你要做一个“自动生成周报”的 AI 应用,流程是这样的:先从项目管理工具拉取本周的任务完成情况,然后从代码仓库拉取提交记录,接着把这两部分数据交给模型做总结归纳,最后按照固定格式生成周报并发送到指定渠道。这个流程里有数据获取、有模型调用、有格式处理、有外部动作,每一步的输入输出都不一样,有些步骤还需要根据上一步的结果来决定下一步怎么做。
Agent 编排要解决的就是这个问题:把一个复杂任务拆解成多个可执行的步骤,定义每个步骤的输入输出、执行条件和异常处理,然后让系统按照定义的流程自动调度执行。它本质上是一个工作流引擎,只不过执行单元从传统的函数调用变成了 AI Agent。
XXL-AI 在 Agent 编排上的设计思路,我理解是走了一条“声明式编排 + 运行时调度”的路线。声明式编排的意思是,你用配置文件或者可视化界面定义好流程的节点和连线,而不是在代码里写一大堆 if-else 和回调。这样做的好处是流程变更不需要改代码、不需要重新部署,运营人员也能参与流程调整。运行时调度则负责处理节点的执行顺序、并发控制、超时重试、状态持久化这些事情。
2.2 编排引擎的核心组件拆解
一个完整的 Agent 编排引擎,我认为至少需要包含以下几个核心组件,这也是我在实际项目中验证过的必要模块。
流程定义层负责描述整个任务的执行逻辑。常见的做法是用有向无环图(DAG)来表示,每个节点是一个执行单元,边表示依赖关系。但 Agent 场景比传统工作流更复杂的地方在于,有些节点需要根据运行时结果动态决定后续走向,所以还需要支持条件分支和循环。XXL-AI 如果支持条件分支节点和循环节点,那就能覆盖绝大多数业务场景了。
节点执行器是真正干活的部分。每个节点类型对应一个执行器,比如 LLM 调用节点、工具调用节点、知识库检索节点、代码执行节点、条件判断节点等。执行器需要处理输入参数的解析、执行过程的超时控制、执行结果的格式化输出。这里有个容易忽略的细节:节点的输入参数往往需要引用上游节点的输出,所以需要一套变量引用机制,比如用{{node_id.output.field}}这样的模板语法来动态取值。
状态管理器负责记录每个流程实例的执行状态。这个组件在单机环境下可能不太起眼,但在分布式部署或者需要支持断点续跑的场景下就非常关键。我踩过的一个坑是:一个流程执行到一半,某个节点因为外部服务超时失败了,如果没有状态持久化,整个流程就得从头再来,前面已经成功执行的节点白白浪费了时间和 token 成本。所以状态管理器需要把每个节点的执行状态、输入输出、执行时间都持久化下来,支持从失败节点重新开始执行。
调度器决定节点什么时候执行、以什么顺序执行。最简单的策略是按拓扑排序串行执行,但实际业务中往往需要并行执行没有依赖关系的节点来缩短总耗时。比如前面说的周报场景,拉取任务完成情况和拉取代码提交记录这两个节点没有依赖关系,完全可以并行执行。调度器还需要处理并发限制,避免同时调用太多次模型接口导致触发限流。
2.3 多 Agent 协作的编排模式
单个 Agent 能处理的任务复杂度是有限的。当任务需要多个不同角色的 Agent 协作完成时,就需要更复杂的编排模式。我在实际项目中用过并且觉得比较实用的模式有三种。
流水线模式是最直观的,多个 Agent 按照固定顺序依次处理,前一个的输出是后一个的输入。比如“需求分析 Agent → 方案设计 Agent → 代码生成 Agent → 代码审查 Agent”这样一条流水线。这种模式实现简单,适合步骤明确、不需要回头修改的场景。
路由模式是先由一个“调度 Agent”判断任务类型,然后分发给对应的“专家 Agent”处理。比如用户提问后,先判断这是技术问题、商务问题还是售后问题,然后分别交给对应的 Agent 回答。这种模式的关键在于路由判断的准确性,通常需要给调度 Agent 提供清晰的分类标准和少量示例。
辩论模式是让多个 Agent 对同一个问题给出各自的答案,然后由一个“裁判 Agent”综合评估后给出最终结果。这种模式在需要高质量输出的场景下很有用,比如方案评审、内容创作。代价是 token 消耗会成倍增加,所以需要权衡质量和成本。
实操心得:多 Agent 协作不一定比单 Agent 效果好。我做过对比测试,在任务复杂度不高的情况下,给单 Agent 一个清晰的系统提示词,效果往往比拆成多个 Agent 更好,而且成本和延迟都更低。只有当任务确实需要不同领域的专业知识,或者需要多轮迭代优化时,多 Agent 协作才有明显优势。
3. 多供应商接入:不让任何一个模型成为单点
3.1 为什么必须做多供应商适配
只接一个模型供应商,在开发测试阶段没什么问题,但到了生产环境就会面临几个现实问题。第一是可用性风险,任何服务都可能出现临时故障或限流,如果只有一个供应商,对方出问题你的产品就挂了。第二是成本问题,不同供应商在不同任务上的性价比差异很大,简单的分类任务用便宜的小模型就够了,复杂的推理任务才需要上大模型。第三是能力差异,有些模型擅长中文,有些擅长代码,有些在函数调用上更稳定,需要根据任务特点选择最合适的模型。
XXL-AI 把多供应商作为核心能力之一,我认为这是非常务实的设计决策。但多供应商适配不是简单地封装几个 API 调用就完事了,里面有很多细节需要处理。
3.2 供应商适配层的设计要点
统一接口抽象是第一层要做的事情。不同供应商的 API 参数格式、返回结构、错误码都不一样。适配层需要定义一套统一的内部接口,把各家 API 的差异屏蔽掉。比如统一用model、messages、temperature、max_tokens这些参数,返回统一用content、usage、finish_reason这些字段。这样上层业务代码就不需要关心底层用的是哪家模型。
能力声明机制是第二层。不是所有模型都支持函数调用、JSON 模式、流式输出、图片输入这些能力。适配层需要为每个模型维护一份能力清单,上层在编排流程时可以根据需求选择支持对应能力的模型。比如一个节点需要模型返回结构化的 JSON,那就只能选择支持 JSON 模式的模型。
参数映射与默认值是第三层。同一个参数在不同供应商那里的取值范围和默认值可能不同。比如 temperature 有些供应商范围是 0 到 1,有些是 0 到 2。适配层需要做归一化处理,同时为每个模型设置合理的默认参数。我一般会把默认 temperature 设为 0.7 用于创意类任务,设为 0.1 用于需要稳定输出的任务。
错误处理与重试是第四层。不同供应商的错误码体系不同,适配层需要把常见的错误类型统一映射,比如限流、超时、认证失败、内容审核不通过等。然后针对不同错误类型采取不同的处理策略:限流就退避重试,认证失败就告警,内容审核不通过就返回友好提示。
3.3 模型路由与降级策略
有了多供应商适配层之后,下一步就是决定每次请求用哪个模型。最简单的做法是在流程定义时写死模型名称,但这样不够灵活。更好的做法是实现一个模型路由层,根据任务类型、成本预算、当前各供应商的健康状态来动态选择。
我常用的路由策略是这样的:首先根据任务类型筛选出能力匹配的模型列表,然后按照成本从低到高排序,优先选择成本低的模型。如果低成本的模型调用失败或者返回质量不达标,再自动升级到更高成本的模型。这个“质量不达标”的判断可以通过一些启发式规则来实现,比如返回内容为空、返回格式不符合要求、包含明显的错误信息等。
降级策略也很重要。当某个供应商整体不可用时,路由层应该能自动把流量切到备用供应商。这里需要注意的是,不同模型的输出风格和能力有差异,切换后可能会影响用户体验的一致性。所以降级策略最好配合灰度发布,先切一小部分流量观察效果,确认没问题再全量切换。
| 策略类型 | 适用场景 | 实现复杂度 | 效果 |
|---|---|---|---|
| 固定模型 | 开发测试、单一任务 | 低 | 简单但不够灵活 |
| 按任务路由 | 多任务类型混合 | 中 | 性价比最优 |
| 按成本路由 | 成本敏感型业务 | 中 | 成本可控 |
| 自动降级 | 高可用要求 | 高 | 可用性最好 |
注意事项:多供应商接入后,日志和监控必须跟上。每个请求都要记录用了哪个供应商、哪个模型、耗时多少、token 消耗多少、是否成功。没有这些数据,你根本不知道钱花在哪里了,也不知道哪个供应商更稳定。我一般会用结构化的日志格式,方便后续做聚合分析。
4. MCP + SKILL + RAG:三种扩展能力的定位与配合
4.1 三种扩展能力的本质区别
MCP、SKILL、RAG 这三个词经常被放在一起讨论,但它们解决的是完全不同的问题。我用一个类比来解释:假设 AI 是一个新入职的员工,RAG 是给他一本公司内部手册让他随时查阅,SKILL 是教他一项具体的操作技能比如使用某个软件,MCP 则是给他一套标准化的工具接口让他能操作公司的各种系统。
RAG 的核心是知识获取。当模型本身的知识不够用,或者需要访问私有数据时,RAG 通过检索的方式把相关文档片段注入到模型的上下文中,让模型基于这些片段来回答问题。它的关键指标是检索的准确率和召回率,以及注入上下文的质量。
SKILL 的核心是能力封装。它把一组相关的提示词、工具调用、处理逻辑打包成一个可复用的技能单元。比如“生成周报”这个技能,内部可能包含了数据获取、格式化、模型调用等多个步骤,但对外只暴露一个简单的调用接口。SKILL 的价值在于复用和标准化,避免每个流程都从头写一遍。
MCP 的核心是工具标准化。它定义了一套标准的协议,让不同的工具提供方按照统一的接口来暴露自己的能力,AI 应用只需要按照协议去发现和调用工具,不需要为每个工具单独写适配代码。这有点像 USB 接口标准化的意义,以前每个设备都有自己的接口,现在统一成 USB 之后,任何设备插上都能用。
4.2 RAG 的工程化落地要点
RAG 看起来简单——把文档切块、向量化、存到向量数据库、查询时检索最相似的块——但实际做起来坑非常多。我见过太多项目在 demo 阶段效果惊艳,上线后用户一用就发现答非所问。
文档切块策略是第一个关键决策。切得太碎,单个块信息不完整,模型无法理解上下文;切得太大,检索精度下降,而且会浪费上下文窗口。我的经验是,对于结构化文档(比如产品手册、规章制度),按照标题层级来切,每个小节作为一个块,块大小控制在 300 到 800 字之间。对于非结构化文档(比如聊天记录、会议纪要),按照语义完整性来切,可以用滑动窗口加重叠的方式,窗口大小 500 字,重叠 100 字。
向量化模型选择是第二个关键决策。不同向量化模型在不同语言和领域上的表现差异很大。中文场景下,我测试过多个开源和商业的向量化模型,发现对于通用中文文本,大部分主流模型效果差距不大,但对于专业领域文本(比如法律、医疗),领域微调过的模型明显更好。如果预算有限,可以用开源模型自己部署;如果追求效果,商业 API 通常更省心。
检索策略优化是第三个关键决策。最简单的做法是纯向量相似度检索,但实际效果往往不够好。我一般会用混合检索:向量检索加关键词检索,两路结果合并后做重排序。关键词检索能弥补向量检索在精确匹配上的不足,比如用户问某个具体的产品型号,向量检索可能返回一堆语义相似但型号不对的文档,关键词检索就能精准命中。重排序则用一个小模型对候选结果做精细打分,把最相关的排到前面。
上下文组装是第四个关键决策。检索到相关文档块之后,怎么把它们组装成模型的输入也有讲究。我通常会在每个文档块前面加上来源标注,比如“来自《产品手册》第三章”,这样模型在回答时可以引用来源,用户也能验证信息的可靠性。另外,如果检索到的多个块之间有内容重叠或矛盾,需要在组装时做去重和冲突处理。
实操心得:RAG 的效果评估不能只看最终回答的质量,要拆开看检索质量和生成质量。我一般会准备一批测试问题,每个问题标注好应该检索到哪些文档块,然后分别计算检索的召回率和准确率。如果检索阶段就有问题,那再怎么优化生成阶段也没用。检索召回率至少要达到 80% 以上,RAG 的整体效果才有保障。
4.3 SKILL 的设计原则与复用机制
SKILL 的设计目标是把常用的 AI 能力封装成可复用的模块。我在设计 SKILL 时遵循几个原则。
单一职责:一个 SKILL 只做一件事,并且做好。比如“文本摘要”是一个 SKILL,“情感分析”是另一个 SKILL,不要把多个功能塞进一个 SKILL 里。这样每个 SKILL 的提示词可以针对特定任务优化,效果更好,也更容易维护。
接口稳定:SKILL 的输入输出接口一旦定义好,就不要轻易改动。因为可能有多个流程在调用同一个 SKILL,接口变了所有调用方都得跟着改。我一般会用版本号来管理 SKILL 的接口,新版本用新的版本号,旧版本继续保留一段时间,给调用方迁移的时间。
参数可配置:SKILL 内部的一些行为应该做成可配置的参数,而不是写死在代码里。比如摘要 SKILL 可以配置摘要长度、语言风格、是否保留关键数据等。这样同一个 SKILL 可以适应不同的使用场景。
错误可处理:SKILL 执行失败时应该返回明确的错误信息,而不是抛一个笼统的异常。调用方需要根据错误类型决定是重试、降级还是直接失败。比如模型调用超时和输入格式错误,处理方式是完全不同的。
SKILL 的复用机制也很重要。我一般会建一个 SKILL 注册中心,所有 SKILL 都在这里注册,包含名称、描述、输入输出 schema、版本号等信息。流程编排时可以从注册中心查找和引用 SKILL。这样当 SKILL 更新时,所有引用它的流程都能自动受益。
4.4 MCP 协议在工具集成中的实际价值
MCP 解决的是工具集成的标准化问题。在没有 MCP 之前,每接入一个新工具,都要写一套适配代码:定义工具的描述、参数的 schema、调用的逻辑、返回的处理。工具多了之后,这些适配代码散落在各处,维护成本很高。
MCP 的思路是定义一个标准协议,工具提供方按照协议来实现服务端,AI 应用按照协议来实现客户端。客户端启动时从服务端拉取工具列表和参数定义,运行时按照协议格式发起调用。这样接入新工具就不需要写代码了,只需要配置服务端地址就行。
在实际项目中,MCP 的价值主要体现在两个方面。一是降低集成成本,特别是当需要接入大量外部工具时,标准化的收益非常明显。二是促进工具生态的形成,工具提供方只需要实现一次 MCP 服务端,就能被所有支持 MCP 的 AI 应用使用,不需要为每个应用单独适配。
不过 MCP 也不是银弹。我实际使用中遇到的主要问题是工具描述的准确性和参数校验的严格性。如果工具描述写得模糊,模型就不知道什么时候该调用这个工具;如果参数校验不严格,模型传了错误的参数类型,调用就会失败。所以实现 MCP 服务端时,工具描述要尽量清晰具体,参数 schema 要尽量严格。
5. 工程化底座:让 AI 应用从能跑到好用
5.1 可观测性体系的搭建
AI 应用和传统应用最大的区别之一就是不确定性。同样的输入,模型可能给出不同的输出;同样的流程,执行时间可能相差很大;同样的提示词,不同版本的模型效果可能完全不同。这种不确定性使得可观测性变得尤为重要。
我在搭建 AI 应用的可观测性体系时,主要关注四个维度。调用链路记录每个请求经过了哪些节点、每个节点的输入输出是什么、耗时多少。这需要在整个流程中传递一个 trace_id,每个节点执行时都带上这个 id 记录日志。Token 消耗记录每个请求消耗了多少输入 token 和输出 token,按供应商、按模型、按业务模块分别统计。这是成本控制的基础数据。质量指标记录模型输出的质量评估结果,比如是否为空、是否符合格式要求、是否被用户采纳等。异常告警在错误率、延迟、成本超过阈值时触发告警,让问题能被及时发现。
这些数据收集起来之后,还需要有地方看。我一般会用 Grafana 做可视化面板,把关键指标做成图表,方便随时查看。另外,详细的调用日志要能按 trace_id 搜索,方便排查具体问题。
5.2 提示词管理与版本控制
提示词是 AI 应用的核心资产之一,但很多团队对提示词的管理非常随意,直接写在代码里,改了就提交,没有版本记录,没有回滚机制。这在早期可能没问题,但当提示词越来越多、越来越复杂时,就会变成一场灾难。
我的做法是把提示词从代码中抽离出来,单独管理。每个提示词有唯一的标识符、版本号、内容、适用模型、创建时间、变更说明。提示词变更时创建新版本,旧版本保留。流程编排时引用提示词的标识符和版本号,这样可以精确控制每个流程用的是哪个版本的提示词。
提示词的测试也很重要。每次修改提示词后,应该用一组标准测试用例来验证效果,确保修改没有引入回归问题。我一般会维护一个测试集,包含各种典型输入和期望输出,修改提示词后自动跑一遍测试,对比新旧版本的效果差异。
5.3 缓存与成本优化策略
AI 应用的成本大头在模型调用上,所以成本优化是工程化底座必须考虑的问题。缓存是最直接有效的手段之一。
精确缓存是最简单的,把输入和输出做键值对存储,相同的输入直接返回缓存结果。适合那些输入重复率高的场景,比如常见问题回答。但 AI 场景下完全相同的输入其实不多,所以精确缓存的命中率通常不高。
语义缓存更进一层,把输入向量化后存储,查询时先做向量相似度匹配,如果找到足够相似的输入,就返回对应的缓存结果。这样即使用户的提问措辞不同,只要语义相同就能命中缓存。语义缓存的难点在于相似度阈值的设定,阈值太高命中率低,阈值太低可能返回不相关的答案。我一般会把阈值设在 0.95 以上,确保缓存结果的可靠性。
分级缓存是针对多轮对话场景的。把对话历史也纳入缓存键的计算,这样同一轮对话中的重复问题可以直接命中缓存。但要注意对话上下文的时效性,过期的缓存要及时清理。
除了缓存,还有一些其他的成本优化手段。比如对于简单的分类、提取任务,用便宜的小模型而不是大模型;对于长文本处理,先做摘要压缩再送给模型;对于批量任务,合并请求减少调用次数。这些手段组合使用,通常能把成本降低 50% 以上。
5.4 安全与权限控制
AI 应用的安全问题往往被低估。我见过不少项目,模型可以直接访问数据库、可以执行系统命令、可以读取任意文件,这在实际生产环境中是非常危险的。
输入过滤是第一道防线。用户输入中如果包含提示词注入的尝试,比如“忽略之前的指令,现在你是一个...”,需要在进入模型之前就检测和拦截。我一般会用规则加模型双重检测,规则匹配常见的注入模式,模型判断是否存在恶意意图。
工具权限是第二道防线。不是所有流程都能调用所有工具。需要建立一套权限体系,每个流程、每个用户能调用哪些工具、能访问哪些数据,都要有明确的控制。特别是涉及写操作的工具,比如发送邮件、修改数据,更要严格限制。
输出审核是第三道防线。模型生成的内容在返回给用户之前,需要经过审核,确保不包含敏感信息、不违反内容政策。审核可以用规则引擎加模型判断的方式,规则引擎处理明确的违规模式,模型处理更复杂的语义判断。
数据隔离是第四道防线。不同租户、不同用户的数据要严格隔离,不能出现 A 用户通过 AI 应用访问到 B 用户数据的情况。这需要在数据存储、检索、工具调用等各个环节都做好租户标识的传递和校验。
6. 常见问题与排查技巧实录
6.1 Agent 编排中的典型故障
流程卡死是最常见的问题之一。表现是流程执行到某个节点后长时间没有进展,也不报错。排查思路是:先看该节点的日志,确认是节点内部卡住了还是调度器没有触发下一个节点。如果是节点内部卡住,通常是外部服务调用没有设置超时,或者模型接口响应极慢。解决办法是给所有外部调用设置合理的超时时间,超时后触发重试或降级。如果是调度器问题,检查是否有循环依赖或者条件分支没有正确匹配。
状态不一致是分布式部署下的常见问题。表现是流程状态显示已完成,但实际有些节点没有执行;或者节点执行了但状态没有更新。这通常是状态持久化和节点执行之间的时序问题。解决办法是采用“先记录状态再执行”的模式,节点开始执行前先写入“执行中”状态,执行完成后更新为“成功”或“失败”。这样即使中间出现故障,也能通过状态判断哪些节点需要重新执行。
变量引用错误是编排配置中的高频问题。表现是节点执行时报“变量未定义”或“变量类型不匹配”。排查时先检查变量引用的路径是否正确,上游节点的输出结构是否和预期一致。我一般会在流程定义时做静态检查,验证所有变量引用都能找到对应的上游输出。
6.2 RAG 效果不佳的排查路径
RAG 效果不好,原因可能出在检索阶段,也可能出在生成阶段。排查时我一般按照以下顺序逐步检查。
先看检索结果。把用户问题和检索到的文档块都打印出来,人工判断这些文档块是否真的和问题相关。如果检索结果就不相关,那问题在检索阶段,需要优化切块策略、向量化模型或检索算法。如果检索结果相关但模型回答不好,那问题在生成阶段,需要优化提示词或调整上下文组装方式。
检索阶段的问题又可以分为几种。如果检索结果完全跑偏,可能是向量化模型不适合当前领域,需要换模型或做领域微调。如果检索结果部分相关但不够精准,可能是切块粒度不合适,需要调整块大小或重叠策略。如果检索结果遗漏了关键文档,可能是召回率不够,需要增加检索数量或改用混合检索。
生成阶段的问题通常是提示词没有说清楚要求。比如模型不知道应该基于检索到的内容回答,还是可以用自己的知识回答;不知道如果检索内容中没有答案应该怎么处理;不知道回答的格式要求。这些都需要在提示词中明确说明。
6.3 多供应商切换的注意事项
多供应商切换时最容易出问题的地方是输出格式的差异。不同模型对同一个提示词的响应格式可能不同,比如有的返回 JSON 有的返回 Markdown,有的会在 JSON 外面包一层代码块标记。如果上层代码按照固定格式解析,切换供应商后就可能解析失败。
解决办法是在适配层做输出格式的归一化处理。不管底层模型返回什么格式,适配层都统一转换成内部约定的格式。对于 JSON 输出,可以用容错的解析器,先尝试直接解析,失败后尝试提取代码块中的内容再解析,再失败就尝试用正则提取关键字段。
另一个需要注意的是不同模型的“性格”差异。有些模型倾向于给出详细冗长的回答,有些则简洁直接。切换供应商后,即使功能正常,用户体验也可能有明显变化。所以切换前最好做 A/B 测试,对比两个模型的输出质量、延迟、成本,确认切换后整体效果可接受再全量切换。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 流程执行超时 | 外部调用无超时设置 | 查看节点日志耗时 | 设置合理超时和重试 |
| 检索结果不相关 | 切块粒度不当 | 人工检查检索结果 | 调整块大小和重叠 |
| 切换模型后解析失败 | 输出格式差异 | 对比不同模型输出 | 适配层做格式归一化 |
| 成本突然升高 | 缓存失效或路由异常 | 查看 token 消耗统计 | 检查缓存和路由配置 |
| 并发请求被限流 | 未做并发控制 | 查看供应商错误码 | 增加队列和退避重试 |
6.4 性能优化的实操经验
AI 应用的性能瓶颈通常在模型调用上,所以优化要从减少模型调用次数和缩短单次调用时间两个方向入手。
减少调用次数的办法包括:用缓存避免重复调用,用批量处理合并多次调用,用规则引擎处理简单判断避免调用模型,用更小的模型处理简单任务。我做过一个统计,在一个客服问答场景中,通过缓存和规则引擎,模型调用次数减少了 60% 以上。
缩短单次调用时间的办法包括:用流式输出让用户更早看到部分结果,用更快的模型处理对延迟敏感的任务,优化提示词长度减少输入 token 数,用并发调用处理没有依赖关系的多个模型请求。流式输出对用户体验的提升特别明显,虽然总耗时没变,但用户感知到的等待时间大幅缩短。
实操心得:性能优化不要凭感觉,一定要有数据支撑。我一般会先做 profiling,找出真正的瓶颈在哪里,然后针对瓶颈做优化。很多时候你以为的瓶颈(比如模型推理速度)并不是真正的瓶颈,真正的瓶颈可能是网络传输、数据库查询或者序列化反序列化。没有数据就优化,很容易白费力气。
7. 一些关于工程化底座的个人体会
做 AI 应用开发这几年,我最大的体会是:模型能力决定了应用的上限,但工程化水平决定了应用的下限。一个工程化做得好的 AI 应用,即使底层模型不是最强的,也能给用户提供稳定可靠的体验;而一个工程化做得差的应用,即使接了最强的模型,也会因为各种超时、错误、不一致而让用户失去信心。
XXL-AI 这个项目把工程化底座作为核心定位之一,我认为方向是对的。Agent 编排、多供应商、MCP + SKILL + RAG 扩展,这些能力最终都要落到一个稳定、可观测、可控制的工程底座上,才能真正发挥价值。我在实际项目中搭建类似体系时,最大的感受是要克制“什么都自己造”的冲动。编排引擎、向量数据库、监控系统这些基础设施,如果有成熟的开源方案或者云服务,优先用现成的,把精力集中在业务逻辑和提示词优化上,那才是真正产生差异化的地方。
另外一点体会是关于迭代节奏的。AI 应用的效果优化是一个持续的过程,不可能一次做到完美。我的做法是先搭好工程底座,保证基本流程能跑通、能观测、能控制,然后小步快跑地迭代提示词和检索策略。每次迭代都基于数据做决策,而不是凭感觉调整。这样虽然起步慢一点,但后续的迭代会越来越快,因为你有数据支撑,知道往哪个方向优化最有效。