去年年底我们团队正式开始搭 XXL-AI 这个内部 AI 应用开发平台,起因其实特别朴素:手里三四个业务线都要接大模型,需求全是"让 Agent 帮用户查数据、填工单、生成报表",但每个项目各写一套编排代码,重复造轮子不说,升级模型、换供应商的时候简直要命。我们最终决定把"模型接入、Agent 编排、工具扩展、知识检索"这些公共能力抽出来做成一个平台层,于是有了 XXL-AI。这篇文章会把我在这套平台里面沉淀下来的架构思路、关键设计决策和踩坑经历完整写出来,包含 Agent 编排、多供应商适配、MCP + SKILL + RAG 扩展机制,以及工程化底座的落地细节。如果你也在自建 AI 应用平台,或者正在纠结要不要从 LangChain 这类框架往平台化方向走,这篇应该能给你不少可参考的实操经验。
1. 为什么我们放弃"开箱即用",选择自研编排层
先说结论:不是开源框架不好,而是团队到了某个规模之后,"框架"和"平台"之间的差距会变成真金白银的成本。
我们早期用的是 LangChain + 各类云厂商的 Agent 服务,Demo 阶段非常爽,一句话就能跑通 ReAct 循环。但到了生产环境,问题开始集中爆发:第一,框架升级频繁,小版本之间行为都会变,团队被迫长期锁版本;第二,业务方要的编排能力五花八门,比如"先查库存再算报价再走审批流",这种流程在框架里写出来是硬编码,换一个场景就得重写;第三,排障困难,一个 Agent 任务跨了模型调用、工具执行、知识检索三个环节,出问题根本说不清是哪个环节挂了。
所以我们当时定了一个原则:XXL-AI 不重新发明模型能力,而是做一层"能编排一切、能替换一切、能观测一切"的底座。这层底座要满足四个硬性要求:
- 模型必须是可插拔的,业务代码不能依赖任何一家供应商的 SDK。
- 工具扩展必须标准化,外部系统通过协议接入,而不是让业务团队到处写胶水代码。
- 知识库检索必须是独立服务,和模型调用解耦,方便单独优化。
- 所有任务链路必须有完整 Trace,每个节点的输入、输出、耗时、成本都要能查到。
这个定位决定了后面所有的技术选型。我们宁可牺牲一点开箱即用的便利性,也要保证生产环境的可控性。事实证明这个取舍是对的——平台上线到现在,业务方接入一个新场景的平均时间从一周半降到了两天,而且几乎不用改平台代码。
2. 平台整体架构:一个薄编排层如何撑起全部能力
XXL-AI 的整体架构并不复杂,甚至可以说故意做得很"薄"。核心思想是:编排层只负责任务调度和数据流转,不掺和具体的业务逻辑。
整个平台从下往上分为四层:
- 接入与网关层:负责 API 统一入口、鉴权、限流、灰度路由。
- 编排引擎层:核心的 DAG 调度器,管理节点之间的依赖、并行、条件分支和循环。
- 能力扩展层:包含模型接入网关、MCP Client、SKILL 注册中心、RAG 检索服务。
- 应用业务层:业务方通过 SDK 或可视化画布创建 Agent 应用,发布后调用统一 API。
我们特别强调"薄编排"的概念。很多平台喜欢把编排引擎做成万能工作流,结果一个简单的问答也要拖一堆节点,用户反而不知道从哪里下手。XXL-AI 的做法是:内置六种基础节点——LLM 调用节点、工具调用节点、知识检索节点、条件分支节点、循环节点、子 Agent 节点,业务方用这六种节点通过可视化画布组装成有向无环图(DAG),平台负责解析和执行。
一个典型的"查库存-算报价-生成审批单"流程在画布上的样子大致是这样的:
{ "nodes": [ {"id": "n1", "type": "llm", "model": "qwen-max", "prompt": "提取用户输入的库存查询意图"}, {"id": "n2", "type": "tool", "tool": "inventory_query", "depends": ["n1"]}, {"id": "n3", "type": "tool", "tool": "price_calculator", "depends": ["n2"]}, {"id": "n4", "type": "llm", "model": "claude-sonnet", "depends": ["n2", "n3"], "prompt": "根据库存和报价生成审批说明"}, {"id": "n5", "type": "tool", "tool": "approval_submit", "depends": ["n4"]} ] }这个 JSON 是运行时表示,实际业务方操作时是在画布上拖节点连线。平台引擎拿到 DAG 之后,先做合法性校验(检查环、检查依赖节点是否存在、检查工具参数 schema 是否匹配),再生成执行计划。执行计划会标记哪些节点可以并行,哪些节点必须等待,然后交给调度器按拓扑序执行。
之所以坚持用 DAG 而不是线性 Chain,是因为真实业务里几乎不可能一条链走到底。比如"先并行查库存和查客户信用,然后根据两个结果分支处理",这种场景用 Chain 写起来非常别扭,但在 DAG 里只是两个并行节点加一个条件分支的事。我们会长期维护这份编排引擎,后续可以把它抽象成独立的能力库单独开源。本节主要是帮助大家理解平台的骨架设计,接下来的章节会逐层展开关键模块的实现细节。
3. Agent 编排引擎:从"单任务对话"到"多 Agent 协作"的主题化落地路径
编排引擎支撑的不只是单 Agent 流程,还承载了多 Agent 协作的完整生命周期管理。我们定义了三种协作模式,分别应对不同复杂度。
第一种是串行接力模式。典型场景是"客服先理解用户诉求,再转给售后 Agent 查订单,最后由质检 Agent 生成服务总结"。每个 Agent 节点视为独立的一个回合,前一个节点的输出作为后一个节点的输入,整个过程可以理解为一个有向链。这种模式容易理解、容易排查,因为每个节点的输入输出都清晰可见,适用于流程相对固定的场景。
第二种是规划-执行模式(Plan-Execute)。平台内置一个 Planner Agent,它不直接干活,而是把用户的目标拆解成一串可执行的子任务,再交给 Execute Agent 逐个执行。我们参照了 paper 里的思路,但做了一点优化:Planner 输出的不是自然语言清单,而是结构化 JSON 数组,每个子任务里显式声明需要的工具、输入参数模板和预期产出格式。这么做的好处是 Execute Agent 不需要自己"悟"该干嘛,直接按 JSON 里的指令执行就行,准确率和稳定性都提升了一大截。
第三种是协商协作模式。适用于需要多个 Agent 针对同一问题提出方案并互相评价的场景,典型的内部场景是"产品方案评审"。我们让三个 Agent 分别扮演产品经理、技术负责人、用户代表,针对同一个需求文档输出意见,再由一个评审 Agent 负责汇总和裁决。这个模式我们做了严格的边界控制:每一轮协商都有明确的轮次上限(默认 3 轮),超过上限自动走汇总节点,避免 Agent 陷入无意义的辩论循环。
多 Agent 编排在实现层面最需要注意的是状态管理。我们为每个 Agent 节点分配了一个独立的"记忆上下文",这个上下文包含该节点的历史会话、上游节点传入的结构化数据、以及它自己调用的工具结果。上下文不是无限增长的——平台有一个全局 Token 预算,比如单节点默认 10 万 Token,超过后自动截断最旧的消息。这在成本控制和响应延迟上都非常关键。
最后是一个容易踩坑的地方:Agent 编排里的"死路"。我们的 DAG 虽然不支持环,但条件分支可能让某个子路径的节点永远等不到输入。平台在解析阶段就会做静态检查,如果发现某个节点的所有入边都来自同一个条件分支的 False 出口,会直接报"不可达节点"警告,防止业务方发布一个永远不会被完整执行的流程。
4. 多供应商接入层:模型要可替换,业务才不被锁定
如果说编排引擎是平台的大脑,那多供应商接入层就是平台的血管——所有模型的调用都从这层走,业务代码从来不会直接接触任何供应商的 SDK。
我们设计的接入层核心是一套"统一模型协议",用结构化的 Request 和 Response 对象屏蔽各家差异。Request 里只包含角色消息、工具定义、参数设置(temperature、max_tokens 等),Response 只包含文本内容、工具调用指令、Token 用量、结束原因。
这套协议最难做的不是文本生成,而是工具调用格式的归一化。各家模型返回工具调用的方式都不一样:OpenAI 是tool_calls数组,Claude 是tool_useblock,Google Gemini 是functionCall。我们的接入层在底层做了适配,无论上游是哪家模型,吐出来的都是统一结构的指令对象:
{ "tool_call_id": "call_001", "tool_name": "inventory_query", "arguments": { "sku": "A100", "warehouse": "shenzhen" } }这样一来,编排引擎不需要关心模型是谁,只需要拿到这个标准结构去调度对应的工具。后续接入新模型供应商,只是接入层多一个适配器的事,业务代码一行不改。
供应商路由策略方面,我们支持三种路由模式:按能力标签路由(比如请求里声明需要"长文档理解"能力,平台自动路由到支持 200K 上下文的模型)、按成本路由(默认走最便宜的满足参数要求的模型)、按延迟路由(精准控制响应时间优先使用低延迟模型)。
我们还会在接入层做故障转移。比如某家供应商的 API 持续返回 5xx 或者响应超时,网关会自动把流量切换到备用供应商,切换过程对上层透明。这里有一个细节:切换不是无脑的,要结合模型能力标签判断备选供应商是否满足当前任务的需求,否则可能出现"切过去了,但模型能力不够导致结果质量下降"的新问题。
最后是成本控制。接入层会记录每一个请求的 token 消耗,按照供应商的实际价格换算成成本数据,写到可观测系统里。我们内部每周都会看一次"模型成本周报",发现哪个场景的模型选择不合理,直接通过路由策略调优。这个功能虽然没有很复杂,但带来的成本节省非常可观——我们曾经仅仅因为把某个场景从 gpt-4o 切到 qwen-max,一个月少了 40% 的模型费用。
5. MCP、SKILL、RAG 三种扩展机制的分工与协同
XXL-AI 最核心的扩展能力是"MCP + SKILL + RAG"三件套。它们解决的问题完全不同,但组合在一起,才真正撑起了一个可进化的 Agent 平台。
5.1 MCP:标准化外部工具接入,把一切系统变成能力
MCP(Model Context Protocol)收到的关注度很高,实际用起来也确实能解决大问题。它的价值类似"工具接入的 USB-C 接口":以前接一个外部系统,要专门开发一套工具封装、鉴权、参数转换代码,现在只要符合 MCP 协议,平台侧就能自动识别和调用。
XXL-AI 内置了 MCP Client 运行时,支持两种传输方式:本地进程的 stdio 方式和远程服务的 HTTP/SSE 方式。对于内部系统部署的 MCP Server,我们推荐走 SSE,因为它不依赖共享文件系统,也方便做负载均衡。接入一个 MCP Server 的过程很简单,只需要在平台配置中心注册一个描述文件:
{ "server_name": "order_system_mcp", "transport": "sse", "endpoint": "https://mcp-internal.company.com/order", "auth": { "type": "bearer", "key_alias": "secret.mcp.order" }, "tool_whitelist": ["query_order", "cancel_order", "update_delivery"], "timeout_seconds": 30, "rate_limit_per_minute": 120 }有了白名单机制,我们可以在不修改 MCP Server 代码的前提下,控制哪些工具向哪些 Agent 暴露,避免安全问题。
MCP 集成中最容易踩的坑是工具参数 schema 不规范。部分 MCP Server 的工具定义里全是{"type": "string"},没有描述、没有 enum 枚举、没有必填标记,这会让大模型在调用时频繁生成错误的参数。我们目前的应对是对工具 schema 做一次"增强补全",由管理员手动补充参数描述和校验规则后提交到工具中心。
5.2 SKILL:把经验固化成可复用的任务技能包
MCP 解决的是"能调用什么",SKILL 解决的是"怎么把一件事办好"。
我们参考了 Claude Agent Skills 的设计思路,把 SKILL 定义为一个包含触发条件、执行步骤、参数约束、输出格式的完整技能包。一个合格的 SKILL 不是一段 prompt 模板,而是一个 YAML 定义加上若干参考脚本的目录。
举一个我们实际使用的例子——"工单催办"技能:
schema_version: 1.0 name: ticket_expedite description: 处理用户催办工单的完整流程,包含查单、判断时效、升级通知三个步骤 triggers: - "催办" - "工单进度" - "什么时候能处理好" steps: - check_order: { tool: mcp.order.query_order, param_from_user: [ticket_id] } - check_sla: { tool: internal.sla_predictor, depends_on: check_order } - decide_action: { llm: express_route, depends_on: [check_order, check_sla] } - notify_owner: { tool: mcp.order.urgent_notify, condition: "decide_action == escalate" } output_format: "必须包含当前状态、预计完成时间、已升级通知的负责人姓名"这个技能包可以被任意一个 Agent 应用引用,也可以被业务方通过画布直接拖进流程。它的价值在于:一个团队调通的工作流,不用换一个场景就重写一遍,沉淀成 SKILL 后全公司复用。
SKILL 最关键的设计点是触发条件和参数抽取。我们要保证某个场景的 Agent 能自动识别"用户说这句话了,应该用这个技能包"。平台的做法是在技能包里声明 trigger 关键词和意图描述,同时允许业务方配置"仅在该 Agent 应用内启用这几个技能"来缩小检索范围。
5.3 RAG:知识检索与 Agent 的记忆扩展
RAG 这部分我们踩过不少坑,慢慢沉淀下来一套相对成熟的方案。先说结论:RAG 仍然值得用,尤其是企业内部私有知识的场景,但它必须跟 Agent 编排深度集成,不能简单做成"查询-拼到 prompt 里"两步走。
XXL-AI 的 RAG 服务包含几个核心组件:
- 文档解析管道:处理 PDF、Word、Markdown、HTML,解析出标题层级和段落结构。
- 分块策略:默认按 512 个 token 分块,块与块之间重叠 64 个 token,保留段落边界。
- Embedding:同时支持 text-embedding-v3、bge-m3 等模型做向量化。
- 检索策略:关键词(BM25)+ 向量检索的混合检索,再经过 rerank 模型打一次分。
- 知识库管理:支持多知识库隔离、文档版本更新、权限控制。
在参数选择上我们积累了一些经验值:中文字符的 token 估算通常乘以 1.6~2,所以一个 512 token 的块大约对应 300 个汉字左右;重叠 64 个 token 是为了防止一句话被块边界切断导致语义丢失;top_k我们一般取 5~8,低于 3 的话召回信息太少,高于 10 的话大模型收到的噪声会明显增多。
RAG 与 Agent 编排的集成方式,我们是把知识检索封装成一个标准 Tool,Agent 在流程中决定是否调用。这样做的好处是统一了工具调用链路,日志和成本核算都能复用现有体系。
很多人问知识库能不能存图片,我们的答案是能存,但要区分用途。如果图片只是作为文档的一部分被引用,平台会把图片转存到对象存储,并在知识片段里保留一个引用标记;如果图片里的文字信息是检索目标的一部分,就必须先跑 OCR 转成文字后再入库。目前 RAG 对图片的语义理解还有瓶颈,但在工程上完全可以通过"OCR + 描述生成"的方式把图片信息纳入检索范围。
5.4 三者的协同:一个实际业务的多能力组合案例
我们做内部售后服务场景时,同时用到了三件套:用户问"我的订单 X 退款到哪一步了",Agent 先通过 MCP 调用订单系统查询退款单状态;发现退款卡在财务审批环节,于是触发 SKILL 技能包"退款跟进",里面定义了这个场景的 SLA 标准和升级流程;如果用户进一步问到"贵司的退款政策",Agent 才会调到 RAG 知识库检索对应的政策原文。
这个案例的启示是:三件套不是三选一的关系,而是互相配合的立体能力。MCP 负责"触达系统",SKILL 负责"规范流程",RAG 负责"提供知识",三者通过编排引擎组织在一起,才是一个完整的智能应用。如果没有 SKILL,这个场景可能需要把退款标准硬编码进业务流程;如果没有 MCP,就得为订单系统单独写一套工具封装;如果没有 RAG,政策相关的问题就只能靠模型瞎猜。这也是我们在平台命名里强调"MCP + SKILL + RAG"的原因。
6. 工程化底座:可观测性、配置管理、密钥与灰度发布
很多自研平台死在一个地方:功能都跑通了,但不敢上线,因为出了问题查不到日志。XXL-AI 从第一天起就把工程化底座当作一等公民来建设。
可观测性是我们投入最多的一块。每个 Agent 任务从进入网关开始,就会生成一个全局唯一的 Trace ID,贯穿 LLM 调用、工具执行、知识检索、编排调度全链路。调用链里记录的不只是成功失败状态,还包括每一跳的输入输出摘要、Token 消耗、延迟、费用。我们自研了一个轻量级的 Trace view,能按 Trace ID 直接查看一个任务的完整因果链,这在排查多 Agent 协作问题时价值巨大。
配置管理用的是一套集中式配置中心,支持多环境隔离(dev/staging/prod)、配置版本管理和变更审批。配置项主要覆盖:供应商 API Key 别名、模型路由策略、工具白名单、技能包启用状态、知识库连接信息等。
密钥管理这块我们做了一个特别重要的决定:API Key 绝不允许出现在业务配置里。所有密钥存放于专用的凭据管理服务中,业务通过key_alias引用。好比说网关配置里只写"key_alias": "secret.mcp.order",实际密钥由凭据服务在调用时注入。这样即便配置仓库被误读,也不会暴露任何真实密钥。
灰度发布是平台上线新特性时的安全阀。我们的做法是按应用维度灰度:一个 Agent 应用可以同时运行两个版本,灰度版本承载 5% 流量,跑一段时间对比响应质量、任务完成率、平均延迟等指标,确认无误后手动切 100%。如果灰度版本的核心指标明显劣于稳定版本,一键回滚。
压测也很重要。大模型接口的延迟分布和传统接口完全不同,长尾可以拉得非常夸张。我们在上线前会做流量回放式压测,把生产环境的真实请求时间序列重放一遍,观察网关线程池、模型并发上限、知识库 QPS 各个节点的表现。实测下来,网关线程池配置在 200~400 之间,配合流量队列几乎能应付我们当前规模的所有场景,再往上走要做的是多实例横向扩容。
7. 上线三个月,我们被现实教育过的几个坑
最后分享几个真实踩过、也真实花了时间解决的坑。这些坑在文档上很难看到,但每个都直接影响了线上稳定性。
坑一:多 Agent 来回调用导致任务发散。最开始我们让两个 Agent 自由对话优化方案,结果两个 Agent 互相"你说得对,但是我觉得还可以优化"地聊了十几个回合,Token 烧掉了 2 万多,最后输出一个毫无变化的结果。我们的解法是给每个多 Agent 协作会话设了严格的轮次上限和"收敛判定"功能,节点每次输出后自动对比上一轮,如果核心内容相似度超过 90%,判定为收敛并强制结束。这是成本控制层面非常重要的底线。
坑二:模型供应商突然限流。某天大促活动,供应商的某个模型入口突然限流,我们整条业务链路的错误率飙升到 35%。后来我们总结了三个经验:网关层必须做主动超时和快速失败,不能傻等供应商超时(默认改写为 8 秒);路由层要配置"同能力模型多个供应商"的兜底;每次供应商发布新模型或调整限流策略,要有人去主动同步更新平台侧的容量规划。生产环境没有侥幸可言。
坑三:RAG 检索结果"看着相关,实则误导"。有一段时间用户总反馈某些问题的回答质量忽高忽低,排查下来发现是检索到了某一个高相似度但并不准确的知识片段。我们的对策是:引入重排序模型,并且在拼接知识片段时要求模型输出引用来源编号,最后在答案下方显示引用的文档标题和段落链接。这不仅提升了答案可信度,还为后续的 RAG 效果评估提供了最真实的用户反馈数据。
还有一个容易被忽略的问题:长上下文对模型输出的影响。在同一个 Agent 节点里,如果塞入的上下文太长(比如超过 50K token),模型输出的有效性会肉眼可见地下降,经常出现重复内容、偏离主题乃至自相矛盾。我们现在对单次 LLM 调用的输入做了强制摘要压缩:当上下文超长时,先调用便宜的轻量模型做分层摘要,把压缩后的上下文再传给主力模型。牺牲了一点信息完整性,但输出的稳定性和响应速度都提升显著。
所以我们在实践里总结的经验是:不要迷信"模型能力越强越好用",平台要做的反而是给模型设好边界——边界内的能力释放,边界外的兜底兜住,才是 AI 应用平台真正的工程价值。