LLM网关项目复盘:多模型统一接入层的架构设计与工程挑战
一、多模型的"巴别塔"问题
一个AI应用同时使用了5个LLM Provider(OpenAI、Anthropic、DeepSeek、通义千问、Moonshot)。每个Provider有自己的SDK、请求格式、响应格式、错误码——代码中散落着5套if-else逻辑。
更麻烦的是:需要做A/B对比(同一问题发给两个模型)、故障切换(OpenAI挂了自动切Anthropic)、成本控制(优先用便宜模型,复杂问题才用贵的)。
这些都指向同一个需求:多模型的统一接入层——不关心"用什么模型",只关心"得到了什么回答"。
二、统一抽象层的设计
核心接口:Provider抽象
type LLMProvider interface { Name() string Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) StreamChat(ctx context.Context, req ChatRequest) (<-chan StreamEvent, error) ListModels(ctx context.Context) ([]Model, error) } // 统一的请求/响应——屏蔽Provider差异 type ChatRequest struct { Model string Messages []Message Temperature float64 MaxTokens int Stream bool } type ChatResponse struct { ID string Model string Content string FinishReason string Usage Usage Latency time.Duration } // OpenAI Adapter type OpenAIProvider struct { client *openai.Client config ProviderConfig } func (p *OpenAIProvider) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { // 内部格式转换:统一格式 → OpenAI格式 resp, err := p.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ Model: req.Model, Messages: toOpenAIMessages(req.Messages), Temperature: req.Temperature, MaxTokens: req.MaxTokens, }) if err != nil { return nil, p.mapError(err) // 错误码标准化 } // 响应标准化:OpenAI格式 → 统一格式 return toChatResponse(resp), nil } // Anthropic Adapter type AnthropicProvider struct { client *anthropic.Client } func (p *AnthropicProvider) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { // Claude格式转换 msg, err := p.client.Messages.Create(ctx, anthropic.MessageNewParams{ Model: req.Model, Messages: toAnthropicMessages(req.Messages), MaxTokens: int64(req.MaxTokens), Temperature: anthropic.Float(req.Temperature), }) // ... }三、智能路由策略
基于内容的路由:
type Router struct { rules []RouteRule defaultProvider string } type RouteRule struct { Matcher func(req ChatRequest) bool Provider string } func (r *Router) Route(req ChatRequest) string { // 代码相关 → DeepSeek(代码能力好且便宜) // 长文本 → Claude(200K上下文) // 默认 → GPT-4o for _, rule := range r.rules { if rule.Matcher(req) { return rule.Provider } } return r.defaultProvider } // 路由规则配置 var defaultRules = []RouteRule{ { Matcher: func(req ChatRequest) bool { return containsKeyword(req.Messages, "代码", "bug", "function") }, Provider: "deepseek", }, { Matcher: func(req ChatRequest) bool { return estimateTokenCount(req.Messages) > 100000 }, Provider: "claude", // 大上下文场景 }, }基于优先级的故障切换:
func (g *Gateway) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { primary := g.router.Route(req) fallbacks := g.getFallbackChain(primary) // [primary, fallback1, fallback2] var lastErr error for _, providerName := range fallbacks { provider := g.providers[providerName] if !g.circuitBreaker.IsOpen(providerName) { resp, err := provider.Chat(ctx, req) if err == nil { return resp, nil } lastErr = err g.circuitBreaker.RecordFailure(providerName) } } return nil, fmt.Errorf("所有Provider都不可用: %w", lastErr) }四、成本控制与可观测性
成本追踪:
每个请求记录实际消耗的Token和费用:
func (g *Gateway) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) { start := time.Now() resp, err := g.routeAndExecute(ctx, req) // 记费用——不管成功失败 cost := g.calculateCost(resp) g.metrics.RecordRequest(MetricRecord{ Provider: resp.Provider, Model: req.Model, Tokens: resp.Usage.TotalTokens, Cost: cost, Latency: time.Since(start), Error: err, }) return resp, err } func (g *Gateway) calculateCost(resp *ChatResponse) float64 { // 各模型定价 pricing := map[string]float64{ "gpt-4o": 0.005 / 1000, // $0.005/1K tokens "gpt-4o-mini": 0.00015 / 1000, "claude-3-opus": 0.015 / 1000, "deepseek-chat": 0.00014 / 1000, } rate, ok := pricing[resp.Model] if !ok { return 0 } return float64(resp.Usage.TotalTokens) * rate }月费用报表:基于记录的数据自动生成各Provider的费用分布。DeepSeek处理了65%的请求但仅占总费用的12%,GPT-4o仅15%的请求却占总费用的72%。
五、总结
LLM网关的核心价值:
- 统一接入层屏蔽了5个Provider的SDK差异——调用方不感知底层用哪个模型
- 智能路由节省了约40%的LLM费用——简单问题用便宜模型,复杂问题才用贵的
- 故障切换让整体可用性从单Provider的99.5%提升到99.9%
- 成本追踪让"每个请求花了多少钱"从黑盒变成白盒
- Provider接口抽象让新增模型接入成本从"改全局代码"降到"实现一个适配器"
当前网关日均处理约2万次LLM调用,月费用约$1800。智能路由策略将DeepSeek/GPT-4o-mini等低成本模型的使用率从初始的40%提升到65%,月节省约$700。
最大的架构取舍:流式响应(streaming)要求Provider适配器支持SSE推送,复杂度是指令式(非流式)的3倍。如果不需要流式输出(如后台批量处理),适配器实现可以大幅简化。网关的流式和非流式两套路径独立维护,是当前架构的主要技术债。