双轨大模型路由架构:Ollama本地与云端API的高可用实践(含容灾降级)

发布时间:2026/9/8 11:10:16
双轨大模型路由架构:Ollama本地与云端API的高可用实践(含容灾降级) 最近一个月我把手头一个 AI 应用从“纯云端 API”改造成了“本地 Ollama 云端大模型”的双轨大模型路由架构。起因很实际云端接口偶尔报限流账单越跑越高还有一部分用户数据敏感不适合全部抛给第三方接口。所以我引入了一条端侧 Ollama 的边缘推理链路并在中间加了一个路由和容灾降级层。这篇东西就是把这套架构从设计到落地、从模型管理到故障排查的完整过程整理出来。如果你是刚在本地跑通 Ollama、又在接云端模型 API想把两条链路组合成一个高可用系统的开发者这篇文章应该能帮你省掉不少弯路。我不打算讲太抽象的理论重点放在怎么做、为什么这么做、以及踩过哪些坑。1. 双轨路由架构的核心逻辑1.1 端侧 Ollama 边缘推理的价值边界最开始接触 Ollama纯粹是图它省事。一条命令把开源模型拉到本地再用它提供的 OpenAI 兼容接口接进自己的代码整个体验跟调用云端 API 几乎一样。但用久了你会发现本地推理的价值不止“省钱”这么简单。对隐私敏感的场景数据不出设备本地模型有天然优势在弱网甚至断网环境下本地链路能保证基本服务不中断响应延迟上小模型在本地显卡推理通常比走公网请求大模型更稳定。说白了边缘推理解决的是“可控、可离线、可低成本”的问题。但你也得清醒地认识到边界。本地跑 7B 或 14B 模型处理摘要、分类、抽取这类结构化任务问题不大一旦遇到复杂逻辑推理、长文档全局理解、严苛的指令遵循能力断层就非常明显。所以“端侧”不是要替代“云端”而是补位。1.2 云端大模型的不可替代性云端的 GPT-4 级别模型依然是复杂任务的性能天花板。代码生成、多步工具调用、超长上下文理解这些场景靠本地小模型硬扛效果会肉眼可见地变差用户感知很强。但云端 API 的脆弱点也很突出。限流是家常便饭网络抖动会导致超时上游服务故障时会批量报错。如果你把整个应用的单点押在云端 API 上等于是把核心链路的稳定性交给别人。我见过不少团队平时跑得好好的一到高峰期就被限流打崩。1.3 双轨路由的设计目标双轨路由不是“本地优先”或“云端优先”二选一而是做一层策略分发。我最终落地的设计是默认把高价值、复杂请求发给云端模板化、隐私敏感、轻量任务直接本地处理云端不可用或限流时自动降级到本地本地能力不足时再升级回云端。这套设计的本质是在成本、质量、稳定性三者之间取一个动态平衡。实现上就是加一层统一的路由网关屏蔽上游差异把两个 Provider 的切换规则收敛到一处。2. Ollama 端侧部署与环境准备2.1 安装、镜像加速与 D 盘迁移Ollama 的安装本身不复杂官网下载安装包双击运行就行。但很多人第一步就被卡住从官网拉安装包特别慢。我实测有两个解决办法一是找第三方维护的下载镜像站下载速度会比官网直连快很多二是让公司内网或朋友帮你下载好安装包再传给你。另外一个高频问题就是怎么把 Ollama 安装到 D 盘。安装包默认把模型存放在 C 盘用户目录C 盘空间紧张的话很容易爆。解决办法是设置环境变量新建用户环境变量OLLAMA_MODELS值设为D:\ollama_models设置完成后先退出托盘区的 Ollama 进程再重新启动命令行执行ollama list看到模型文件确实出现在 D 盘指定目录就说明生效了这里有个细节修改环境变量后不重启 Ollama 服务是不生效的。很多人改完直接跑发现模型还在 C 盘就是这个原因。2.2 模型拉取与常见网络问题模型文件通常有 4GB 到 8GB拉取速度受网络链路影响很大尤其是ollama pull qwen2.5:7b这种大文件卡在 10% 到 30% 的情况非常常见。我的建议是配置镜像加速地址这一步对下载体验改善非常明显。镜像加速的具体做法是在启动 Ollama 服务时注入加速地址环境变量。Windows 下可以直接在系统环境变量里加OLLAMA_MIRROR或者在启动命令中显式指定。社区里有很多公开可用的镜像选择响应快的即可。还有一个省时技巧如果你认识的人已经下载好了同一个模型直接拷贝他的模型目录到你的OLLAMA_MODELS路径下重启 Ollama 就能识别能省掉几小时的下载时间。需要注意的是跨系统拷贝时最好保持目录结构完整缺文件会导致模型加载失败。2.3 Ollama 服务与 API 调用要点Ollama 默认监听127.0.0.1:11434提供了两组 API原生接口和 OpenAI 兼容接口。原生接口最常用的是POST /api/chat对话补全POST /api/embed文本向量化GET /api/tags列出本机模型OpenAI 兼容端点是http://localhost:11434/v1这个端点太关键了。因为你原来所有接 OpenAI 的代码只需要把base_url改掉把api_key随便填一个非空字符串就能直接切换到本地模型。Dify、CC Switch、Claude Code 这些工具能接 Ollama靠的都是这层兼容。调用时有个参数容易忽略就是keep_alive。默认情况下模型在空闲 5 分钟后会被从显存卸载。频繁切换模型时每次都要重新加载权重首字延迟可能飙到十几秒。设成-1可以常驻内存但代价是显存一直被占用跑别的大模型时容易 OOM。我看场景来定常驻我自己最常用的一个模型其余模型随用随载。2.4 常见集成场景Dify、CC Switch 与 Claude CodeDify 接 Ollama 非常简单模型供应商列表里找到 Ollama填上base_url和模型名就行。需要注意的一点是如果你的 Dify 跑在 Docker 容器里访问宿主机不能用localhost要填http://host.docker.internal:11434这是我第一次配置时踩得最深的坑。CC Switch 是很多人在用的 Claude Code 配置切换工具。它本质上是维护多套环境配置让你在官方云端 API 和本地模型端点之间快速切换。claude code cc switch ollama这套组合社区里讨论热度很高实际就是把 Claude Code 的请求基地址切到 Ollama 的/v1端点。我实测下来本地 7B 模型跑 Claude Code 的操作能力比较有限代码生成质量不稳定但作为备用链路完全没问题。要是云端的 Anthropic API 挂了切到本地 Ollama 至少还能完成简单的问答、摘要任务不至于整个工具瘫痪。3. 路由层与容灾降级架构实现3.1 为什么需要一个独立路由层最简单的双轨写法是硬编码 if/else请求来了判断一下类型要么发本地要么发云端。这种方案做 Demo 可以上线就麻烦。上游服务的健康状态是动态的限流和故障的出现往往没有征兆超时和重试策略需要统一收敛降级的触发要自动化不能等人大半夜手动改配置还必须留日志知道每个请求实际走了哪条链路。所以我把双轨的能力抽象成一个独立的LLMProxy层向上对业务方屏蔽本地和云端的差异向下管理两个 Provider。业务方只需要说“这个问题偏复杂走云端”或者“这条数据敏感走本地”具体怎么路由、失败后怎么降级由路由层决定。3.2 上游适配层设计我定义了统一的 Provider 接口每个上游实现自己的适配器chat(messages, options)对话补全支持流式和非流式embed(texts)文本向量化health_check()健康检查本地 Provider 把请求转发到 Ollama 的/v1/chat/completions云端 Provider 转发到云端 API。路由层拿到请求后先按策略选中一个上游调用对应适配器如果抛异常再按降级顺序尝试下一个。这样做的好处是上层逻辑完全不用关注上游协议细节。以后想接第三家云厂商只需要写一个新的适配器路由规则一行不用动。3.3 降级策略与参数设计降级策略里我重点做了五件事超时控制本地模型非流式响应可能很慢我给的超时是 60 秒云端 API 正常在 3 到 10 秒返回超时设 30 秒足够。快速重试云端失败后只重试一次避免雪上加霜。熔断器连续失败 5 次就短路这个上游 30 秒期间直接走另一条链路。偏好路由业务方可以用prefer字段指定链路偏好比如“简单分类请求强制走本地”。手动开关维护期可以手动下线某个上游路由层自动把流量切走。本地链路和云端链路还有一个核心区别是成本结构。本地是固定硬件成本跑多跑少边际成本很低云端是按 token 计费请求越长越贵。所以我的策略里有一条默认规则短文本、结构化任务优先本地长文本、复杂生成优先云端。3.4 核心代码实现我简化一下核心逻辑方便你理解。这里先用 Python 写一个最基础的版本import requests class LLMProvider: def chat(self, messages, **kwargs): raise NotImplementedError class OllamaProvider(LLMProvider): def __init__(self, base_urlhttp://localhost:11434/v1): self.base_url base_url def chat(self, messages, **kwargs): resp requests.post( f{self.base_url}/chat/completions, json{ model: kwargs.get(model, qwen2.5:7b), messages: messages, stream: False, }, timeoutkwargs.get(timeout, 60), ) resp.raise_for_status() return resp.json() class CloudProvider(LLMProvider): def __init__(self, api_key, endpoint): self.api_key api_key self.endpoint endpoint def chat(self, messages, **kwargs): resp requests.post( self.endpoint, headers{Authorization: fBearer {self.api_key}}, json{ model: kwargs.get(model, cloud-model-name), messages: messages, stream: False, }, timeoutkwargs.get(timeout, 30), ) resp.raise_for_status() return resp.json()然后是最关键的路由降级逻辑class LLMRouter: def __init__(self, providers): self.providers providers def chat(self, messages, prefercloud): # 按偏好决定尝试顺序 if prefer local: order [local, cloud] else: order [cloud, local] last_error None for name in order: try: result self.providers[name].chat(messages) # 一定要把实际路由结果打点 log_route(name) return result except Exception as e: last_error e log_warning(name, str(e)) raise last_error真实生产环境还要在上面叠三个能力asyncio并发控制、熔断器状态记录、以及观察性日志。比如熔断器可以这样设计每个 Provider 维护一个连续失败计数达到阈值后进入open状态直接跳过该 Provider一段时间后进入half-open放一个试探请求成功则闭合熔断器。这里有一个非常容易被忽略的问题流式响应。上面示例用的是非流式但实际产品基本都要流式输出。降级时如果上游从“流式”切到“非流式”前端的解析逻辑会直接出问题。我的做法是统一对外提供流式接口内部无论上游返回什么路由层都把它转成统一的 SSE 格式输出。3.5 上下文与 Embedding 一致性双轨路由还有一个隐蔽的坑上下文长度和向量维度。本地 7B 模型的上下文窗口可能是 8K 或 32K云端模型可能是 128K。如果某次请求带了很长的历史记录降级到本地之前必须做上下文裁剪否则直接报错。Embedding 一致性更关键。假设你的知识库是用云端 Embedding 模型生成的 1536 维向量检索时换成本地 Embedding 模型输出的维度可能只有 1024 或 768维度都对不上检索直接崩。所以我在架构里固定只用一套 Embedding 模型做向量化对话模型可以随便切但向量模型永远不变。Ollama 本身可以通过/api/embed生成向量也提供 OpenAI 兼容的/v1/embeddings端点。如果知识库对向量质量要求高建议单独用一个本地 Embedding 模型比如nomic-embed-text并且在全链路中锁定它。4. 踩坑实录与排查技巧4.1 Ollama 连接失败error: could not connect to ollama server这个报错我在群里看到无数遍基本都是下面几种情况Ollama 服务没有启动。Windows 下安装后一般会自动常驻托盘但版本更新或者异常退出后不会自动拉起来。直接执行ollama serve能解决。环境变量OLLAMA_HOST设置后没重启服务。局域网访问时监听地址还是默认的127.0.0.1。想让其他机器访问必须设置OLLAMA_HOST0.0.0.0并确保防火墙放行 11434 端口。端口被占用。11434 被别的进程占住时Ollama 会启动失败换端口即可。排查建议按顺序走先看进程是否存在再看端口监听是否正常最后看客户端请求的地址能不能通。4.2 模型下载慢、镜像加速配置模型拉取速度问题我踩了很久。最后确定的方案配置镜像加速地址然后错峰拉取。大型模型的下载在晚间或凌晨速度会明显好于白天高峰期。另外可以用ollama pull --help看看当前版本是否支持一些额外的参数不同版本的行为差异不小。如果你在公司内网还可以考虑在内网服务器上部署一次然后通过内网分发模型文件这样团队所有人都能走内网拉取速度和稳定性都比公网靠谱得多。4.3 显存与内存不足的处理本地跑模型时 OOM 是最常见的运行问题。Ollama 默认行为是GPU 显存不够时自动回退到 CPU 推理但这会导致响应速度断崖式下降。我的经验是把OLLAMA_MAX_LOADED_MODELS设为 1强制同一时间只保留一个模型在显存里避免模型间互相挤兑。另外当多个并发请求进来时默认会导致多个模型同时加载显存直接被吃满。可以通过设置OLLAMA_NUM_PARALLEL控制每个模型的并发请求数正常情况下设为 1 或 2 就够用。如果显存实在紧张优先选择量化版本模型比如 Q4_K_M 的 7B 模型只需要 4GB 左右的显存比原版节省一半还多。4.4 Windows 服务自启与图形化管理Windows 下 Ollama 的“服务自启”偶尔会失灵解决办法是创建一个计划任务开机时静默执行ollama serve保证链路稳定。如果你想用图形界面管理模型列表、查看日志社区有一些开源 UI 工具可以选择但注意选支持 Docker 或本地进程绑定的方案否则又要多维护一套服务。4.5 双轨容灾真正落地时容易忽略的细节几个大家很容易踩的细节我集中列一下上下文裁剪必须在降级前完成。本地模型放不下超长请求实际做法是保留最近的系统提示和最后几轮对话丢弃中间内容。系统提示词的差异。云端模型对 system prompt 的指令遵循能力更强切到本地模型后同样的提示词效果可能打折扣建议为本地链路单独维护一套精简版 system prompt。打点日志必须有路由字段。线上出问题时你首先要能确认哪个用户请求实际走了哪条链路否则排查无从下手。业务观测不能只看成功率。降级到本地后接口成功率可能还是 99%但回答质量下降会导致用户满意度下降。我加了一个反馈按钮让用户对每次回答简单评分用这个评价来衡量降级是否“过度”。最后说一个我实际体会最深的点双轨路由的自动降级逻辑不是最难的难的是让团队信任降级后的服务不会砸口碑。我的做法是分两阶段灰度第一阶段只把 10% 的流量切到本地链路对比满意度和延迟确认没问题再逐步放开。同时保留一个“强制云端”的开关一旦本地链路出现质量滑坡可以一键切回。这个开关给了团队信心也让这套架构在实际项目中真正落地并持续运行了下来。