最近一两年我接触到的AI项目越来越多地从“调用一个大模型接口”变成“要让好几个模型、一堆工具和一份知识库协同干活”。项目一复杂问题就跟着来流程是写死在代码里的换一个供应商模型要改半天想给Agent加个外部工具又得现写一套JSON Schema接入知识库检索的效果还时好时坏。也就是在这个背景下我自己折腾了一套AI应用开发平台名字就叫XXL-AI核心思路一句话就能说清楚Agent编排、多供应商适配再加上“MCP SKILL RAG”三种扩展机制最后用工程化底座把这些东西兜住。这篇文章不是我写产品宣传稿而是把我在设计、实现和使用XXL-AI过程中的完整思路、踩坑记录和可以“抄作业”的细节都摊开来聊一遍。如果你正在纠结要不要自建Agent平台或者被多Agent编排、MCP接入、RAG知识库落地折腾得头大这篇文章对你应该有点用。1. 为什么我最后自建了XXL-AIAgent应用的复杂度不是靠“堆代码”能解决的1.1 从一个“报告助手”看编排需求是怎么长出来的先讲一个具体场景。假设我要做一个“行业调研报告助手”最朴素的版本就一个Prompt把问题丢给GPT生成一篇报告。这个阶段确实用不着什么平台直接写代码调Chat Completions接口就够了。但真实业务不会这么简单。需求很快变成这样先要联网搜索最新资料搜完的资料要放进知识库做RAG检索让模型引用具体来源然后要一个“分析师”Agent来整理框架再要一个“写作”Agent撰写正文最后还要一个“质检”Agent检查事实错误。你看需求从“一次模型调用”膨胀成了“多Agent协作”代码量从几十行能撑到几百行甚至上千行而且每次加一个工具、换一个模型、调一个流程都要动业务代码。这中间有大量逻辑是重复的调用模型、维护上下文、解析工具调用、处理重试、记录成本。把这些逻辑抽象成统一的编排层就是XXL-AI最早的原型。我在这个过程中最大的体会是Agent应用和传统后端应用的根本差异在于“不可预测”。传统后端是“请求进来逻辑固定数据返回”AI应用则是“模型每一步都可能产生新的意图”它可能决定调用某个工具也可能改写你预设的流程目标。所以你在代码里写死的“顺序执行”很快会被打破。把流程改成配置化的图节点边让引擎来跑是更可持续的方案。1.2 “多Agent协作”到底该用什么姿势别一上来就整黑帮开会我看到很多团队一聊多Agent就急着让好几个Agent自由对话、互相争论美其名曰“让AI自己辩论出真理”。说实话90%的业务场景根本用不着这种“群聊模式”。我试过几种常见的多Agent编排方式各有各的适用面流水线模式Agent按顺序执行前一个的输出是后一个的输入。适合“调研-写作-质检”这类有明确工序的任务。规划-执行模式一个规划Agent拆解任务多个执行Agent并行干活最后汇总。适合“把大问题拆成小任务”的场景。群聊/辩论模式多个Agent围绕一个主题交互。要说能不能用能用但成本高、结果不可控适合创意脑暴或决策推演不适合对正确性要求高的生产场景。XXL-AI里首推的是流水线模式和规划-执行模式因为它们在工程上可观测、可重试、可定位问题。群聊模式我通常建议放到“实验区”真正上线时先在沙箱里跑足够多轮再决定要不要用。这不是否定多Agent协作的价值而是工程化落地时你得能回答“这次失败到底是哪个Agent搞砸的”这个问题——群聊模式回答不了。1.3 多供应商适配不是墙头草是保命符自建平台时“多供应商”这件事一开始我很不理解觉得接一个模型不就完了。后来被现实教育了不同的模型在不同任务上表现差异巨大有的供应商会宕机或限流有的是某个时段某个模型特别便宜。为了“业务不绑死在一棵树”上也为了让每一分token都花得值XXL-AI从第一天就做了供应商适配层。这个适配层做的事看起来简单实际上细节非常多。各家返回的结构不一致OpenAI的tool_calls和Anthropic的tool_use字段结构完全不一样流式返回的chunk格式也不同用量计费字段的位置不一样。还有国内厂商普遍提供OpenAI兼容接口但有些参数支持得不完整比如tool_choice、logprobs这些。适配层要统一这些差异对外只暴露一套标准接口背后再按供应商做转换。我把这层比作“脱敏层”上层业务代码永远不知道底层是哪个供应商、哪种模型在响应它只知道自己发了一组消息进去收到一个标准化的返回对象。这样带来的直接好处是同一套Flow配置里我甚至可以让调研用Anthropic的模型写作用DeepSeek质检用OpenAI各用所长互不干扰。2. 核心设计拆解编排引擎和供应商适配层的关键实现2.1 节点、边与上下文把流程变成可配置的图Agent编排如果只做“顺序执行”那和普通的脚本没区别。真正的编排引擎要有几种基础节点类型和灵活的连线规则。我在XXL-AI里定义了这样几类节点Agent节点核心是调大模型可以绑定某个供应商的某个模型可以带工具、带技能、带提示词模板。工具节点直接调用MCP工具或内部工具比如搜索引擎、文件写入、数据库查询。知识节点走RAG检索把查询词拿到向量库召回结果注入后续Prompt。逻辑节点条件分支、循环、并行聚合。没有这几个Flow就是一条道走到黑。人工确认节点让Agent执行到关键动作发送邮件、修改订单前停下来等待人审批这个节点在真实业务里几乎是必须的。边可以是“从A到B”的顺序边也可以是带条件的分支边比如“如果质检Agent返回的评分低于80分就回到写作Agent重写”。上下文管理上我一开始犯过错把历史消息全部传给每个节点结果上下文窗口被无关内容塞满。后来改成了层状结构Flow级变量、节点输入输出、会话历史分开。节点之间只传递声明依赖的字段而不是把整个数据库都搬过去。2.2 多Agent编排示例调研、写作、质检三条流水线怎么串上面讲了通用设计现在给一个可以直接参考的多Agent编排示例。下面这段配置是XXL-AI里一个真实Flow的简化版本任务是“基于某个主题写一篇带来源的公众号文章”{ id: research_writer_flow, name: 调研-写作-质检流水线, nodes: [ { id: researcher, type: agent, provider: anthropic, model: claude-sonnet-4-5, prompt: 你是调研员。请联网搜索主题【{{inputs.topic}}】的最新资料输出一份要点清单每条要点注明信息来源。, tools: [web_search] }, { id: writer, type: agent, provider: deepseek, model: deepseek-chat, prompt: 你是公众号作者。基于调研员的要点清单创作文章{{steps.researcher.output}}。请保持口语化禁止AI腔。, skills: [anti_ai_smell_writer] }, { id: qc, type: agent, provider: openai, model: gpt-4o, prompt: 你是质检员。检查文章的事实表述、来源标注和格式输出评分和修改建议。, require_human_confirm: true } ], edges: [ {from: researcher, to: writer}, {from: writer, to: qc} ] }注意几个细节。第一writer节点没有绑定“联网工具”因为它只需要消费researcher的输出不需要自己去搜这样能避免工具调用混乱。第二researcher的输出在writer的Prompt里通过{{steps.researcher.output}}引用这就是我前面说的“节点输入输出分离”。第三qc节点设了require_human_confirm: true实际运行到这里平台会推送给人工审核员一个确认界面人工确认后才算Flow结束。加了这一步线上出大错的概率低了很多。2.3 供应商适配层的“脱敏”思路统一请求、统一返回、统一计量适配层在实现上有一个很容易犯的错为了省事直接透传供应商的结构化字段。比如把OpenAI的ChatCompletion参数原样传给DeepSeek看起来能用但一旦要做流式、要做工具解析代码里就会充斥各种if provider openai这种分支。正确做法是定义一套“平台内部的消息格式”各家供应商的SDK只负责在边缘做转换。我在XXL-AI里给适配层定了三个统一目标。第一是统一请求内部统一用system/user/assistant三类消息工具用统一的JSON Schema描述适配层负责翻译成各家要求的格式。第二是统一返回内部只认统一的对象包含文本内容、工具调用列表、token用量、结束原因这几个字段。第三是统一计量prompt_tokens、completion_tokens、总成本、延迟全部在适配层记账业务层想查随时能查。这样设计之后我在Flow配置里切换模型就是改一个字符串的事代码完全不用动。有一次某家供应商大规模限流我把主模型切到另一家全程只改了配置中心的一个model字段线上几乎没有感知。这个“脱敏层”看起来不起眼但在运营阶段真的是保命的。3. MCP、SKILL、RAG扩展体系不是堆功能是给Agent分别装“手、脑、记忆”3.1 MCP协议Agent的手一次接入到处复用MCPModel Context Protocol模型上下文协议我已经把它当成XXL-AI的“通用工具总线”。简单理解它解决的是“AI应用如何统一地调用外部工具”这个问题。在这套协议里一个MCP Server会暴露三类能力Tools可执行动作、Resources可读数据、Prompts提示模板。客户端和Server之间通过JSON-RPC通信传输方式可以是本地stdio也可以是HTTP。你写了一个文件系统MCP Server任何支持MCP的AI前端——Claude Desktop、Codex、Cherry Studio、你自己写的平台——都能直接接入并用起来。这也是MCP现在这么火的原因工具接入从“每个应用单独适配”变成了“一次开发到处复用”。我在XXL-AI里接MCP的方式不复杂。客户端启动一个MCP Client进程或者发起HTTP连接握手之后调用tools/list拿到工具清单把工具清单转成模型能理解的Function Schema注册到全局工具库。Agent执行时发现自己需要某工具编排引擎从工具库拉起对应的MCP Server调用tools/call把结果带回上下文。这个生态现在发展得非常快什么方向都有人封装MCP插件代码调试器有x32dbg、IDA的MCP插件设计工具有Figma的MCP浏览器有Dify的浏览器MCP办公文档、数据库、支付系统基本上都能找到现成Server。接入效率比我早期自定义工具接口的方式高了一个量级。3.2 Skill把会做的事做成高内聚技能包如果说MCP是给Agent装了“手”那Skill就是给Agent装了“脑回路”——一组经过沉淀的、可复用、带参数和输出规范的能力单元。我见过社区里那些有趣的命名superpower skill、codex skill、豆包skill、ai备课skill、打斗动作提示词skill、前任skill……虽然有的偏玩梗但思路是一致的把重复出现的任务固化成一个skill包有明确的触发条件、输入输出约定、Prompt策略和可选的工具依赖。XXL-AI里的Skill本质上是一个带运行规则的模板包。下面是我的一份报告写作Skillname: report_writer description: 根据结构化要点生成正式中文报告自动去除AI腔 when_to_use: 用户要求输出正式报告、周报、调研总结时 version: 1.2.0 params: topic: string key_points: type: list required: true tone: type: enum values: [report, casual] default: report prompt: | 你是一名资深行业分析师。基于以下要点撰写中文报告{{key_points}} 规则 1. 禁止以“随着…的发展”“综上所述”“值得注意的是”等模板句式开头。 2. 每段必须有具体事实或数据拒绝空泛表态。 3. 段落之间用因果或场景关系过渡不要机械使用“首先/其次/最后”。 4. 结尾用可执行的行动建议或待验证的判断收束。 tools: [] formatter: markdown实际运行的时候XXL-AI会解析这个Skill的params把用户的输入校验后填入Prompt启用绑定的工具最后按formatter指定的格式整理输出。这个抽象层最大的价值是“可沉淀”你在一个项目里调试好的优秀Prompt不再是一段躺在代码里的字符串而是可以被其他项目、其他Agent随时引用的资产。3.3 RAG知识库的三种形态别混用说RAG之前我先把一个被问烂了的概念问题讲清楚知识库和知识库之间差别很大。现在大家常说的“RAG知识库”它内部是“向量索引”适合处理大量非结构化文本比如PDF、Markdown、规章制度、聊天记录它的强项是语义相似检索。而KG知识库知识图谱是用节点和关系表达结构化知识的适合“A和B是什么关系”这种多跳推理。还有一类是我说的结构知识库底层是数据库或业务表比如订单系统、ERP适合精确查询“订单编号多少”。这三种知识库在XXL-AI里是不同节点类型是不能互相替代的。你不能拿向量索引去查精确账目也不能拿图数据库去长期存储海量正文。我的经验是业务场景80%的“把私有资料喂给大模型”需求用RAG知识库就够了如果要做企业级的关系查询再上KG涉及精确数值的查询优先走SQL和API而不是硬塞给RAG。很多人把三者混在一起讨论最后方案设计出来怎么都别扭。3.4 RAG落地瓶颈与图片存储这两个问题被问烂了RAG的瓶颈我在自己项目中体会最深的是三个方面。第一是切分。文档切分是整条链路上最不起眼、但影响最大的一环。一段几百字的文本如果被硬切在句子中间语义断了召回再强也白搭。第二是召回精度。向量检索对同义词、问法变化比较敏感但精确术语识别的能力弱所以我现在几乎都用“BM25关键词向量检索”的混合召回有条件再加上rerank重排模型。第三是数据更新。“知识库更新”在演示时是个按钮在真实系统里是个难题你有几十万个chunk删了旧的补新的还要考虑同源文档幂等这些都要在设计阶段考虑进去。至于“RAG知识库能存储图片吗”能但要看你要的“存储”是什么深度。最直接的方式是存图片的路径或URL把图片当作元数据挂在相关文本chunk上检索到文本后一并返回链接。这种方式工程成本最低。第二种方式是对图片做OCR或ASR把图片里的文字抽取出来转成可检索文本适合扫描件、截图类场景。第三种是真正让图片参与语义检索用多模态向量模型比如CLIP、img2vec把图片编码成向量检索时图片和文本一视同仁地参与召回。第三种效果最好但需要额外资源。大多数业务场景做到前两种就已经很实用了。4. 工程化底座从“Demo能跑”到“上线不出事”差的就是这些4.1 可观测性每次Agent调用都要能回放AI应用排错比传统后端难一个数量级。传统后端出错报错堆栈一贴基本就知道是哪个函数的问题。AI应用出错可能是Prompt写得不好、模型理解偏了、工具返回异常、上下文被污染、供应商返回了奇怪字段……如果没有完整trace排查会变成一场灾难。我为XXL-AI设计了一套“运行回放”机制每一个Flow实例从开始到结束所有节点执行记录都落库。记录里包含节点ID、调用的供应商和模型、发送的完整消息体、返回的完整内容、token用量、耗时、成本、工具调用参数和结果、上游输入和下游输出。每次有人反馈“这个Agent回答得不对”我不需要猜直接拉出这次运行的trace看它每一步拿到了什么输入、输出了什么通常一眼就能定位问题。日志格式上我会额外记录一个request_id贯穿整条调用链。流程节点里再发起子调用的时候子调用的trace_id一定带上父级request_id这样跨服务排查也方便。这个习惯早期没有后来补上才发现没有链路ID的AI应用日志完全没法用。4.2 配置版本化、多租户与模型安全XXL-AI里Flow、Skill、MCP连接、供应商配置都是“配置”不是代码。既然是配置就要做版本管理。我见过线上事故是怎么发生的有人改了一个Agent的Prompt测试环境验证没问题直接同步到了生产结果模型输出风格大变。加了配置版本控制之后线上发布绝不直接改生产配置而是“测试版升级为正式版”版本号递增同时保留回滚能力。这条规则救了我很多次。多租户方面我按照“一组供应商密钥一组Flow一组知识库”来划分工作空间。不同的业务线或客户用同一个平台但互不可见。每个租户的模型调用配额独立核算密钥不能跨租户读取。安全性上特别提醒一句供应商的API Key绝不能出现在前端代码里也不要在日志里明文打印。我见过有人把API Key放在前端环境变量里结果工具里查得到来源这是非常危险的做法。4.3 上下文与token治理省钱和稳定是同一件事很多Agent平台跑着跑着就发现成本飙升问题不一定出在模型贵而是上下文失控。一个节点如果每次都把历史消息、知识库检索结果、工具返回全倒进Prompttoken消耗会呈指数级增长。我在XXL-AI里做了三层治理。第一层是节点输入裁剪。每个Agent节点都声明它需要哪些字段其它一概不注入。第二层是历史消息压缩。当会话历史超过预设阈值先触发“摘要压缩Agent”把早期对话浓缩成一段摘要再继续后续对话。第三层是知识库检索优化不是简单top_k越大越好而是结合score阈值把不相关内容过滤掉宁缺毋滥。这三层下来我实测过同一个长对话场景的token成本能降40%到60%。“省钱”和“稳定”在AI应用里是同一件事因为上下文越干净模型越不容易被无关信息带偏输出质量也会更稳定。5. 实操复盘5步把一个“会查知识库的写作Agent”跑起来5.1 目录结构与Provider配置搭建XXL-AI我推荐的项目结构长这样xxl-ai/ ├── flows/ # 编排流程定义一个JSON文件一个流程 ├── skills/ # Skill定义YAML或JSON ├── knowledge/ # 知识库配置与文档原文 ├── providers/ # 供应商适配配置 ├── mcp_servers/ # 内置MCP Server启动脚本和连接配置 ├── engine/ # 编排引擎核心代码 ├── web/ # 管理台前端 └── storage/ # 运行日志、trace、缓存Provider配置我强烈建议做成独立文件不要写在代码里。这是DeepSeek和OpenAI两套配置的示例# providers/openai.yaml provider: openai api_base: https://api.openai.com/v1 api_key_env: OPENAI_API_KEY models: - name: gpt-4o context_window: 128000 cost_per_1k_input: 0.0025 cost_per_1k_output: 0.0100# providers/deepseek.yaml provider: deepseek api_base: https://api.deepseek.com/v1 api_key_env: DEEPSEEK_API_KEY models: - name: deepseek-chat context_window: 64000 cost_per_1k_input: 0.00014 cost_per_1k_output: 0.00028 other: enable_tool_choice_warning: true为什么要把api_key用环境变量引用而不是直接写在YAML里因为配置文件要进Git做版本管理直接写密钥等于把密钥同步给了所有能看到仓库的人。用环境变量引用代码库里永远只有变量名实际密钥留在服务器环境里。5.2 写一个合格的Skill附YAML模板Skill是XXL-AI里最好上手、又最容易写废的东西。我见过的失败案例是把Skill写成“一大段万能Prompt”什么场景都能套结果什么场景都不好用。合格Skill的边界是清晰的明确它解决什么问题、什么时候用、需要哪些参数、输出成什么样。下面这个模板可以直接抄我把关键字段都写进去name: weekly_report_writer description: 根据一周工作记录生成周报 version: 1.0.0 when_to_use: 用户提供工作记录并要求生成周报时激活 params: work_log: type: string required: true description: 原始工作记录可以很乱 focus_keywords: type: list required: false description: 重点汇报方向 prompt: | 你是项目周报助手。原始工作记录如下 {{work_log}} 请提炼出本周完成的重点工作、进展数字、风险与下周计划。 写作要求 1. 用词客观不要用“我们团队非常努力”这种自我评价。 2. 尽量量化没数字的工作用具体产出描述。 3. 风险部分写出可能影响项目进度的问题不掩盖。 output: format: markdown sections: - 本周完成 - 数据与进展 - 风险与阻塞 - 下周计划when_to_use这个字段很重要。平台会用它做Skill匹配如果不写清楚可能出现用户要写周报结果触发了一个“写营销文案”的Skill输出完全跑偏。另外Skill内部可以绑定工具依赖比如周报Skill绑定“git提交记录查询”工具这样Agent调用Skill时会自动具备读取提交记录的能力而不是让用户手动粘贴。5.3 接入MCP Server并把输出流式写文件接入MCP Server在XXL-AI里是最省心的环节。以文件系统MCP为例在mcp_servers/下加一段配置name: fs_tool transport: stdio command: npx args: [-y, modelcontextprotocol/server-filesystem, /tmp/workspace]然后引擎在启动时拉起这个Server执行握手并注册工具。工具注册和调用在代码层面可以封装成一个很薄的客户端# engine/mcp_bridge.py from mcp import Client mcp Client(config) tools mcp.list_tools() for tool in tools: tool_registry.register( nametool.name, schematool.input_schema, handlerlambda name, args: mcp.call_tool(name, args) )调用时Agent只要在Prompt里提到“把内容保存到文件”模型就会发起一次工具调用call_tool(write_file, {path: /tmp/workspace/out.md, content: ...})。关于“用MCP工具流式输出内容到文件”这个需求我经常见到比如在Cherry Studio这类客户端里把大模型的回答落盘。MCP的write_file工具通常是接收完整内容一次性写入但有时内容很长或者我们希望边生成边保存。我的做法是分两层如果MCP Server提供独立的write_stream或append_to_file工具那就按流式chunk循环追加如果没有就在客户端循环里把每次生成的一块内容先积攒起来每凑够一定量调用一次带mode: append的写入工具。实时落盘的价值在于生成中途断了文件里至少已经有大部分内容重试成本低很多。这里要注意设置写入循环的最大步数避免模型陷入“不断写同一个文件”的死循环。5.4 本地搭建RAG知识库切分、向量化、检索在macOS上搭建一个本地RAG知识库我的常用组合是unstructured或pymupdf4llm负责文档拆解SentenceTransformer加载BAAI/bge-m3做中文向量化向量存储用sqlite-vss或者直接上Chroma轻量又不折腾。文档切分我踩过很多坑。一开始我按固定字符数切每500字一段结果很多段落被从中间截断检索出来的chunk语义支离破碎。后来我改成了“按标题结构切分”先用Markdown或PDF的标题层级把文档分成大块大块再按段落边界切成小块每块500到800字块与块之间保留50到100字的overlap同时把标题信息作为chunk的元数据保存。这样检索时哪怕命中的是正文也能回溯到它是哪一章的内容在做答案引用时非常有用。切分和向量化的核心代码不复杂from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-m3) def build_kb(file_path): chunks split_by_markdown_heading(file_path) vectors model.encode(chunks, normalize_embeddingsTrue) for text, vec in zip(chunks, vectors): vector_store.insert(texttext, vectorvec.tolist(), meta{source: file_path}) def retrieve(query, top_k5): q_vec model.encode([query], normalize_embeddingsTrue)[0] hits vector_store.search(q_vec, top_ktop_k) return [h.text for h in hits]检索时我建议把score阈值加进去低于阈值的chunk就不要进上下文了。我自己对bge-m3的经验阈值通常在0.35左右具体要看你的知识库和模型但“有阈值好过没阈值”是一定的。无脑塞top_k返回结果会把大量不相关内容带进Prompt模型输出质量断崖式下降。5.5 把RAG、Skill、MCP编排进同一个Flow最后把前面所有部件串成一条完整流水线。一个Flow里可以这样组合入口用户消息先到“流程分析节点”决定要不要走RAG检索如果走就调用知识节点检索出相关chunk这些chunk作为上下文交给写作Skill节点写作Agent如果需要展示实时数据再通过MCP工具节点查询最终结果由质检节点把关。这个组合方式的好处是各部分职责单一RAG管记忆、MCP管操作、Skill管产出规范、Flow管节奏。我再说一个编排时的经验不要把一个节点里的Prompt写得包罗万象又要检索、又要调用工具、又要格式化输出。正确的做法是把职责拆给不同的节点让每个节点只做好一件事。模型在单一职责下的表现远比在一个“全能节点”里稳定。每次我想“省节点数、合并Prompt”的时候最后基本都会后悔。6. 现场排坑我在这套架构里踩过的8个真实问题6.1 Codex/客户端找不到MCP三步定位“接入了MCP但客户端就是找不到工具”是我被问得最多的问题之一。排查思路固定三步。第一步确认MCP Server进程本身有没有起来看Server端的日志有没有完成initialize握手有没有被客户端拉起。stdio模式下客户端是通过执行命令启动Server的命令写错、npx慢、依赖没装进程根本没起来工具列表自然为空。第二步确认工具是否注册成功直接调一次tools/list看返回什么。如果工具列表是空的要么Server里没有定义工具要么initialize之后没有正确注册。第三步检查工具名和参数Schema模型按名称调用工具名字写错、参数类型和描述不清模型要么找不到要么老传错参数。我见过有人建了20个工具名字全是tool_1、tool_2模型看着一脸懵当然不会调用。6.2 多Agent编排的上下文爆炸多Agent流水线跑久了最典型的问题就是上下文越滚越大。每个Agent节点都把自己收到的完整输入传给下一个Flow跑十条记录之后下一轮推理的上下文里全是前面九条的历史细节。我现在的做法是子Agent输出默认只保留“摘要”而不是“全文”给下游。比如调研Agent的原始搜索结果有20页传给写作Agent的只有5条要点的总结来源URL单独放进引用列表。Flow变量也只保留实际会被后续节点引用的字段用Listener监听变量读写超时自动清理。同时我给每个Agent节点设置了一个“历史窗口上限”超过上限就触发摘要压缩。这个机制不能写得过于激进否则模型会丢失关键细节。折中方案是分两类历史一类是“最近N轮完整保留”一类是“更早前的内容只留摘要”。用户在对话中通常只需要最近几步的完整记忆再往前靠摘要就够了。6.3 RAG效果反而不如直接问模型这种情况我遇到太多次了。明明加了知识库效果却比整个知识库裸问模型还差。我通常按三个地方查。第一切分是否破坏了语义完整性。查切分结果样本看有没有把表格拆散、把代码块从中间横切、把标题和正文分离的情况。第二embedding模型和语言是否匹配。中文文档用了英文优化的向量模型检索效果一定差换个支持中文的模型往往立刻好转。第三召回阶段没有做混合检索和重排。关键词精确匹配和语义近似是两回事纯向量检索容易漏掉那些“文档里有这个词但语义距离远”的命中BM25能兜住这部分。检索回来之后再上一步rerank模型把最相关的结果顶到最前面。做完这三件事大多数RAG效果差的问题都能缓解。6.4 供应商tool calling格式不一致导致解析失败业务代码调用不同供应商时最明显的适配问题是工具的“结构化输出格式”不一致。OpenAI返回的tool_calls在消息的顶层字段里Anthropic返回的tool_use藏在content数组里Google的functionCall又在parts里。如果适配层不做转换上层用一个通用解析函数去读大概率拿到空值表现为“Agent说要调用工具但平台没收到任何工具参数”。我的经验是适配层的解析函数要按供应商分支处理但是对外返回统一的对象结构。这件事必须放在最底层做千万不要在上层业务里写if provider ...。一旦分散后面每增加一个模型你都要在几十个地方改代码代码会迅速烂掉。统一字段至少包括name、argumentsJSON对象、call_id。把这几个字段对齐了上层编排引擎就不需要关心是哪家的模型在干活。6.5 Skill“去AI味”的实操配方“去AI味”也是我写过最多版本的方向之一原因是模型默认的输出习惯太明显了。我自己总结了一套可执行的配方大家可以根据场景调整Prompt规则层在Skill的Prompt里直接写死“禁用词汇表”比如不用“首先/其次/最后/总之/综上所述/值得注意的是/不难发现”。同时规定段落之间必须用场景或因果关系过渡而不是用逻辑连接词机械拼接。示例注入层给模型提供1到2个高质量的人类写作片段作为风格参考。有的模型单纯看规则会用力过猛但给了正确范例之后学得很快。后处理层输出完成后用脚本做一轮清洗。把开头那句“作为一个人工智能语言模型”直接删掉把连续的“项目赋能”、“抓手”、“闭环”这类黑话换成更直白的表述。这三个层次叠加实测下来“AI味”能降低不少。但也要说实话完全去除不可能模型有它自己的语言惯性和统计学偏好我们能做的是把最刺眼、最模板化的表达清理干净让文本读起来像一个真正的人在写。社区里还有人分享“打斗动作提示词Skill”“备课Skill”这类偏垂直场景的技能包底层逻辑和我上面说的一样先定场景边界再给规则再给示范。6.6 MCP流式输出的落地细节最后补充一个关于MCP流式输出的细节。我见过不少人以为MCP工具天然支持流式返回实际不是这样。MCP的tools/call返回是一个完整的JSON-RPC响应工具的执行结果是一次性返回给客户端的。如果你想实现“一边生成一边把内容写进文件”真正要控制的是调用端在Agent的生成循环里把模型流式吐出的每个chunk先累积到缓冲区。每累积到一定量比如512字符就调用文件的append工具把这段内容追加到目标文件。生成结束后再调一次close或做最终flush确保文件完整。一定要设置循环保护次数防止异常情况下无限写文件。如果MCP Server本身提供了write_stream这种流式写入工具直接调用更优雅没有的话分批append是通用性好又稳定的替代方案。我在Cherry Studio这类客户端里也试过同样的思路照样能跑通。回看整个XXL-AI的搭建过程最大的收获不是“我写了一个多牛的平台”而是彻底想明白了一件事AI应用开发的核心不是某个模型多聪明而是你能不能在一个统一的底座上灵活地把模型、工具、技能和知识库按需组合起来。我在这套架构里跑了大半年最深的体感是谁换模型换得快、加工具加得快、流程调得快谁在真实业务里就更从容。模型在变、协议在变、知识库的方案也在变但“编排适配扩展工程化”这套骨架的稳定性比我预想的高很多。希望这篇记录能帮打算自己动手的人少走几步弯路。
阅读完成 · 觉得有帮助?