ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

多Provider切换+RAG知识库+Agent编排:AI应用架构拆解与实战

2026/9/28 8:58:17 拓冰建站 浏览量
多Provider切换+RAG知识库+Agent编排:AI应用架构拆解与实战 做 AI 应用落地这几年最明显的一个感受是模型能力迭代太快厂商格局变化也太快。今天还在用的主力模型下个月可能就因为价格、限流或者效果问题被换掉业务代码里如果到处硬编码了某一个 Provider 的 SDK 调用每次模型层面的调整都是一次伤筋动骨的重构。所以我在设计自己的 AI 项目时第一原则就是——把模型调用、知识检索和任务编排拆成三个独立模块。这一篇完整记录这套架构的设计思路和落地细节多 Provider 切换怎么抽象、RAG 知识库怎么搭、Agent 编排怎么做以及三者集成时踩过的一堆坑。内容偏实战适合正在做智能应用后端、想把 LLM 接入做得更工业化的人参考也适合刚入门想建立整体架构认知的同学。1. 为什么要把 Provider、RAG、Agent 拆成三个独立模块1.1 单体调用的痛点很多项目的初版 demo 都是在业务代码里直接chat.completions.create参数、密钥、prompt 全堆在一个函数里。用户问一句“帮我查一下合同里违约金怎么写的”代码就同时干了三件事调模型、查知识库、拼提示词。这种写法在只有一两个接口的时候没问题一旦你开始加第二个模型、加知识库、加工具调用问题就全冒出来了。第一个问题是换模型成本高。今天用 A 厂商的模型明天发现 B 厂商的效果更好或者价格更低你得把所有调用点都改一遍。第二个问题是检索和生成耦合在一起。知识库改了切分策略、换了向量库生成模块却不知道反过来生成模块调整了提示词检索逻辑也跟着受影响。第三个问题是没法做容错。单一厂商一旦限流或者服务异常整个功能直接瘫痪没有任何降级手段。1.2 三个模块的职责边界我的划分方式很简单Provider 层只负责“把统一请求翻译成各家 API 调用再把响应翻译回来”RAG 层只负责“从知识库里检索出跟问题相关的内容”Agent 层只负责“决定下一步做什么怎么组织多步推理和工具调用”。三层之间通过标准的数据结构通信谁也不直接依赖谁。用表格看更清楚模块核心职责典型输入典型输出Provider 层模型接入、路由、容错、流式封装统一 ChatRequest统一 ChatResponse / 流式增量RAG 层文档入库、切分、向量化、检索、重排用户问题纯文本命中的文档片段列表Agent 层任务拆解、工具调用、多步推理用户目标 可用工具列表最终回复或行动结果这样的好处是每一层都可以独立测试、独立升级。RAG 换了重排序模型Provider 和 Agent 完全无感Agent 从单智能体改成多智能体协作底层模型接口也不用动。1.3 这套架构的适用范围这套设计不是只有大厂才用得上的。我个人体感是只要你的应用里同时满足两个条件——会调用大模型 API且未来有换模型或加知识库的可能——就值得按这个思路拆。哪怕是一个个人博客问答机器人、一个小团队的客服系统拆完之后维护成本都会低很多。当然也有不需要拆的场景比如只是做一个临时脚本、一次性的数据分析那直接调 SDK 反而更快。过度设计也是成本别为了架构而架构。2. 多 Provider 切换抽象层与动态路由实战2.1 统一接口设计向 OpenAI 兼容协议看齐多 Provider 切换最核心的一件事是定义一套自己的统一接口。参考 OpenAI 的协议结构基本是行业默认做法因为现在绝大多数厂商——无论是闭源商业模型还是本地部署的开源模型——都提供了/v1/chat/completions兼容接口。这意味着你的 Provider 适配器在绝大多数情况下只需要处理一种协议个别不兼容的厂商单独写适配逻辑。我自己的抽象层核心接口长这样from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import AsyncIterator, Optional dataclass class ChatMessage: role: str # system / user / assistant / tool content: str tool_calls: Optional[list] None tool_call_id: Optional[str] None dataclass class ChatRequest: messages: list[ChatMessage] model: str tools: Optional[list] None temperature: float 0.7 max_tokens: Optional[int] None stream: bool False dataclass class ChatResponse: message: ChatMessage usage: dict provider: str model: str class LLMProvider(ABC): abstractmethod async def chat(self, req: ChatRequest) - ChatResponse: ... abstractmethod async def chat_stream(self, req: ChatRequest) - AsyncIterator[ChatMessage]: ... abstractmethod async def embed(self, texts: list[str], model: str) - list[list[float]]: ...这里有个经验一定要把 request 和 response 定义成自己的数据结构而不是直接用某个厂商 SDK 的类型。因为不同厂商的字段名差异很大比如 OpenAI 用max_tokens有的厂商用max_completion_tokens再比如工具调用格式OpenAI 的tool_calls和 Anthropic 的tool_use结构完全不同。有了自己的中间结构适配器只需要做两层映射进来翻译成厂商格式出去再翻译回统一格式。上层业务永远面对同一套接口。2.2 配置化驱动多厂商密钥与 base_url 管理多 Provider 切换最常用的管理方式不是写代码而是写配置。我把所有厂商的接入信息放在一个 YAML 里代码只认配置不认厂商。这样加一个新模型、切换主备模型改配置就够了不需要发版。providers: deepseek: type: openai_compatible base_url: https://api.deepseek.com/v1 api_key_env: DEEPSEEK_API_KEY models: [deepseek-chat, deepseek-reasoner] qwen: type: openai_compatible base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 api_key_env: DASHSCOPE_API_KEY models: [qwen-plus, qwen-max] zhipu: type: openai_compatible base_url: https://open.bigmodel.cn/api/paas/v4 api_key_env: ZHIPU_API_KEY models: [glm-4-plus] ollama: type: openai_compatible base_url: http://localhost:11434/v1 api_key_env: OLLAMA_API_KEY # 本地模型通常随便填 models: [llama3.1, qwen2.5] routes: default: deepseek/deepseek-chat rag_generate: qwen/qwen-plus fallback_chain: [deepseek/deepseek-chat, qwen/qwen-plus, ollama/llama3.1]这里强调两件事。第一密钥绝对不要写死在代码里用环境变量注入配置里只存环境变量名。第二base_url是最容易出错的配置项。接入兼容服务或自建网关时如果忘了配base_urlSDK 会默认请求官方地址结果就是用 A 厂商的密钥去调 B 厂商的端点报 401 或者“缺少 base_url 配置”接入本地模型时base_url必须要指向本机端口写错一个路径都会导致请求失败。凡是我看到的“配置错误: xxx provider 缺少 base_url 配置”这类报错原因几乎都是配置文件里只写了模型名和密钥没写网关地址。2.3 路由与降级重试、熔断、主备切换Provider 层除了翻译协议之外最重要的能力是容错。我见过太多项目把“调用大模型”当成一个百分百可靠的操作结果某厂商一限流用户全部报错。实际上大模型 API 的可用性远没有大家想的那么高429、5xx、超时都是家常便饭所以路由和降级必须设计进来。一个比较可靠的路由器逻辑是这样的class ProviderRouter: def __init__(self, providers: dict[str, LLMProvider], routes: dict): self.providers providers self.routes routes async def chat(self, route_key: str, req: ChatRequest) - ChatResponse: chain self.routes[route_key][chain] last_error None for provider_name in chain: try: provider self.providers[provider_name] return await provider.chat(req) except RateLimitError as e: last_error e continue except ProviderUnavailableError as e: last_error e continue # 注意401、参数错误这类不要 fallback直接抛 raise AllProvidersFailedError(last_error)关于重试有几个原则429 和 5xx 可以重试401、403、参数错误不能重试重试了也没用只会浪费配额和时间重试要用指数退避再加一点随机抖动防止多个请求同时打爆服务连续失败达到阈值后应该直接打开熔断开关短时间内不再请求该厂商直接走备用链路。路由方面我还主张“按任务路由”摘要、标题生成这类轻任务走便宜快速的小模型复杂推理、长文本生成走强模型RAG 生成的最后一步和 Agent 的规划步骤用好模型其他环节能省则省。这样整体成本可以降 30% 到 50%效果基本不掉。2.4 流式输出与工具调用的兼容处理多 Provider 切换有一个特别容易翻车的细节流式输出。各家 SSE 流式返回的字段风格不同有的把增量放在choices[0].delta.content有的直接给完整内容还有的在流式里夹杂 tool call 增量。我的做法是统一封装一个chat_stream内部解析成ChatMessage的增量序列上层只管消费。工具调用在流式场景下更要小心。很多模型流式返回 tool call 参数时JSON 会被切成很碎的小片段你需要把所有delta.tool_calls拼接起来再解析拼接过程一旦漏掉某个片段整个 JSON 就坏了。我踩过几次坑之后的处理方式是如果请求带了 tools先不发流式请求单独发一个不带 stream 的请求拿到完整的 tool_calls 结构只有最终文字回复阶段才用流式。牺牲一点点首字延迟换来的是工具调用的高可靠性尤其是工具数量多、参数嵌套深的场景非常值得。3. RAG 知识库从文档入库到检索增强3.1 RAG 的完整链路拆解RAG检索增强生成的本质是把“模型记住的”和“检索出来的”分开。模型负责推理和表达知识库负责提供事实依据。这样做的好处很明显知识可以随时更新不用重新训练模型回答可以溯源因为你能说清楚“这句话来自哪篇文档”幻觉大幅减少因为模型有了参考答案。完整的 RAG 链路分两条线。入库线文档解析 → 清洗 → 切分 → 向量化 → 写入向量库查询线用户问题 → 向量化 → 召回候选片段 → 重排 → 拼入提示词 → 模型生成。很多项目只关注查询线忽略入库线这是不对的。入库质量直接决定查询质量切分切得烂再好的检索算法也救不回来。3.2 切分策略决定检索质量的第一道关口切分chunking是 RAG 里最容易被低估的一步。我自己早期做知识库问答时直接按固定长度 500 字硬切结果大量段落被拦腰截断一句话的上半句在 A 块下半句在 B 块检索时经常只命中一半生成的答案断断续续。经过几种方案对比我现在的策略分四层固定长度 重叠按 300-800 token 切重叠 10%-15%。重叠的目的是让跨块语义尽量保留减少边界截断的损失。结构化切分优先按照文档本身的层级切比如 Markdown 的标题、PDF 的章节、合同的条款编号。这是最推荐的因为文档天然有语义边界。语义切分用 embedding 计算相邻句子的相似度相似度低的地方切开。适合没有明显结构的文档。父子分块小块负责检索父块负责喂给模型。检索命中子块后把包含它的完整父块作为上下文兼顾精度和上下文完整性。我实测下来在中文合同、技术文档这类结构化文本上结构化切分 父子分块的组合效果最好hit rate 能比固定长度切分高出 10 到 15 个百分点。需要注意的坑是切分时不要把表格、代码块拆到两个 chunk 里这类格式一旦拆分基本就废了建议解析阶段先识别出来单独处理。3.3 向量化与向量库选型向量化就是给文本计算一个稠密向量语义相近的文本向量距离更近。中文场景下我常用的模型有 OpenAI 的text-embedding-3-small/large、智源的bge-m3、阿里的text-embedding-v3等。bge-m3 在中文效果很稳而且带稀疏向量能力可以配合做混合检索。向量库选型是很多人的纠结点我直接给一个选型参考表向量库适合场景优点注意点Chroma个人项目、原型部署简单pip 装上就能用数据量大时性能一般FAISS单机百万级以下快内存索引需要自己管理增删改和持久化pgvector业务数据已在 Postgres和业务库同库事务一致需要 Postgres 扩展支持Qdrant中小规模生产过滤功能强支持 payload独立服务多一个组件Milvus大规模生产、十亿级分布式、功能全面部署运维成本高Elasticsearch已用 ES 的团队全文检索 向量一体资源占用高我的建议是数据量低于 100 万条、团队没有专门向量库运维经验的直接上 pgvector 或者 Qdrant先别想着一步到位上 Milvus运维成本比你想的高得多。向量维度方面大模型 embedding 通常是 1024 到 3072 维维度越高精度越高但检索越慢线上服务注意控制 embedding 模型的 IO 吞吐必要时做批量向量化入库。3.4 检索、重排与查询改写检索环节我自己最喜欢的一句话是召回要广精排要准。第一轮召回可以放宽条件多捞一些候选第二轮用重排模型精挑细选最终只把最相关的几段喂给大模型。具体参数给个参考向量召回 top-k 设在 20-50重排后取 top-3 到 top-5。top-k 太大会稀释提示词里的有效信息模型容易被不相关内容带偏太小则可能漏掉关键片段。混合检索是个提升召回质量的利器把 BM25 关键词检索和向量检索的结果用 RRF倒数排名融合合并能同时兼顾语义相似和精确词匹配。比如用户搜“合同编号 A-2024-001”向量检索可能因为语义不敏感而漏掉精确编号BM25 却能精确命中。重排我推荐用专门的 reranker 模型比如bge-reranker或 Cohere Rerank它们对“问题-文档片段”的匹配度打分比 embedding 相似度更准。中文场景bge-reranker-v2-m3效果不错缺点是推理速度比向量检索慢所以一定要放在召回之后、用在小候选集上。查询改写也很重要尤其是多轮对话场景。用户上一句问“第四条规定是什么”这一句问“那违约金呢”——如果不做指代消解检索出来的全是无关内容。我通常在 RAG 模块外面加一个轻量改写层把多轮上下文压缩成一个独立查询词再送进检索。3.5 评估指标hit rate 之外还要看什么RAG 做得怎么样不能靠感觉要靠指标。热词里出现了rag hit rate说明大家已经注意到召回指标的重要性。Hit rate 的定义是标准答案所在的文档片段是否出现在召回结果里。它是最基本的召回质量指标但它有个盲区——只看“有没有召回到”不看“排在第几位”。所以还要配合 MRRMean Reciprocal Rank和 Recallk 一起看。MRR 衡量正确答案排得靠不靠前答案排在第一位得 1 分第二位 0.5 分依次递减Recallk 看的是前 k 个结果里覆盖了多少正确答案。说白了hit rate 高只说明“碰运气碰到了”MRR 高才说明“每次都能准确排在最前面”。生成侧同样要有指标。Faithfulness 是检查模型生成内容是否忠实于检索到的上下文防止模型脱离资料自由发挥Answer Relevance 是检查回答是否真正回答用户的问题。实际操作中我建议维护一份 100 到 200 条的标准评测集用 LLM-as-judge 自动打分每次改切分策略、换 embedding 模型、调重排参数后都跑一遍全量评测用数据说话。3.6 进阶方向GraphRAG、Ontology RAG、Agentic RAG传统 RAG 对“关系密集型问题”很吃力。比如“A 公司的股权穿透关系是什么”“这个系统里哪些模块依赖了已废弃的接口”向量检索只能找到零散片段拼不出完整的实体关系网络。这时候就需要进阶形态。GraphRAG 的思路是先抽取文档里的实体和关系构造成知识图谱检索时既做向量召回又做图检索把和问题相关的多跳关系也捞出来。Ontology RAG 更进一步用领域本体比如医学、法律里的概念层级约束知识库的构建和检索精度更高但需要领域专家参与建模成本也高。这类方案适合知识密度高、关系复杂的垂直领域普通问答场景没必要上。还有一个明显在演进的方向是 Agentic RAG——让 Agent 决定“要不要查、查什么、查几次”。传统 RAG 是每次必查Agentic RAG 会先判断问题是否需要外部知识需要的话自主规划检索策略一次不够就多轮检索。这里我特别推荐把 RAG 封装成一个内部服务像“RAG as a Service”一样给 Agent 层用Agent 只需调用一个search_knowledge_base(query)工具具体怎么切分、怎么重排、用什么向量库全部对上层隐藏。热词里提到的langchain4j也提供了类似的 AI Service 能力和 RAG 管道Java 技术栈的同学可以直接参考。4. Agent 编排从工具调用到多智能体协作4.1 先分清Workflow 与 Agent、Harness 与 Agent 的差别很多人一开始做 Agent 就把 Workflow 和 Agent 混为一谈。我的理解很简单Workflow 是确定的流程走什么分支、调什么工具都是代码写死的Agent 是不确定的流程模型根据当前输入现场决定下一步。比如“用户上传文件 → 解析 → 归档 → 发通知”是 Workflow“帮我把这个调研做了自己规划先搜什么、读什么、怎么汇总”是 Agent。热词里有个高频问题叫“harness 和 agent 区别”。Harness 可以理解成 Agent 的“运行外壳”它负责跑循环、管上下文、处理中断、调度工具而 Agent 本身是决策的大脑。换句话说模型是 Agent 的大脑Harness 是承载这个大脑的躯体。有些框架把 ReAct 的 while 循环帮你去掉就是你平时说的这个内置的系统循环。理解这一点你会发现很多框架的底层原理其实是一样的。我的选型建议是能写死优先写死。用户需求稳定、步骤可预测的场景就用 Workflow只有任务开放、需要自主决策的场景才上 Agent。Agent 的延迟更高、成本更高、行为更不可控用它做简单任务纯属给自己找麻烦。4.2 Function Calling 是 Agent 的地基Agent 能做事核心靠的是工具调用。大模型本身不会执行任何操作但你可以通过 Function Calling 让它“请求”调用某个工具然后由你的代码真正执行。比如给 Agent 一个search_knowledge_base工具模型觉得需要查资料时就会返回一个结构化的调用请求你的代码执行完把结果以 tool 消息的形式回传给模型模型再继续推理。工具的定义核心是把参数 schema 写清楚关键词怎么写、要不要必填、约束范围是什么。description 尤其重要模型完全靠 description 决定什么时候用这个工具写得太笼统会导致误调用写得太细又会占 token。比如{ type: function, function: { name: search_knowledge_base, description: 当用户询问产品手册、合同条款、技术文档等内部资料时调用普通闲聊不要调用, parameters: { type: object, properties: { query: {type: string, description: 检索的关键词或问题建议控制在50字以内} }, required: [query] } } }这个 description 就比单纯写“搜索知识库”好很多——它告诉模型触发条件和不触发条件。4.3 单 Agent 的 ReAct 循环单 Agent 最经典的编排逻辑是 ReActReasoning Acting一条非常直观的循环推理 → 行动 → 观察 → 再推理。整个循环用伪代码描述就是这样async def run_agent(goal: str, tools: list[Tool], llm: LLMProvider, max_steps: int 10): messages [{role: system, content: SYSTEM_PROMPT}, {role: user, content: goal}] for step in range(max_steps): resp await llm.chat(ChatRequest(messagesmessages, toolstools, streamFalse)) if resp.message.tool_calls: for call in resp.message.tool_calls: result await execute_tool(call.name, call.arguments) messages.append(tool_result_message(call.id, result)) messages.append(resp.message) else: return resp.message.content return 达到最大步数任务结束这里有几个必须设置的保险栓。第一是 max_steps 上限没有步数限制的 Agent 在复杂任务里很容易陷入死循环白烧 token。第二是工具结果要裁剪一个工具可能返回上万字的数据库导出结果直接塞回上下文会把窗口撑爆一般截断到 500-2000 字超长部分提示模型“内容已经保存到文件 xxx路径是 yyy”。第三是每次工具调用都要记录日志因为 Agent 是多步过程事后要能回放它每一步做了什么、为什么这么做出了问题才能定位。4.4 多智能体编排模式单 Agent 能力有限遇到复杂任务拆分成多个各司其职的子 Agent 往往更可靠。常见的编排模式有三种。Planner-Executor规划器-执行器一个 Planner 负责把大目标拆成子任务清单多个 Executor 各自领任务执行最后汇总。适合调研、报告生成这类流程型任务。Supervisor监督者:一个主管 Agent 负责判断当前问题该分给哪个专家子 Agent再把结果汇总返回。适合客服系统、多领域专家协作。Debate / Critic辩论-评审多个 Agent 各自产出方案互相评审、指出问题最后综合成最终答案。适合方案设计、代码审查这类需要多角度把关的任务。多智能体并不是越复杂越好。Agent 之间的通信本身消耗大量 token而且一个 Agent 的错误可能会传给下一个 Agent形成错误传播。我的经验是先用单 Agent 加工具解决解决不了再考虑多智能体而且优先考虑 Planner-Executor 这种结构清晰、职责分明的模式。Supervisor 模式的“主管判断”本身就是一个新的失败点主管判断错了下面全错。4.5 编排引擎选型与学习路线Agent 编排现在有很多现成框架可以用我按可控性排序给个参考。第一类是代码级框架比如 LangGraph、LlamaIndex Workflow灵活度最高状态和流程全在自己手里适合对行为有严格要求的场景。第二类是平台级编排工具比如 Dify、Coze可视化拖拽就能搭 Workflow 和 Agent适合快速落地这类平台一般能把编排好的应用发布成标准 API 服务供外部工具调用热词里有人问“Dify 编排的应用能不能作为 API 让别的工具调用”答案是可以发布成 API 端点之后任何支持标准 HTTP 调用的工具都能接入。第三类是自研状态机如果场景足够简单自己写一个有限状态机就够了我还真见过不少产品就是这么搞的维护成本反而最低。给新人的一个学习路线先把 ReAct 用手写实现一遍别急着上框架。你会发现所谓 Agent 框架不过是把循环、状态、记忆这些通用的东西帮你封装好了。理解原理后再用 LangGraph 或 Dify 提效遇到问题才知道该去哪里改。我见过太多人一上来就套框架框架的抽象反而成了黑盒出了问题根本无从下手。5. 三个模块集成时最常踩的坑5.1 配置类错误速查把 Provider、RAG、Agent 三层接起来之后很大一部分线上事故都出在配置上。我把最常见的一批报错整理成速查表很多是从项目群里收集的真实案例报错信息根本原因处理方式400 配置错误: xxx provider 缺少 base_url 配置接兼容服务或网关时没指定 base_url配置中心补上 base_url确认不带多余结尾斜杠no api key for provider route deepseek-official路由指定了某厂商但对应密钥没加载检查环境变量名是否和配置一致model is unavailable / 404 model not found模型名写错或账号无权访问核对厂商模型列表确认开通权限unexpected status 413 payload too large请求体太大超过厂商上限裁剪历史消息压缩工具描述减小上下文error from provider: upstream request failed上游网关或下游服务临时故障按重试策略处理必要时走备用链路401 invalid api key密钥无效或环境变量配错逐项核对环境、密钥、端点的对应关系429 rate limit exceeded触发限流指数退避重试同时降级到备用厂商特别注意 413 这个错误。它经常出现在 Agent 多步执行之后——工具结果不断累积上下文越来越大最终超过厂商单次请求体限制。解决的核心不是调大上限你调不了而是做好消息压缩规则很简单超过 N 轮就把早期对话摘要成一小段工具结果一律截断。5.2 上下文管理与记忆集成三层还有一个绕不开的难题——上下文窗口是硬约束。RAG 检索出的片段、Agent 多轮推理的中间结果、工具返回的内容全都要塞进有限窗口里。你必须像一个精打细算的人管理钱包一样管理 token。我的几个做法分享给大家。第一长文档一定不要整篇塞给模型靠 RAG 检索片段一次最多带 3-5 段。第二多轮对话做一个滑动窗口加摘要机制旧消息压缩成一句话概要新上下文留足空间。第三工具结果遵守“小结果直接回传大结果落盘给路径”的原则既省 token 又保完整。第四如果厂商支持 prompt caching 或前缀缓存尽量复用固定前缀系统提示词、工具定义这部分缓存命中后能省不少时间和成本。这里提醒一句上下文管理和记忆是两个不同的问题。上下文管理是把当前窗口内的信息排布好是工程问题记忆是把长期信息存下来需要单独的记忆模块可以借助向量库来做。别把两者混在一起否则 Agent 的记忆会越来越乱性能也会崩。5.3 成本与性能平衡把模型接入抽象层做好之后降本的空间反而更大因为你有了路由能力。我常用的手法是模型分级路由。同一个会话里意图识别走小模型检索后生成走中档模型复杂推理和总结走强模型每个环节单独配路由规则成本直接摊薄。RAG 检索结果做缓存相同或相似问题直接命中缓存不用重复计算 embedding 和重排。LLM 响应只做精确命中缓存因为语义缓存命中率不稳定容易返回答非所问的旧答案。性能上流式输出是必须的首字时间压到 1 秒以内用户才能“感觉快”生成过程用队列削峰避免上游限流时全量报错。还有一点链路日志一定要带全字段provider 名、模型名、每轮耗时、token 数、检索命中的文档 ID。没有这些数据你永远没法判断一条坏回答到底是检索召回不好、重排排错、还是模型生成太胡来。5.4 几件我自己坚持做的事最后分享几个我自己的落地习惯算是踩坑之后沉淀下来的纪律。一是每个模块都要有“降级不报错”的能力。Provider 挂了自动切备用RAG 服务不可用时退化成纯模型生成并明确告知用户“当前没有检索到相关资料”Agent 执行超时返回当前结果而不是让用户干等。二是工具和模型的版本要解耦。工具定义会经常改模型也会经常换两者同时变更时一定要通过评测集验证。我吃过一个亏更新了工具 schema 忘了同步评估结果新模型频繁误调用工具线上反馈乱成一锅粥。三是永远保留“人工干预”入口。Agent 的自主性再强关键操作比如发邮件、改配置、删除数据之前必须有确认环节。这是对用户负责也是对自己负责。四是别追新。今天 GraphRAG 火明天 Agentic RAG 热框架每周都在变。架构上守住“Provider 抽象、RAG 独立、Agent 编排”这三个边界具体组件随时可以换但边界一旦被打破重构成本会指数上升。我个人在实际操作中的体会是AI 应用架构设计里最难的不是某一个技术点而是让三个模块各自独立又能无缝配合。多 Provider 切换解决的是“模型不确定性”RAG 解决的是“知识时效性”Agent 编排解决的是“任务复杂性”三者拆开设计、按标准接口集成整个系统才能长期健康地演化。每次模型涨价、知识库更新、Agent 逻辑调整你都能只改一个模块而不惊动其他部分——这种“可以持续修改而不伤筋动骨”的感觉才是这套架构真正值钱的地方。