
去年年底我们团队正式开始搭 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_useblockGoogle 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标准化外部工具接入把一切系统变成能力MCPModel 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 编排的集成方式我们是把知识检索封装成一个标准 ToolAgent 在流程中决定是否调用。这样做的好处是统一了工具调用链路日志和成本核算都能复用现有体系。很多人问知识库能不能存图片我们的答案是能存但要区分用途。如果图片只是作为文档的一部分被引用平台会把图片转存到对象存储并在知识片段里保留一个引用标记如果图片里的文字信息是检索目标的一部分就必须先跑 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 应用平台真正的工程价值。