我用一个轻量级网关,解决了团队多模型接入的四个痛点

2 阅读1分钟

一、先说说背景:为什么非得搞个网关?

2026 年,我们团队内部的 AI 工具链大概长这样:

  • 写代码用 Codex(编程上下文理解最好)

  • 处理文档用 GPT-4o(多模态支持完善)

  • 内部知识库用 本地 Qwen(数据不出域)

  • 成本敏感场景用 DeepSeek-V3(便宜量大)

看起来美好,实际接入时全是坑:

  1. 协议碎片化:OpenAI 是 /v1/chat/completions,Anthropic 是 /v1/messages,Codex 虽然兼容 OpenAI 协议,但不同模型的 model 字段命名规则完全不同。

  2. 密钥管理灾难:每个项目里散落着不同的 OPENAI_API_KEY,轮换一次要改十几个仓库。

  3. 网络抽风:直接调用官方 API,超时、断连、IP 受限是家常便饭。

  4. 成本盲盒:没有用量监控,月底看账单像开盲盒。

最痛的一次:某个后端服务因为直接持有 OpenAI 密钥,被同事误传到 GitHub,第二天 token 就被刷光了。

痛定思痛,我们在应用层和模型层之间,加了一层AI API 网关(也叫模型网关)。这篇文章聊聊架构设计和落地踩坑。

二、网关架构:四个核心模块

整体链路很简单,但每个模块都有讲究:

plain

┌─────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  你的应用    │────▶│   AI API 网关    │────▶│  各模型服务商   │
│ (Codex/自研 │     │ ·协议转换        │     │ (OpenAI/Claude/ │
│  系统/脚本)  │     │ ·密钥托管        │     │  DeepSeek等)    │
└─────────────┘     │ ·智能路由        │     └─────────────────┘
                    │ ·用量审计        │
                    └─────────────────┘

2.1 协议转换层:屏蔽模型差异

Codex 用的是 OpenAI 协议,但如果我们想让它无感切换到 Claude 或 DeepSeek,网关就要做请求翻译。

Python

# 简化版:OpenAI 请求 → 多模型适配
class ModelAdapter:
    def convert_request(self, body: dict, target_provider: str) -> dict:
        """将标准 OpenAI 请求转译为不同厂商格式"""
        messages = body.get("messages", [])
        model = body.get("model", "gpt-4o")
        
        if target_provider == "anthropic":
            # Claude 的 messages 格式与 OpenAI 略有差异
            return {
                "model": model.replace("gpt-4o", "claude-sonnet-4"),
                "messages": messages,
                "max_tokens": body.get("max_tokens", 4096),
                "temperature": body.get("temperature", 0.7),
                "stream": body.get("stream", True)
            }
        elif target_provider == "deepseek":
            return {
                "model": model.replace("gpt-4o", "deepseek-chat"),
                "messages": messages,
                "stream": body.get("stream", True)
            }
        # 默认透传 OpenAI 格式
        return body

关键点content 字段的兼容性。Claude 支持 content 为数组(text + image 混合),早期 OpenAI 只接受字符串,网关层必须做扁平化处理。

2.2 智能路由层:不只是转发

网关应该根据策略选最优上游,而不是无脑转发:

class SmartRouter:
    def __init__(self):
        self.providers = {
            "o3": [
                {"name": "azure-east", "latency": 120, "cost_per_1k": 0.005, "healthy": True},
                {"name": "openai-official", "latency": 200, "cost_per_1k": 0.005, "healthy": True},
            ],
            "claude-sonnet-4": [
                {"name": "aws-bedrock", "latency": 150, "cost_per_1k": 0.003, "healthy": True},
                {"name": "official", "latency": 180, "cost_per_1k": 0.003, "healthy": False}
            ]
        }
    
    def route(self, model: str, strategy: str = "cost") -> dict:
        candidates = [p for p in self.providers.get(model, []) if p["healthy"]]
        if not candidates:
            raise Exception(f"模型 {model} 无可用上游")
        
        if strategy == "latency":
            return min(candidates, key=lambda x: x["latency"])
        elif strategy == "cost":
            return min(candidates, key=lambda x: x["cost_per_1k"])
        return candidates[0]  # 轮询

三种策略

  • 开发环境:按延迟优先,响应快最重要

  • 生产环境:按成本优先,或设置"成本上限 + 故障转移"

  • 关键业务:多上游并发,取最快返回

