新闻详情

新闻详情

首页 / 资讯中心 / 详情

多Provider路由、RAG与Agent编排:AI应用三层架构设计实战

发布时间:2026/9/26 15:13:56来源:尧图网络
多Provider路由、RAG与Agent编排:AI应用三层架构设计实战
1. 从单点调用到多 Provider 路由为什么一开始就要把口子留出来做 AI 应用最怕的一件事就是第一版代码里把某一家模型服务商的 SDK 直接写死在业务逻辑里。我见过太多项目最开始只是调一个对话接口图省事client.chat()到处飞等到要换模型、要加备用通道、要做成本对比的时候才发现改动量比重新写一遍还大。所以这一篇我想聊的核心就是多 Provider 切换、RAG 知识库、Agent 编排这三件事怎么在一个模块架构里各就各位而不是互相打架。先把概念对齐一下。这里说的Provider指的是模型服务的提供方抽象层它可能对应不同的模型厂商、不同的部署实例甚至是本地推理服务。RAGRetrieval-Augmented Generation检索增强生成解决的是模型不知道你私有数据的问题通过外挂知识库把相关片段喂给模型。Agent 编排解决的是单次问答不够用的问题让模型能调用工具、分步骤完成任务。这三者不是并列关系而是层层递进Provider 是地基RAG 是外挂记忆Agent 是行动能力。为什么我要把这三块放在同一篇里讲因为实际项目里它们耦合得非常紧。举个真实场景一个 Agent 在执行任务时需要先检索知识库拿到业务规则再根据规则决定调用哪个工具而每一步的推理又可能走不同的 Provider——便宜的模型做意图识别贵的模型做最终生成。如果你没有一套统一的架构这种组合会迅速变成一团乱麻。提示架构设计的第一原则不是功能全而是边界清。Provider 层只负责把请求发给某个模型并拿回结果不要让它知道 RAG 和 Agent 的存在。我个人的经验是在写第一行业务代码之前先把 Provider 抽象层搭好哪怕当时只有一个模型可用。这个投入大概半天时间但后面能省下几十个小时的重构。下面这张表是我总结的三种常见做法对比你可以对照自己的项目看看处在哪个阶段。做法典型表现换模型成本适合阶段硬编码调用SDK 直接写在业务里高需全局搜索替换一次性 Demo简单封装有个call_llm()函数中改函数内部小项目Provider 抽象统一接口 注册表 路由低加配置即可长期演进从表里能看出来Provider 抽象的价值在项目早期不明显但一旦你要做多模型对比、灰度切换、故障降级它就是救命的东西。接下来我会把每一层的设计逻辑拆开讲包括我踩过的坑和最后落地的方案。2. Provider 抽象层的接口设计与路由策略2.1 统一接口应该长什么样设计 Provider 接口时最容易犯的错是照着某一家 SDK 的签名来定义。比如某家 SDK 的入参是messages加system另一家是prompt加history如果你按第一家的格式定接口第二家接进来就要做大量适配。正确的做法是定义一个最小公约数的内部格式各家 Provider 负责把它翻译成自己的格式。我通常定义的接口包含这几个核心方法class BaseProvider: def chat(self, messages: list[dict], **kwargs) - str: 同步对话messages 为 [{role: user, content: ...}] raise NotImplementedError def stream_chat(self, messages: list[dict], **kwargs): 流式对话逐块 yield 文本 raise NotImplementedError def embed(self, texts: list[str]) - list[list[float]]: 文本向量化RAG 检索用 raise NotImplementedError property def model_name(self) - str: raise NotImplementedError这里有个关键决策要不要把embed放进同一个 Provider 接口。我的答案是放但允许抛NotImplementedError。原因是对话模型和向量模型经常来自不同厂商如果强行要求每个 Provider 都实现 embed会逼着你写一堆空方法。更好的做法是拆成ChatProvider和EmbedProvider两个基类需要哪个继承哪个。另一个细节是**kwargs的透传。不同 Provider 有各自的特殊参数比如温度、top_p、最大 token 数甚至有些厂商有独家的reasoning_effort之类的参数。我的做法是通用参数显式定义厂商特有参数走 kwargs 透传并在文档里注明哪些参数会被忽略。这样既保证了接口稳定又不会限制高级用法。2.2 路由策略不只是选一个模型Provider 抽象搭好之后下一个问题就是这次请求该发给谁。很多人以为路由就是读个配置选默认模型其实远不止。我在实际项目里用到的路由维度至少有四种按任务类型路由意图识别、分类这种简单任务走小模型复杂推理走大模型。按成本路由给每个 Provider 配一个单价在满足质量要求的前提下选最便宜的。按可用性路由主 Provider 超时或报错时自动降级到备用 Provider。按用户/租户路由不同客户可能要求数据走不同的部署实例。这四种维度经常需要组合。我的实现方式是责任链模式每个路由规则是一个节点请求依次经过第一个能给出明确决策的节点胜出否则走默认。这样新增规则不用改老代码符合开闭原则。class Router: def __init__(self, rules: list): self.rules rules def route(self, request) - str: for rule in self.rules: provider rule.match(request) if provider: return provider return self.default_provider注意降级路由一定要设熔断阈值否则主 Provider 只是偶发慢你却把所有流量都切走了反而造成备用通道过载。我一般设连续 3 次失败或 5 秒超时才触发降级恢复后逐步放量。2.3 配置驱动的 Provider 注册路由要灵活前提是 Provider 的注册要配置化。我习惯用一个 YAML 或 JSON 描述所有 Provider启动时动态加载providers: - name: primary type: openai_compatible base_url: https://api.example.com/v1 model: gpt-4-class api_key_env: PRIMARY_KEY timeout: 30 weight: 100 - name: fallback type: openai_compatible base_url: https://api.backup.com/v1 model: gpt-3.5-class api_key_env: FALLBACK_KEY timeout: 15 weight: 0这里有个我踩过的坑base_url缺失导致的配置错误。热词里出现的 provider 缺少 base_url 配置 这类报错本质上是 Provider 初始化时没有校验必填字段。我的做法是在加载配置时做一次 schema 校验缺字段直接启动失败而不是等到第一次请求才报错。启动即失败比运行时才发现要好得多。另外api_key千万不要写进配置文件用环境变量引用。我见过有人把 key 提交到代码仓库后果很严重。配置里只放api_key_env这个变量名运行时从环境读取。3. RAG 知识库切块、检索与上下文拼装的实际取舍3.1 切块策略决定了检索质量的上限RAG 效果不好十有八九问题出在切块上。很多人上来就调检索参数、换向量模型其实切块策略才是决定检索质量上限的那一环。切块的核心矛盾是块太小语义不完整检索出来答非所问块太大噪声多还会挤占上下文窗口。我常用的切块策略是递归字符切块 语义边界优先。具体来说按优先级依次尝试在段落、句子、逗号处切分保证每块尽量落在自然语义边界上。块大小我一般设 300 到 500 个 token重叠 50 到 80 个 token。重叠的作用是防止关键信息正好被切在边界上导致两边都不完整。def chunk_text(text, chunk_size400, overlap60): separators [\n\n, \n, 。, , , , , ] # 递归按分隔符切分直到每块小于 chunk_size # 相邻块之间保留 overlap 个字符的重叠 ...对于结构化文档比如带标题的 Markdown 或带层级的说明书我会把标题路径拼进每个块。比如一个块来自第三章 3.2 配置项 超时设置那这个块的文本前面会加上这段路径。这样检索时即使块本身没提到超时标题路径也能提供上下文召回率明显提升。提示切块参数没有万能值一定要用你自己的数据做小规模评测。我的做法是准备 20 到 30 个真实问题人工标注正确答案所在的块然后调整参数看召回率变化。3.2 检索环节向量、关键词还是混合检索这块纯向量检索和纯关键词检索各有短板。向量检索擅长语义相似但对专有名词、编号、代码符号不敏感关键词检索BM25 之类擅长精确匹配但不懂同义改写。混合检索是目前比较稳的方案两路各召回一批再用 RRFReciprocal Rank Fusion之类的算法融合排序。检索方式优势短板适用场景向量检索语义理解强专有名词弱概念性问答关键词检索精确匹配强不懂同义编号、术语查询混合检索兼顾两者实现复杂通用推荐融合之后通常还要加一层重排序Rerank。向量检索是粗筛重排序模型是精排用交叉编码器对候选块和问题做精细打分取 top 3 到 5 个喂给模型。这一步对最终质量影响很大但会增加延迟所以候选集不要太大一般粗筛 20 到 50 个精排后留 3 到 5 个。3.3 上下文拼装别把检索结果直接堆进去检索出相关块之后怎么拼进 prompt 也有讲究。我见过最粗暴的做法是把所有块用换行拼起来结果模型分不清哪段是问题、哪段是资料。我的模板大致是这样你是一个严谨的助手请仅根据下面的资料回答问题。 如果资料中没有相关信息请明确说明资料中未提及不要编造。 【资料开始】 [1] {chunk_1} [2] {chunk_2} [3] {chunk_3} 【资料结束】 问题{question}给每个块编号并要求模型在回答时引用编号这样既方便溯源也能抑制幻觉。另外资料块要按相关性从高到低排列因为很多模型对上下文开头和结尾的内容更敏感把最相关的放前面效果更好。还有一个容易被忽略的点token 预算。检索块加上系统提示、对话历史、问题本身总长度不能超过模型的上下文窗口。我一般会预留 20% 的余量给模型输出剩下的按比例分配给各来源。如果历史对话太长就做摘要压缩而不是直接截断截断容易丢掉关键信息。4. Agent 编排工具调用、状态管理与失败重试4.1 Agent 的本质是带状态的循环很多人把 Agent 想得很玄其实剥开看就是一个循环模型思考 → 决定调用工具 → 执行工具 → 把结果喂回模型 → 继续思考直到模型认为任务完成或达到最大轮数。所以 Agent 编排的核心就是把这个循环管好包括状态怎么存、工具怎么注册、失败怎么处理。工具注册我推荐用装饰器 schema 自动生成的方式避免手写 JSON Schema 出错tool def search_knowledge(query: str) - str: 在知识库中检索相关内容。query 为检索关键词。 return rag_retrieve(query)装饰器负责从函数签名和 docstring 里提取参数说明生成模型能理解的工具描述。这样加工具只需要写函数不用维护额外的 schema 文件减少不一致的风险。4.2 状态管理别把整个历史都塞回去Agent 跑多轮之后对话历史会迅速膨胀。如果每轮都把完整历史塞回去token 消耗会爆炸而且模型容易被早期无关内容干扰。我的做法是分层记忆短期记忆最近 3 到 5 轮的完整对话保证连贯性。工作记忆当前任务的中间结果比如已调用的工具和返回值结构化存储。长期记忆跨会话的重要信息落到 RAG 知识库或专门的记忆存储里。工作记忆这块特别关键。比如 Agent 调了搜索工具拿到 10 条结果不需要把 10 条全塞回模型而是只回传摘要或前几条把完整结果存在外部需要时再按 ID 取。这样既省 token又避免上下文被噪声淹没。注意Agent 循环一定要设最大轮数和总超时。我见过 Agent 陷入死循环反复调用同一个工具把额度烧光的案例。一般设 10 到 15 轮上限超时 60 到 120 秒到点强制终止并返回已有结果。4.3 失败重试与错误分类Agent 执行失败的原因五花八门工具报错、模型输出格式不对、超时、额度不足。不同错误要用不同策略不能一律重试。我的分类处理是这样的错误类型典型表现处理策略工具临时故障网络超时、限流指数退避重试 2 到 3 次模型输出格式错JSON 解析失败把错误信息回传让模型重试一次配置错误缺 base_url、缺 key直接失败不重试记录告警额度耗尽返回额度不足切换到备用 Provider这里要特别说配置错误不要重试。热词里那些 缺少 base_url 配置、no api key for provider 的报错重试一百次也没用只会浪费时间。我的做法是在 Provider 初始化阶段就校验配置把这类错误挡在请求之前。另外模型输出格式错误时把解析失败的原文和错误信息一起回传让模型自己纠正比单纯重试有效得多。比如你上次的输出不是合法 JSON错误是 XXX请重新输出。 实测这样一次纠正的成功率能到 80% 以上。5. 三层如何协同一个完整的请求生命周期5.1 从用户输入到最终回答的完整链路把三层串起来看一个典型请求会经过这些步骤入口层接收用户输入做基础校验和敏感词过滤。路由层根据任务类型和当前 Provider 健康状态选定本次使用的 Provider。Agent 层判断这是简单问答还是需要多步任务。简单问答直接走 RAG复杂任务进入 Agent 循环。RAG 层如需要检索知识库拼装上下文。Provider 层发起模型调用处理流式返回。结果层做后处理包括引用标注、格式整理、日志记录。这个链路里每一层都只依赖下一层的抽象接口不关心具体实现。这样换 Provider 不影响 Agent换 RAG 方案不影响路由各层可以独立演进。5.2 一个容易忽略的协同点RAG 作为 Agent 的工具很多人把 RAG 和 Agent 当成两条平行线其实RAG 完全可以作为 Agent 的一个工具。当 Agent 需要查资料时调用search_knowledge工具这个工具内部走完整的 RAG 流程。这样做的好处是Agent 可以自主决定什么时候需要查资料查几次用什么关键词查比固定流程灵活得多。但要注意Agent 自主检索会增加不确定性和延迟。我的经验是给检索工具设一个调用次数上限比如最多 3 次避免 Agent 反复检索。同时检索工具的返回要做摘要不要把原始块全塞回去。5.3 可观测性没有日志的架构等于没有架构三层协同之后出问题最难排查的就是到底是哪一层的问题。所以全链路追踪是必须的。我的做法是给每个请求分配一个 trace_id每一层的关键节点都打日志路由决策、检索命中的块 ID、Agent 的每一轮思考和工具调用、Provider 的耗时和 token 消耗。logger.info(route_decision, extra{ trace_id: trace_id, provider: provider_name, reason: rule_name, latency_ms: latency, })有了这些日志排查问题时可以快速定位是路由选错了 Provider还是检索没召回还是 Agent 循环卡住了。我强烈建议在项目早期就把这套埋点加上后期补的成本高得多。6. 落地过程中的几个真实坑与应对6.1 流式输出与 Agent 循环的冲突流式输出对用户体验很重要但 Agent 循环需要拿到完整结果才能解析工具调用。这两者天然有冲突。我的处理方式是分阶段Agent 的思考阶段用非流式因为要解析结构化输出最终回答阶段用流式。这样既保证了工具调用的可靠性又让用户看到逐字输出的效果。如果一定要全程流式那就需要增量解析边收边判断是不是工具调用。这个实现复杂度高容易出 bug除非有强需求否则不建议。6.2 多 Provider 的输出格式差异不同 Provider 对同一个 prompt 的输出风格差异可能很大。比如同样要求输出 JSON有的会老老实实只输出 JSON有的会加一句好的以下是结果再跟 JSON。我的应对是写一个健壮的解析器先尝试直接解析失败则用正则提取第一个 JSON 块再失败才报错。同时在 prompt 里明确要求只输出 JSON不要任何额外文字能减少大部分问题。6.3 成本失控的预防多 Provider 加 Agent 循环成本很容易失控。我的做法是给每个请求设 token 预算上限超过就截断或降级。同时按天统计各 Provider 的消耗设阈值告警。另外Agent 循环里每一步都要记录 token 消耗方便事后分析哪一步最费钱。提示小模型做意图识别、大模型做最终生成这个组合通常能省 50% 以上的成本而质量损失很小。值得一试。6.4 配置热更新Provider 配置经常需要调整比如临时切流量、改超时。如果每次都要重启服务体验很差。我的做法是配置监听 原子替换配置文件变化时重新加载并校验校验通过后原子替换内存中的 Provider 注册表正在进行的请求不受影响。这样运维起来灵活很多。7. 我在这套架构上的一些个人体会这套三层架构我前后迭代过好几个版本最大的体会是抽象要适度不要为了抽象而抽象。早期我曾经把 Provider 层设计得极其灵活支持各种插件、中间件、钩子结果代码复杂度飙升新人根本看不懂。后来砍掉了一大半只保留最核心的接口和路由反而更好维护。另一个体会是测试要覆盖降级路径。正常路径大家都测但主 Provider 挂了、检索返回空、Agent 超时这些异常路径往往上线后才暴露。我的做法是写一个故障注入的测试开关能模拟各种失败定期跑一遍确保降级逻辑真的有效。最后说个小的日志里不要打完整的 prompt 和用户输入涉及隐私和合规风险。我一般只打长度、hash 和关键字段需要排查时再按 trace_id 去专门的审计存储里查。这个习惯能帮你避开很多麻烦。这套架构不是银弹但它给了我一个稳定的骨架让我在换模型、加知识库、上 Agent 的时候不用每次都推倒重来。如果你正在做类似的东西建议先把 Provider 层和日志埋点搭好剩下的可以慢慢迭代。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

