1. 为什么我会去折腾一个 AI 应用开发平台
先说结论:我最初并不是想造一个平台,而是被项目里散落一地的 Agent 逻辑逼到没办法了。去年下半年开始,团队里陆续接了四五个 AI 相关的需求,有做知识库问答的,有做流程自动化的,还有做多轮对话客服的。每个需求单拎出来都不算复杂,但问题出在它们各自为政——A 项目用一套提示词管理,B 项目自己写了个工具调用循环,C 项目把检索逻辑硬编码在业务代码里。等到要改一个模型供应商、换一个向量库、加一个外部工具的时候,我发现自己要在四五个代码库里重复同样的改动。
这就是我动手做XXL-AI的直接动机。它本质上是一个AI 应用开发平台,核心能力围绕四块展开:Agent 编排、多供应商接入、MCP + SKILL + RAG 三种扩展方式,以及一套能扛住真实业务的工程化底座。说白了,它想解决的问题是:让 AI 应用的开发从"每个项目重新造轮子"变成"在统一底座上拼装能力"。
这篇文章适合谁看?如果你正在做 AI 应用,被多模型切换、工具调用、知识库检索这些事反复折磨,或者你团队里已经有多个 AI 项目开始出现重复建设,那这篇内容应该能帮到你。我会把整个平台的设计思路、核心模块的实现要点、踩过的坑,以及可以直接抄的配置方案都摊开讲。哪怕你只是想了解 Agent 编排和 RAG 到底该怎么落地,也能从里面挑到有用的部分。
需要提前说明的是,下面涉及的具体实现细节,有一部分是基于我实际项目的做法,有一部分是基于行业常见实践的合理补充,我会尽量标注清楚哪些是实测结论、哪些是推荐方案,方便你按自己的场景取舍。
2. 平台整体设计与核心思路拆解
2.1 为什么不做成又一个"全家桶框架"
市面上 AI 应用框架已经不少了,我一开始也想过直接拿现成的用。但试了一圈之后发现两个问题:一是很多框架把"编排"和"运行时"绑得太死,你想换个模型供应商,得改一堆配置甚至改代码;二是扩展机制要么太弱(只能加提示词),要么太重(要写完整的插件类)。XXL-AI 的设计原则就是反着来——编排归编排,运行时归运行时,扩展归扩展,三层解耦。
具体来说,平台分成四个层次。最底层是工程化底座,负责配置管理、日志追踪、限流熔断、密钥托管这些和 AI 无关但任何生产系统都躲不掉的事。往上一层是供应商抽象层,把 OpenAI、Claude、通义、DeepSeek、本地 Ollama 这些模型的调用差异抹平,统一成一套接口。再往上是能力扩展层,也就是 MCP、SKILL、RAG 这三种扩展方式,分别对应"接外部工具""注入领域能力""挂载知识库"。最上面才是Agent 编排层,用可视化的方式把模型、工具、知识库串成一条可执行的流程。
这样分层的好处很直接:换模型只动第二层,加工具只动第三层,改业务流程只动第四层。我实测下来,把一个原本写死在代码里的客服 Agent 迁移到平台上,只花了半天,其中大部分时间还是在整理原来的提示词。
2.2 三种扩展方式到底该怎么选
这是我在实际项目里被问得最多的问题:MCP、SKILL、RAG 到底有什么区别,什么时候用哪个?很多人一开始会混淆,觉得都是"给 AI 加能力",其实它们的定位完全不同。
MCP解决的是"AI 怎么和外部系统对话"的问题。它是一个协议层的标准,让 Agent 能以统一的方式调用外部工具或服务,比如查数据库、调 API、操作文件系统。你可以把它理解成 AI 世界的 USB 接口——只要对方支持这个协议,插上就能用,不用为每个工具单独写适配代码。
SKILL解决的是"AI 怎么掌握一类特定任务的做法"的问题。它更像是一份封装好的操作手册加执行逻辑,比如"如何做代码审查""如何生成周报""如何做竞品分析"。一个 SKILL 里通常包含提示词模板、few-shot 示例、可选的工具调用链,以及输出格式约束。它不依赖外部系统,纯粹是能力的沉淀和复用。
RAG解决的是"AI 怎么获取它训练时没见过的知识"的问题。通过检索增强,把企业文档、产品手册、历史工单这些私有知识挂载进来,让模型在回答时能引用真实资料,而不是靠记忆瞎编。
我一般给团队的建议是:要接外部系统用 MCP,要固化一类任务用 SKILL,要注入私有知识用 RAG。三者可以叠加使用,比如一个"技术支持 Agent"可以同时挂 RAG 知识库、调用 MCP 查工单系统、再用 SKILL 约束回复格式。
2.3 多供应商接入的取舍逻辑
多供应商这件事,很多人第一反应是"我接一个不就够了"。但真实业务里,你迟早会遇到这几种情况:某个模型突然限流了要临时切换、不同任务用不同模型更省钱、客户要求数据必须走本地模型、某个模型对中文支持更好。所以供应商抽象层不是"锦上添花",而是"迟早要用"。
我的做法是定义一个统一的ModelProvider接口,包含chat、embedding、stream三个核心方法,每个具体供应商实现这个接口。上层编排只依赖接口,不关心底层是谁。配置上用一个 provider 注册表,支持运行时热切换。这里有个细节值得说:不同供应商的 token 计算方式、上下文窗口、函数调用格式都不一样,我在抽象层里加了一层"能力描述",让编排层能知道当前模型支持什么、不支持什么,避免调用到不支持的能力。
3. 核心模块的细节解析与实操要点
3.1 Agent 编排:从"写代码"到"画流程"
Agent 编排是平台的门面,也是最容易做砸的地方。我见过太多编排工具,要么抽象过度导致简单事情复杂化,要么太底层导致还是要写大量代码。XXL-AI 的编排设计遵循一个原则:常见场景零代码,复杂场景可插代码。
编排的基本单元是"节点"。一个典型的 Agent 流程包含这几类节点:输入节点(接收用户请求)、模型节点(调用 LLM)、工具节点(调用 MCP 或内置工具)、检索节点(查 RAG 知识库)、条件节点(根据结果分支)、输出节点(返回结果)。节点之间用连线表示数据流向,每个节点的输出可以作为下游节点的输入变量。
这里的关键设计是变量系统。每个节点都有输入变量和输出变量,变量名在整个流程内唯一。比如模型节点的输出可以命名为answer,下游的条件节点就可以用answer.contains("无法回答")来判断是否要走兜底分支。变量系统让流程具备了真正的数据流转能力,而不是简单的线性拼接。
实操中我总结了几条经验。第一,节点粒度不要太细。有人喜欢把一个提示词拆成好几个节点,结果流程图画得像蜘蛛网,维护起来很痛苦。我的建议是:一个节点做一件完整的事,比如"生成初稿""审核内容""格式化输出"各是一个节点,而不是把"拼接提示词""调用模型""解析结果"拆成三个。第二,善用子流程。如果一个流程被多个主流程复用,就把它抽成子流程,通过参数传递数据。第三,给每个节点加超时和重试。模型调用偶尔会卡住,没有超时保护的流程会一直挂着。
3.2 多供应商接入:统一接口背后的坑
多供应商接入听起来简单,实际做起来坑不少。我踩过的第一个坑是流式输出的差异。OpenAI 的流式返回是 SSE 格式,每个 chunk 是一个 JSON;有些供应商返回的是纯文本流;还有些在流式模式下不支持函数调用。我的处理方式是在抽象层做归一化,统一转成StreamChunk对象,包含delta、finishReason、toolCalls三个字段,上层不用关心底层格式。
第二个坑是函数调用的格式差异。OpenAI 用tools参数,Claude 用tools但结构不同,有些国产模型用的是自己的一套。我在抽象层定义了一套标准的工具描述格式,然后在每个 provider 里做转换。这里要注意:不是所有模型都支持函数调用,编排层需要根据模型能力描述来决定是否启用工具节点。
第三个坑是错误处理和重试。不同供应商的错误码、限流策略、重试建议都不一样。我统一封装了一个ProviderError,包含type(限流/超时/参数错误/服务异常)、retryable(是否可重试)、retryAfter(建议重试间隔)三个字段。编排层根据这些信息决定是重试、降级到备用模型,还是直接报错。
配置上,我用一个 YAML 文件管理所有供应商:
providers: - name: openai-gpt4 type: openai model: gpt-4-turbo apiKey: ${OPENAI_API_KEY} baseUrl: https://api.openai.com/v1 capabilities: [chat, stream, function_call, vision] limits: maxTokens: 128000 rpm: 500 - name: local-ollama type: ollama model: qwen2.5:14b baseUrl: http://localhost:11434 capabilities: [chat, stream] limits: maxTokens: 32000 rpm: 60这种配置方式的好处是,新增一个供应商只需要加一段配置,不用改代码。密钥用环境变量注入,避免硬编码。
3.3 MCP 扩展:让 Agent 真正能"动手"
MCP 是这两年 AI 圈讨论很多的一个协议标准,它的核心价值是让 AI 应用能以统一方式接入外部工具和数据源。在 XXL-AI 里,MCP 是工具节点的底层支撑。
我实现 MCP 接入时,重点解决了三个问题。第一是工具发现。MCP 服务端会暴露一个工具列表,包含每个工具的名称、描述、参数 schema。平台启动时拉取这个列表,注册到工具注册表里。编排时,模型节点可以根据工具描述自动决定调用哪个工具,这就是所谓的 function calling。
第二是参数校验和转换。模型生成的工具调用参数是 JSON,但不一定符合 schema。我在调用前做一层校验,参数不对就返回错误让模型重新生成。这里有个实用技巧:在工具描述里写清楚参数示例,能显著降低模型生成错误参数的概率。
第三是结果处理。工具返回的结果可能是任意结构,需要转成模型能理解的文本。我的做法是保留原始 JSON,同时生成一段自然语言摘要,一起塞回给模型。这样模型既能理解语义,又能在需要时引用具体字段。
实操中我遇到一个典型问题:MCP 服务连接失败时,整个 Agent 流程会卡住。解决办法是给每个 MCP 连接加健康检查和超时,连接不可用时工具节点直接返回"工具暂不可用",让模型走兜底逻辑,而不是让整个流程挂掉。
3.4 SKILL 扩展:把经验沉淀成可复用的能力
SKILL 是我个人最喜欢的一个设计,因为它解决了一个很实际的问题:团队里那些"会做某件事"的经验,怎么变成所有人都能用的能力。
一个 SKILL 的结构包含四部分:元信息(名称、描述、适用场景)、提示词模板(带变量的系统提示)、示例集(few-shot 示例)、输出约束(格式要求、校验规则)。比如一个"代码审查 SKILL",它的提示词模板会告诉模型"你是一个资深工程师,请从可读性、性能、安全三个维度审查代码",示例集里放几个审查案例,输出约束要求按固定格式返回问题列表。
SKILL 和普通提示词的区别在于可组合、可版本化、可测试。可组合是指一个 Agent 可以挂载多个 SKILL,根据任务类型动态选择;可版本化是指 SKILL 有版本号,改了之后可以回滚;可测试是指每个 SKILL 可以配一组测试用例,改了提示词之后跑一遍测试,看输出是否还符合预期。
我踩过的一个坑是SKILL 之间会冲突。比如一个 SKILL 要求"回答要简洁",另一个要求"回答要详细",同时挂载就会让模型无所适从。解决办法是在编排层做优先级管理,同一时刻只激活一个主 SKILL,其他作为辅助。
3.5 RAG 扩展:知识库不是"塞进去就行"
RAG 是三个扩展里最容易上手、也最容易做砸的。很多人以为 RAG 就是"把文档切块、向量化、检索、塞给模型",但实际效果往往很差。我在项目里踩过的坑,基本都集中在检索质量上。
第一个问题是切块策略。固定长度切块会把一句话切成两半,导致检索到的片段语义不完整。我的做法是按语义边界切块,优先在段落、标题、列表项处切分,同时保留一定的重叠(overlap)避免上下文丢失。对于结构化文档,还会保留层级信息,比如"第三章 > 3.2 节 > 具体内容"。
第二个问题是检索召回率。纯向量检索对语义相似但用词不同的查询效果一般。我用了混合检索:向量检索 + 关键词检索(BM25),两路结果做融合排序。实测下来,混合检索的命中率比纯向量检索高不少,尤其是在专业术语多的场景。
第三个问题是重排序。检索回来的 top-k 片段,相关性参差不齐。我加了一个重排序模型(rerank),对候选片段重新打分,只把最相关的几个塞给模型。这一步对最终回答质量的提升非常明显,代价是多一次模型调用。
第四个问题是知识库更新。文档改了之后,向量库要同步更新。我的做法是给每个文档块打上版本标记,更新时先删旧块再插新块,避免残留过期内容。
关于热词里提到的"RAG 知识库能存储图片吗",我的实践是:可以,但要看场景。如果图片里有文字,可以先做 OCR 提取文本再入库;如果是纯图,可以用多模态模型生成图片描述,把描述文本入库。检索时返回图片 URL 和描述,让模型决定是否引用。
4. 完整实操流程与关键环节实现
4.1 环境准备与依赖安装
先把底座搭起来。XXL-AI 的后端我选的是 Java 技术栈(Spring Boot + LangChain4j),原因是团队里 Java 人多,而且 LangChain4j 对国内模型的支持比较友好。前端用 Vue3 + TypeScript,编排画布用的是开源的流程图库。
环境要求如下:
| 组件 | 版本要求 | 说明 |
|---|---|---|
| JDK | 17+ | 推荐 21,虚拟线程对 IO 密集场景友好 |
| Maven | 3.8+ | 构建工具 |
| Node.js | 18+ | 前端构建 |
| PostgreSQL | 14+ | 主数据库,存配置和元数据 |
| Redis | 6+ | 缓存和限流 |
| 向量库 | Milvus 2.3+ 或 pgvector | RAG 检索 |
安装步骤不复杂,核心是配置好数据库和向量库。这里有个细节:向量库的选型要看数据量。数据量小(百万级以下)用 pgvector 就够了,省一个组件;数据量大或者要高并发,上 Milvus。我一开始用 pgvector,后来数据涨到千万级检索变慢,才迁到 Milvus。
4.2 供应商配置与模型接入
环境好了之后,第一件事是配置模型供应商。前面给过 YAML 示例,这里补充几个实操要点。
密钥管理:绝对不要把 API Key 写进配置文件提交到代码库。我用的是环境变量 + 配置中心的方式,本地开发用.env文件(加进.gitignore),生产环境用配置中心下发。
模型能力声明:每个供应商要声明自己支持哪些能力。这个声明会直接影响编排层的可用节点。比如一个不支持 function calling 的模型,编排时工具节点就会置灰。
连接测试:配置完供应商后,平台提供一个"测试连接"功能,发一个最简单的请求验证配置是否正确。这一步能省掉很多"配了半天发现 key 错了"的时间。
4.3 编排一个完整的 Agent 流程
我拿一个实际项目举例:智能客服 Agent。需求是接收用户问题,先查知识库,如果知识库有答案就直接回复,没有就调用工单系统查历史工单,还不行就转人工。
流程设计如下:
- 输入节点:接收用户问题
question - 检索节点:用
question查 RAG 知识库,返回kbResults和kbScore - 条件节点:判断
kbScore > 0.8,是则走模型节点 A,否则走工具节点 - 模型节点 A:基于
kbResults生成回答,输出answer - 工具节点:调用 MCP 工单查询工具,参数
question,返回ticketResults - 模型节点 B:基于
ticketResults生成回答,输出answer - 条件节点:判断
answer是否包含"无法回答",是则走转人工节点 - 输出节点:返回
answer
这个流程里,每个节点的配置都要仔细。检索节点的 top-k 我设的是 5,重排序后取 3;条件节点的阈值 0.8 是调了几次才定下来的,太低会引入不相关答案,太高会漏掉有效答案。
4.4 RAG 知识库的搭建与调优
知识库搭建分四步:文档采集、切块、向量化、入库。
文档采集支持多种格式:PDF、Word、Markdown、HTML、纯文本。PDF 解析是个坑,扫描版 PDF 要先 OCR,我用的是开源的 OCR 方案,准确率够用。
切块策略我前面提过,按语义边界切,块大小控制在 300-500 字,重叠 50 字。这个参数不是拍脑袋定的,是实测出来的:块太小语义不完整,块太大检索精度下降。
向量化用 embedding 模型,我选的是支持中文的模型。这里要注意:embedding 模型和生成模型可以不是同一个供应商。我用国产 embedding 模型做向量化,用另一个模型做生成,效果和成本都更优。
入库后要做检索测试。我准备了一组测试问题,每个问题标注了期望命中的文档块,然后跑检索看命中率。命中率低就调切块策略或换 embedding 模型,反复迭代。
4.5 工程化底座的落地细节
工程化底座是平台能不能上生产的关键。我重点做了这几件事。
可观测性:每次 Agent 执行都生成一个 trace,记录每个节点的输入、输出、耗时、token 消耗。出问题时能快速定位是哪个节点的问题。这个功能在排查"为什么这次回答不对"时特别有用。
限流与熔断:模型调用是外部依赖,必须做保护。我用了令牌桶算法做限流,每个供应商独立配置 QPS。熔断用 Resilience4j,连续失败达到阈值就熔断,走降级逻辑。
成本控制:每次调用记录 token 消耗,按供应商和项目维度统计。设置预算告警,超了就通知。这个功能帮我们避免过一次"某天突然跑了几百万 token"的事故。
版本管理:Agent 流程、SKILL、提示词都支持版本化。改了之后先发布到测试环境,验证没问题再上生产。出问题可以一键回滚。
5. 常见问题与排查技巧实录
5.1 模型调用类问题
问题一:模型返回结果不稳定,同样的输入有时对有时错。
这是最常见的抱怨。排查思路:先看 temperature 设置,如果大于 0.3,调低试试;再看提示词是否有歧义,加几个 few-shot 示例;还不行就换模型,有些模型在特定任务上就是不稳定。
问题二:流式输出中断。
通常是网络问题或超时设置太短。检查客户端的超时配置,服务端的流式响应要有心跳保活。另外注意,某些代理层会缓冲流式响应,导致看起来"不流式"。
问题三:函数调用参数格式错误。
模型生成的参数不符合 schema。解决办法是在工具描述里写清楚参数示例,同时在调用前做校验,不合法就返回错误让模型重试。
5.2 RAG 检索类问题
问题一:检索不到相关内容。
先确认文档是否真的入库了,再检查 embedding 模型是否适合当前语言。中文场景用英文 embedding 模型效果会很差。还可以试试混合检索,关键词检索能兜住向量检索漏掉的情况。
问题二:检索到不相关内容。
调高相似度阈值,或者加重排序。如果还是不行,可能是切块策略有问题,块太大导致语义稀释。
问题三:知识库更新后检索结果没变。
检查向量库是否真的更新了。常见原因是只更新了文档没更新向量,或者有缓存没清。我的做法是更新时打版本标记,检索时只查最新版本。
5.3 编排流程类问题
问题一:流程执行到某个节点卡住。
先看是不是模型调用超时,再看是不是工具节点在等外部系统响应。给每个节点加超时是必须的。
问题二:变量传递错误。
检查变量名是否一致,上游节点的输出变量名和下游节点的输入变量名要对上。我建议用统一的命名规范,比如nodeId_outputName。
问题三:条件分支判断不符合预期。
检查条件表达式的语法,以及变量类型。字符串比较和数字比较的写法不一样,容易搞错。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决建议 |
|---|---|---|---|
| 回答质量差 | 提示词/模型/检索 | 逐层排查 | 先固定模型调提示词,再调检索 |
| 响应慢 | 模型/网络/检索 | 看 trace 耗时分布 | 优化慢节点,加缓存 |
| 成本高 | token 消耗大 | 看 token 统计 | 精简提示词,换小模型 |
| 检索不准 | 切块/embedding | 跑检索测试 | 调切块,换模型,加重排序 |
| 工具调用失败 | 参数/连接 | 看工具调用日志 | 校验参数,加健康检查 |
| 流程卡死 | 超时/死循环 | 看节点状态 | 加超时,检查循环条件 |
5.5 几个独家避坑技巧
技巧一:提示词要版本化。我见过太多团队提示词改来改去,最后不知道哪个版本效果好。把提示词当代码管理,每次改动记录原因和效果。
技巧二:给模型"退路"。在提示词里明确告诉模型"如果不知道就说不知道",能显著降低幻觉。同时编排层要有兜底分支,模型答不上来时有降级方案。
技巧三:小步快跑做 A/B 测试。改了提示词或检索策略后,不要直接全量上线,先拿 10% 流量测试,对比效果再决定。
技巧四:日志要记全。每次调用的输入、输出、耗时、token、模型版本都要记。出问题时这些日志就是救命稻草。
技巧五:别迷信大模型。很多任务用小模型 + 好的提示词 + RAG,效果不比大模型差,成本却低很多。先试小模型,不够再上大的。
6. 我对这套平台的一些真实体会
做这个平台最大的收获,不是技术上的,而是认知上的。我一开始以为 AI 应用开发的核心是"选对模型",做完之后才发现,模型只是其中一环,真正决定成败的是工程化能力。同样的模型,有没有好的编排、有没有 RAG、有没有工具调用、有没有可观测性,效果天差地别。
另一个体会是,扩展机制的设计比功能本身更重要。MCP、SKILL、RAG 这三种扩展方式,本质上是在回答"当需求变化时,我改哪里"。如果每次加需求都要改核心代码,这个平台就是失败的;如果加需求只是加配置、加 SKILL、加知识库,那它就成功了。
最后分享一个我一直在用的小习惯:每次上线一个新 Agent,我都会准备一组"回归测试问题",包含正常问题、边界问题、恶意问题。每次改动后跑一遍,看回答是否还符合预期。这个习惯帮我避免了好几次"改了一个地方,坏了另一个地方"的事故。AI 应用的不确定性比传统软件大得多,测试是唯一能给你安全感的东西。