2.3 密钥与配额管理:唯一可信的持有方

网关作为唯一持有上游真实密钥的实体,客户端只拿到带权限的短期 Token:

Python

class KeyManager:
    def __init__(self):
        # 上游真实密钥,只存服务端,永不暴露
        self.upstream_keys = {
            "openai": "sk-real-xxxx",
            "claude": "sk-ant-yyyy"
        }
        self.client_tokens = {}
    
    def issue_token(self, user_id: str, quota: int, allowed_models: list) -> str:
        """签发带模型权限和额度限制的客户端 Token"""
        token = f"gw_{uuid.uuid4().hex[:16]}"
        self.client_tokens[token] = {
            "user_id": user_id,
            "remaining_quota": quota,
            "rate_limit": "100/min",
            "allowed_models": allowed_models
        }
        return token
    
    def validate(self, token: str, model: str) -> bool:
        if token not in self.client_tokens:
            return False
        client = self.client_tokens[token]
        return model in client["allowed_models"] and client["remaining_quota"] > 0

收益

  • 上游密钥零暴露

  • 支持按项目、按模型、按用户细粒度控权

  • 额度耗尽自动熔断,防止半夜被刷爆

2.4 可观测性:网关是最佳埋点位置

class MetricsCollector:
    def log(self, trace_id: str, model: str, provider: str, 
            input_tokens: int, output_tokens: int, latency_ms: int):
        cost = self.calculate_cost(model, provider, input_tokens, output_tokens)
        # 实际可对接 Prometheus + Grafana
        print(f"[{trace_id}] {model}@{provider} | "
              f"tokens={input_tokens}+{output_tokens} | "
              f"latency={latency_ms}ms | cost=${cost:.4f}")

三、实战:让 Codex 走网关

Codex 原生支持 OpenAI 协议,接入网关非常轻量。

3.1 配置环境变量

# 临时生效
export OPENAI_BASE_URL="https://www.aegisy.cc/v1"
export OPENAI_API_KEY="你的网关客户端Token"

# 或者写入 ~/.zshrc 持久化
echo 'export OPENAI_BASE_URL="https://www.aegisy.cc/v1"' >> ~/.zshrc

3.2 网关侧的模型映射配置

# .env
CODEX_DEFAULT=o3
CODEX_MINI=o4-mini
FALLBACK_MODEL=claude-sonnet-4

# 上游配置
OPENAI_BASE_URL=https://www.aegisy.cc/v1
OPENAI_API_KEY=sk-your-real-key

效果:Codex 默认走 o3,轻量任务走 o4-mini,遇到长上下文或复杂推理时网关自动切到 claude-sonnet-4。业务代码零改动,只改网关配置。

四、企业级落地:三个必须考虑的点

4.1 高可用

  • 多上游冗余:至少配 2-3 个不同渠道,一个挂了自动切

  • 熔断降级:连续错误率 > 5% 时自动暂停该上游,避免雪崩

  • 缓存层:Embedding 结果做 Redis 缓存,降低 50%+ 成本

4.2 合规与审计

  • 日志脱敏:请求日志中自动过滤手机号、身份证号等 PII

  • 数据留存:敏感业务 7 天,普通业务 30 天

  • 跨境合规:境外上游 API 需确保网关部署在合规区域

五、踩坑记录(真实经验)

  1. 流式响应的 Buffer 不匹配:SSE 流式传输时,网关→客户端、网关→上游的 Buffer 大小如果不一致,会出现"卡顿"或"断流"。建议统一用 4KB Buffer。

  2. Token 计费偏差:不同上游计费规则不同(有些按字符、有些按 Token),网关层最好自己用 tiktoken 重新计算,避免账单对不上。

  3. 模型切换的上下文兼容性:Codex 的 system 消息和 tool_calls 格式与 Claude 有细微差异,协议转换时要特别注意字段映射。

六、总结

AI API 网关不是"为了中转而中转",而是企业级 AI 架构中必然出现的一层

  • 解耦:业务代码与模型供应商解耦,换模型只改网关配置

  • 治理:统一管控密钥、配额、审计,避免"各自为政"

  • 优化:路由策略 + 缓存机制,压低成本的同时保证体验

  • 扩展:新模型上线,业务端零感知

如果你也在用 Codex、Cursor 或自研 AI 应用,建议尽早把网关层纳入架构设计。不要等到密钥泄露、账单爆炸、网络抽风时才想起来补这一层。