给爸妈配吸附性义齿,做子女的要先弄清哪几件事?/钟祥小灰兔科普 2026/9/26 15:47:52

给爸妈配吸附性义齿,做子女的要先弄清哪几件事?/钟祥小灰兔科普

咱们钟祥人讲孝心,都是实打实的。上回在阳春大街碰见老同学,他说给老爷子买了副新假牙,结果老爷子吃饭还是嫌松,打喷嚏的时候赶紧用手捂着嘴,生怕假牙“跑”出来。这场景,好多街坊家里是不是都见过&#xf…

阅读更多 →
Mosquitto CVE-2017-9868 安全公告解读:持久化文件权限漏洞的成因、修复与防护实践 2026/9/26 15:47:45

Mosquitto CVE-2017-9868 安全公告解读:持久化文件权限漏洞的成因、修复与防护实践

物联网消息队列后端网络/通信 【免费下载链接】mosquitto Eclipse Mosquitto - An open source MQTT broker 项目地址: https://gitcode.com/gh_mirrors/mo/mosquitto 点击查看 免费下载 导读:本文以 Eclipse Mosquitto 官方安全公告(securi…

阅读更多 →
InternVL1.5 配 TaoToken:多模态模型 settings.json 配置与 GPT-4V 差距验证 2026/9/26 15:47:45

InternVL1.5 配 TaoToken:多模态模型 settings.json 配置与 GPT-4V 差距验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
【清晰教程】Claude Code 安装教程:从 Node.js 到 settings.json 配 TaoToken 2026/9/26 15:47:45

【清晰教程】Claude Code 安装教程:从 Node.js 到 settings.json 配 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Inpaint-web:免费在线图片修复与高清化,浏览器里直接跑 2026/9/26 15:47:39

Inpaint-web:免费在线图片修复与高清化,浏览器里直接跑

Inpaint-web:免费在线图片修复与高清化,浏览器里直接跑 【免费下载链接】inpaint-web A free and open-source inpainting & image-upscaling tool powered by webgpu and wasm on the browser。| 基于 Webgpu 技术和 wasm 技术的免费开源 inpaintin…

阅读更多 →
DesignForClines 配 TaoToken:ChangeClinesWidth 参数与 config.toml 骨架 2026/9/26 15:47:39

DesignForClines 配 TaoToken:ChangeClinesWidth 参数与 config.toml 骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