我这两个月一直在做一件事:给自己手头几个同时在跑的项目做一个统一的 LLM 访问层。一开始只是不想每个项目里都写一份 OpenAI SDK 调用代码,后来发现事情远不止“封装 API”这么简单——云端 API 贵、延迟波动大、有些数据不能出内网、端侧跑个小模型又快又便宜但能力有限。于是这个项目慢慢长成了一个端云协同的 LLM 网关,目前已经在内部跑通了完整链路,现在想找 3~5 位真正在做 LLM 应用开发的开发者来一起做一轮真实场景测试。
这个项目的核心定位很简单:在端侧(手机、PC、边缘盒子)和云侧(GPU 集群、公有云 API)之间,做一个智能的 LLM 请求分发层。应用层不需要关心请求最终被哪个模型处理,网关会根据设备当前状态、网络情况、任务复杂度、成本预算这些因素,自动决定是走本地小模型、还是转发到云端大模型。它解决的是一组很实际的问题:想让 LLM 应用跑得便宜、跑得快、数据还能留在本地,但又不想在应用代码里写一堆复杂的路由逻辑。
如果你正在做 AI 应用、AI 硬件、端侧智能或者私有化部署相关的东西,这个网关对你应该是直接能用的。我写这篇文章是想把项目的设计思路、核心实现、测试计划,以及这段时间踩过的坑都摊开来讲,也希望看到这篇内容的开发者有兴趣来当第一批真实测试用户。
1. 端云协同网关的设计思路与核心场景拆解
1.1 为什么不能直接“全上云端”或者“全跑本地”
先说个我在项目初期反复纠结的问题:为什么一定要做端云协同,而不是干脆全云端或者全端侧。
全云端是最省事的,OpenAI、Claude、国内各家大模型 API 都很成熟,SDK 一调就能用。但我在实际项目中遇到了几个绕不过去的坎。第一个是成本,做过 AI 功能的人都懂,token 费用看着单价不高,一旦业务量上来,每个月账单是实打实的钱。第二个是延迟和稳定性,有些场景对响应时间特别敏感,比如语音助手、实时翻译、交互式对话,云端 API 的 P99 延迟经常让人崩溃。第三个是数据合规,我手上有个医疗相关的项目,患者的对话记录根本不能出内网,全云端方案直接就毙了。
全跑本地也有问题。端侧模型的能力天花板摆在那里,跑个 Llama 3 8B 量化版或者 Qwen 2.5 7B,处理简单问答、摘要、分类没问题,但复杂推理、代码生成、长文本理解就明显拉胯。而且不是所有设备都能跑模型,很多用户的手机和 PC 没有足够的算力。我做过一个测试,在同一台 MacBook Pro 上跑 7B 模型,生成速度大约 20 token/s,而云端 GPT-4o 可以到 80 token/s 以上,差距是数量级的。
所以端云协同不是“选一个”,而是“两个都要”——让简单请求留在端侧,让复杂请求上云,两者之间还要让用户无感切换。这个思路最初的灵感其实来自传统 CDN 和边缘计算,内容分发靠边缘节点就近响应,回源才到中心服务器。LLM 网关做的也是类似的事:能本地处理的本地处理,处理不了的再“回源”到云端大模型。
1.2 场景拆解:什么请求适合端侧,什么请求必须上云
在确定网关架构之前,我先把自己手上的应用场景全部列出来,逐个分析请求特征,再决定路由策略。这里分享一下我整理的判断维度,做这个网关的同学可以直接参考:
| 场景 | 延迟敏感度 | 数据敏感度 | 任务复杂度 | 推荐路由 |
|---|---|---|---|---|
| 闲聊对话 | 高 | 中 | 低 | 端侧优先 |
| 文本摘要 | 低 | 高 | 中 | 端侧/私有云 |
| 代码生成 | 中 | 中 | 高 | 云端 |
| 情感分析 | 高 | 高 | 低 | 端侧 |
| 复杂推理 | 低 | 低 | 高 | 云端 |
| 实时翻译 | 高 | 中 | 中 | 端侧优先,失败上云 |
| 知识问答 | 中 | 高 | 高 | 私有云/混合 |
这个表看起来简单,实际做的时候每个场景都要细抠。比如“文本摘要”,看起来应该是数据敏感优先走本地,但如果是长文档摘要,端侧模型的上下文窗口根本装不下,强行截断会导致摘要质量崩掉。所以网关的路由逻辑不能只看任务类型,还要结合输入长度、模型上下文窗口、端侧可用内存这三个实时参数来综合判断。
我在网关里把路由策略设计成了一个可配置的分层决策器:第一层看数据合规策略(数据绝对不能出内网的请求直接锁死到本地),第二层看任务复杂度(通过提示词长度、预期生成长度、任务类型标签来判断),第三层看设备实时状态(当前端侧推理服务的负载、可用显存/内存、电池状态)。三层跑完,才决定最终走哪条路。
1.3 与传统 API 网关的本质区别
很多人会问:这不就是 API 网关加了个模型路由吗?我一开始也觉得是,但做深了发现差别非常大。
传统 API 网关做的事情是流量管理:鉴权、限流、负载均衡、灰度发布,它面对的是多个后端服务,每个服务是确定性的,返回结构和行为都是可预期的。LLM 网关面对的是多个模型,每个模型的行为是概率性的,同一个提示词在不同模型、不同温度参数下会得到完全不同的输出。这意味着网关不能做简单的“转发”,它必须理解请求内容,对请求做分类、改写、裁剪,甚至要对模型的输出做校验和兜底。
另外,LLM 网关还多了一个传统网关完全没有的维度:token 成本管理。一个请求该花 0.1 元还是 1 元,网关应该能给出预算控制。我在网关中实现了分用户、分业务的 token 配额,不同业务线设置不同的成本上限,一旦超限可以自动降级到端侧小模型或者限速。这个功能在传统网关里完全没有对应物,也是实际业务中最容易被需要的。
2. 网关核心架构与关键技术选型
2.1 整体架构:五大模块各司其职
这个网关的架构我前后重构了三次,最终稳定成五个核心模块。每个模块解决一类明确问题,模块之间通过事件总线通信,避免强耦合:
- 接入层:负责接收应用请求,统一鉴权、限流、格式转换。对外暴露 OpenAI 兼容的
/v1/chat/completions接口,这样接入方不需要改任何代码,原来的 OpenAI SDK 直接换个 base_url 就能用。 - 路由决策层:核心引擎,负责判断请求应该走端侧还是云侧,以及选择具体哪个模型。这层拥有一套可配置的路由策略系统,支持规则、权重、AI 分类器三种模式。
- 端侧管理模块:通过 WebSocket 长连接维护与端侧推理服务的关系。实时收集端侧状态、模型列表、当前负载,还能远程下发放置在端侧的模型配置。
- 云侧适配层:统一封装各家云端 API 的差异,包括 OpenAI、Anthropic、国内主流模型。实现了统一的超时重试、错误码映射、流式输出协议转换。
- 可观测与成本模块:记录每个请求的全链路 trace,包括路由决策依据、端侧耗时、云侧耗时、token 消耗、预估费用。实时汇总成指标看板。
2.2 技术栈选择与关键依赖
技术选型上我没有追新,全部选了经过验证的稳定方案。后端用 Python + FastAPI,这个选择主要考虑到 AI 生态基本都在 Python 这边,后面要集成向量检索、RAG、模型推理框架都会方便很多。网关本身对性能要求没有核心业务那么极端,FastAPI 的异步能力足够支撑几百路并发,配合 Uvicorn 多 worker 部署,实测单机可以稳定承载 300+ 并发请求。
关键依赖方面,路由决策引擎用了rule-engine这个库来跑规则匹配,比我自己写 if-else 树清晰得多,规则可以写成 JSON 配置下发,不用改代码就能调策略。配置管理用了pydantic-settings,所有配置项支持 YAML 文件与环境变量双重覆盖。异步任务队列用了arq,用于处理流式请求的逐字转发和日志异步落盘。
端侧那部分我单独写了一个轻量客户端,用 WebSocket 和网关通信,通过消息类型区分心跳、状态上报、推理请求、模型切换。通信协议用 Protobuf 序列化,比 JSON 省流量,在弱网环境下明显更稳。端侧 SDK 目前提供 Python 和 C++ 两个版本,Python 版方便快速接入,C++ 版用在资源受限的边缘设备上。
2.3 配置驱动的路由策略设计
网关里最灵活的部分是路由策略系统。我不想让每个业务方都来改代码才能调整路由行为,所以设计成了配置即策略。一份 YAML 文件定义所有路由规则,网关启动时加载,也支持运行时通过管理接口热更新。
一个简化版的策略配置长这样:
route_strategies: - name: "privacy_lock" priority: 100 condition: request_tags: ["medical", "legal"] action: "force_local" - name: "realtime_chat" priority: 80 condition: task_type: "chat" device_capability_score: ">= 60" action: "local_first_with_fallback" - name: "complex_reasoning" priority: 60 condition: task_type: "reasoning" action: "cloud_only" - name: "cost_control_fallback" priority: 40 condition: billing_tier: "free" monthly_token_usage: "> 1000000" action: "degrade_to_local"每条策略包含优先级、匹配条件、执行动作。网关按优先级从高到低匹配,第一条命中的策略胜出。这个设计的核心思路是:合规永远优先,成本控制兜底,性能和能力在中间段自由博弈。实际部署中发现,配置化的好处不仅在于灵活,更在于出了问题可以快速回滚——只需要下发一版新配置,不用重新发布服务。
2.4 为什么没有用现成的开源 LLM Gateway
很多人会问,Kong、APISIX 这些网关也有 AI 插件,为什么不直接用?这里要说下我的调研结论。现有的 API 网关确实加了一些 LLM 相关功能,比如请求转发、API Key 管理,但它们本质上是“流量管道”,不感知模型能力差异,不做请求内容分析,没有端侧调度的概念。另外我也看过几个专门的 LLM Gateway 开源项目,大多停留在云侧多模型路由这个层面,把多个云 API 聚合到一个入口,但没有解决端和云之间的协同问题。
这个项目最重要的是“端云协同”四个字,这是和现有所有方案的核心差异点。网关不仅要知道云端有哪些模型可用,还要知道当前这个设备上有没有模型、是什么模型、现在负载怎么样。端侧模型和云侧模型之间还要能共享对话上下文,一个会话可以在端侧模型跑几轮,再无缝切换上云,上下文不丢。这个能力我目前没有在任何一个开源项目里找到完整实现。
3. 实操过程:从零搭起端云协同 LLM 网关
3.1 本地开发环境搭建与最小闭环
我把完整搭建过程拆成四步,依赖较少,按顺序执行基本十分钟能跑起来。
第一步是准备网关侧环境。先创建虚拟环境,然后安装核心依赖,目前项目锁定在 Python 3.10+:
python -m venv venv-core source venv-core/bin/activate # Windows 用 venv-core\Scripts\activate # 核心依赖 pip install fastapi uvicorn[standard] websockets httpx pydantic-settings pip install rule-engine arq protobuf pyyaml # 数据库,先上轻量的 SQLite,后续迁移 PostgreSQL pip install sqlite-utils第二步是下载端侧推理运行时。端侧推理我目前优先支持 Ollama,因为它对新手最友好,一条命令装完就能跑模型。后续计划加 llama.cpp 原生集成和 MLC-LLM(Apple Silicon 上性能更好)。
curl -fsSL https://ollama.com/install.sh | sh ollama pull qwen2.5:7b-instruct-q4_K_M这个模型大约 4.7GB,在 M 系列 MacBook Pro 16GB 内存上跑起来没有压力,实测生成速度能有 25~30 token/s。
第三步是把端侧客户端跑起来。我写了一个独立的小程序负责和网关通信,它做的事情就是注册设备信息、周期上报状态、接收推理请求并调用 Ollama:
python examples/simple_edge_client.py \ --gateway-url ws://localhost:8000/ws/edge \ --device-id mac-mini-test-01 \ --model qwen2.5:7b-instruct-q4_K_M启动后如果你在网关侧看一下日志,会看到类似这样的注册消息:
{ "type": "edge_register", "device_id": "mac-mini-test-01", "capabilities": { "models": ["qwen2.5:7b-instruct-q4_K_M"], "memory_available_mb": 8392, "inference_backend": "ollama" } }第四步是启动网关主进程,默认监听 8000 端口。启动后随便用 OpenAI SDK 打一个请求,看它能不能自动路由到端侧模型:
from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="test-key") resp = client.chat.completions.create( model="default", # 网关会根据策略自动解析 messages=[{"role": "user", "content": "你好,简单介绍一下你自己"}], stream=False, ) print(resp.choices[0].message.content)如果配置了“default 模型优先走端侧”,这个请求会在本地 Ollama 上完成推理,在网关日志里会看到一条路由记录,标注edge而不是cloud。到这里,最小闭环就跑通了。
3.2 云端模型接入与多供应商适配
端侧闭环通了之后,接入云侧就相对简单了。为了兼容性考虑,云侧适配层做了两层设计:第一层是统一请求格式,把各家 API 的差异(请求体、鉴权方式、错误码)封装掉;第二层是统一的流式协议,因为流式对话是 LLM 最常见的交互方式,各家 SSE 事件格式不同,网关要把它归一化成 OpenAI 兼容的格式再转发给客户端。
我目前接入了三家:OpenAI 官方 API、Anthropic Claude,和国内一家主流模型服务商。配置很容易,在cloud_providers.yaml里填好 API Key 和 base_url 就行:
cloud_providers: openai: type: openai_compatible base_url: https://api.openai.com/v1 api_key_env: OPENAI_API_KEY default_model: gpt-4o-mini claude: type: anthropic_compatible base_url: https://api.anthropic.com api_key_env: ANTHROPIC_API_KEY default_model: claude-3-5-sonnet-latest云侧接入有一个特别容易踩的坑:流式响应超时。云端模型在长文本生成时,如果中间有较长的思考停顿,连接可能被误判为超时而断开。我的做法是在适配层把“首字延迟”和“字间延迟”分别设置超时时间,首字延迟放宽到 60 秒,字间延迟放宽到 30 秒,同时启动一个心跳包定期向客户端发送 keep-alive 事件,避免客户端代理层先切断了连接。
3.3 端云切换的会话上下文同步机制
这个功能是整个项目里技术挑战最大的一块。端到端的对话,用户跟设备聊了五轮,前五轮在端侧模型,第六轮因为任务复杂被路由到了云侧,怎么保证第六轮对话还能记住前五轮说了什么?
我的方案是做一个统一的会话状态管理层。对话上下文以标准格式存储在网关侧(目前是内存+Redis,后续持久化到数据库),每一轮对话结束时,由端侧或云侧模型的响应触发上下文更新。无论下一次请求路由到哪里,网关从统一存储里取出完整上下文,按目标模型的要求重新组装 messages。
这里有个关键问题要处理,端侧小模型的上下文窗口通常只有 8K,云侧大模型有 128K 甚至 200K。如果端侧已经积累了 6K 的上下文,切到云侧自然没问题,但如果云侧积累了 30K 的上下文,切回端侧就装不下。我的方案是:路由决策时必须带上上下文长度作为约束条件,上下文超过端侧窗口的请求直接禁止路由到端侧。同时在端侧切换时自动做一轮摘要压缩,用一个较小模型把长历史压缩成 500 token 左右的摘要,塞进系统的 system prompt 里,既保留核心信息,又不超窗口。
3.4 可观测性与成本统计的落地实现
做 LLM 网关如果不做可观测性,上线就是灾难。我之前被坑过一次——某个业务方反馈“模型怎么越答越差”,排查半天才发现是路由策略静默失效,所有请求都打到了一个小参数模型上,而监控面板只看了整体成功率,完全没发现。
现在网关内置了一套完整的链路追踪机制,每个请求从进入网关开始就生成一个 request_id,连同路由决策的关键因子一起记录到日志里:
{ "request_id": "req_8f3k2d", "route_decision": "cloud", "reason": "task_type=reasoning, input_tokens=4521, device_available=false", "target_provider": "openai", "target_model": "gpt-4o-mini", "latency_total_ms": 1842, "latency_breakdown": {"edge": 0, "cloud": 1842, "overhead": 37}, "usage": {"input_tokens": 4521, "output_tokens": 312}, "estimated_cost_cny": 0.16 }成本统计这里多说一句。各家 API 的价格计算方式不一样,有的按百万 token 计价,有的按调用次数计价,有的有阶梯价。网关把这些都抽象成了统一的计价规则,在配置里定义每款模型的单价,然后按实际使用量实时累计。这个功能上线后,我发现很多团队的 AI 成本存在明显的重复消费问题——同一个模型在多个应用里各自调用,没有共享缓存,同样的提示词同样的输入,每次都重算一遍。所以在网关里加了一个语义缓存模块,基于 embedding 相似度判断两个请求是否等价,等价请求直接返回缓存结果。实测这个功能能省 20%~35% 的 token 费用。
4. 测试计划与真实问题排查实录
4.1 寻找 3~5 位开发者的测试目标与接入方式
做这个项目最需要的就是真实场景测试。我需要的是正在做 LLM 应用的开发者,也欢迎自己做 AI 硬件或者私有化项目的朋友。你不需要部署我的完整代码,仓库会提供一个 Docker Compose 一键启动的网关侧环境,端侧客户端也支持一键安装,你只需要花半小时把网关接入到自己的现有项目里,然后把平时的流量打进去观察路由行为。
测试的核心目标有三个维度。第一是路由准确率,端侧和云侧的分流是否符合场景预期,有没有该上云的被挡在端侧、该留本地的被发到云上的情况。第二是端云切换平滑度,对话过程中意外切换模型时,用户是否能感知到切换,响应是否中断,上下文是否连贯。第三是成本和延迟的实际收益,对比接入网关前后的账单和 P50/P95 延迟,判断端云协同到底省了多少钱、快了多少。
接入方式我已经做了尽量简化,如果你用 OpenAI SDK,只需要改一行代码:
# 原来的写法 client = OpenAI(base_url="https://api.openai.com/v1", api_key="sk-xxx") # 接入网关后 client = OpenAI(base_url="http://localhost:8000/v1", api_key="your-gateway-key")然后你正常调client.chat.completions.create(),网关全接管。如果你用的是 LangChain 或 LlamaIndex,它们的底层也是 OpenAI SDK,同样只需要改 base_url。
4.2 实测中发现的三个典型问题
开发过程中我自己跑了很多轮测试,踩了一些坑,挑三个最典型的分享。
问题一:端侧模型被“高估”了。我在测试中发现,小模型的稳定性远不如大模型,同样的请求,有时回答质量还行,有时就直接崩溃。一开始网关把大量请求都路由到端侧,结果用户反馈“时好时坏”。后来我加了一个机制:每个端侧请求结束后,网关会对输出做个质量评分,评分方式是检查输出长度是否合理、是否有重复循环文本、是否包含低质量的“嗯”“啊”等填充词。连续三次低分,网关自动把该设备在一段时间内的流量切到云端。
问题二:弱网环境下的端侧不可用。我的一个测试场景是手机连着不太稳定的 Wi-Fi,端侧模型虽然跑在本地不需要网络,但网关和端侧之间的管理通道走的是 WebSocket,网络抖动会导致连接断开,网关误判端侧不可用,把本该走本地的请求全部发到了云端。解决办法是端侧客户端加了本地兜底缓存,网络断开时缓存请求,恢复后重放,同时网关加入了 graceful degradation 机制,状态上报超时不会立即判定离线,而是等到两次心跳间隔 + 5 秒宽限期。
问题三:上下文切换导致“精分”。有一次测试中,用户在端侧跟设备聊了十几轮家常,突然问了一个数学题,网关果断路由到云端大模型。但因为端侧上下文比较长,压缩摘要时把“用户刚才说自己在准备考研”这个关键背景丢掉了,云端大模型给出的回答风格非常正式,跟之前端侧那种轻松的聊天风格完全不一致,用户明显感觉到“换了个 AI 在跟我说话”。这提醒我:上下文传递不仅要传信息,还要传风格和语气。现在压缩摘要时会把系统提示词里的角色设定和语气要求一并传过去,并约束下游模型“延续之前的语气”。
4.3 常见问题速查与避坑建议
整理了一份自己排查问题的速查表,涉及几个最常遇到的坑,其他开发者接入时可以直接对照:
| 现象 | 可能原因 | 排查方式 | 解决办法 |
|---|---|---|---|
| 请求全部打到云端,端侧不生效 | 设备能力评分过低或状态上报失败 | 查看网关/v1/edges接口确认设备在线状态 | 检查端侧客户端日志,确认 Ollama 服务是否启动 |
| 路由规则的优先级配置混乱 | 多条规则互相覆盖但没有优先级概念 | 开启网关debug_route=true查看决策链日志 | 给每条规则配独立优先级,避免非精确匹配 |
| 流式请求经常中断 | 云侧适配层超时设置过短 | 抓包看 SSE 连接的断点 | 调整首字延迟和字间延迟超时时间 |
| 端侧模型输出质量不稳定 | 量化等级过低或模型参数不足 | 对比同一请求端侧/云侧输出 | 提升量化等级,或为特定任务配置强制云侧路由 |
| token 费用超预算 | 没有设置配额或语义缓存未开启 | 查看成本看板定位高消耗业务 | 配置业务级 token 配额,开启语义缓存 |
| 切换模型后对话出现“精分” | 上下文压缩丢失了风格信息 | 检查压缩摘要中是否包含角色和语气标记 | 压缩时保留风格描述字段 |
4.4 后续版本规划与可扩展方向
网关目前还在 0.2 版本阶段,核心链路已经通了,但离我理想中的形态还有距离。规划中的 0.3 版本主要有三件事。
第一是更细粒度的端侧任务分解。现在一个请求只能整体路由到端侧或云侧,0.3 会支持任务拆分——比如一个复杂的请求先由端侧模型做意图识别,把任务拆成几个子任务,简单子任务直接端侧解决,复杂子任务上云,最后再汇总结果。这在代码生成、文档分析这类多步骤任务里能显著节省云侧 token 消耗。
第二是多设备联动调度。如果用户家里有多个端侧设备,比如手机、电视、NAS、树莓派,网关应该能识别这些设备的算力差异,把一个任务拆分到多个设备上并行推理。这个想法目前还在验证阶段,因为涉及异构设备间的模型同步和结果聚合,工程复杂度不低。
第三是插件化扩展机制。计划把 RAG、提示词优化、输出校验这些能力做成网关插件,业务方按需加载,不用自己再搭一套。目前网关已经预留了插件接口的框架设计,核心改动是对外暴露事件回调钩子,插件可以订阅请求前、响应后、路由决策前三个生命周期事件。
我在测试中的一点体会
做这个项目断断续续花了两个月,最大的感受是:端云协同 LLM 网关不是一个纯技术问题,而是一个系统工程问题。技术上最难的其实不是路由算法、不是模型适配,而是如何在不可靠的真实环境中维持体验的一致性——网络会抖,端侧模型会抽风,云端 API 会超时,用户不会关心你路由到了哪,只关心回答快不快、准不准、贵不贵。这三个目标互相牵制,必须通过细粒度的可观测数据来不断修正策略。现在我每天都会花十分钟看网关的路由决策日志,隔几天就会调整一版路由规则,这个调优过程本身已经成为我最重要的经验来源。如果你也对端云协同 LLM 网关这个方向感兴趣,非常欢迎来真实环境里跑一跑,到时候把你的使用反馈丢给我,我这个阶段最需要的,就是这些来自实际场景的打磨意见。