如果你正在构建一个基于大语言模型LLM的应用那么“网关”Gateway这个词很可能已经让你头疼过不止一次了。无论是处理不同模型供应商的API差异还是管理复杂的路由、认证和限流一个健壮的LLM网关都是现代AI应用架构中不可或缺的“交通枢纽”。然而当你搜索“LLM Gateway”时找到的往往是Spring Cloud Gateway、Kong等通用网关的配置教程或是Java生态下的实现。对于前端或全栈开发者尤其是TypeScript技术栈的团队直接上手这些方案存在不小的鸿沟。更棘手的是当出现“unexpected status 502 bad gateway”这类错误时面对黑盒般的网关排查往往无从下手。这篇文章要解决的核心问题就是如何从零理解并构建一个专为LLM设计的、用TypeScript编写的网关。我们将通过深入剖析一个典型的开源LLM Gateway的TypeScript源码不仅学习网关的核心设计模式更将其作为一次绝佳的TypeScript高级特性实战。你会发现读懂这份代码你收获的远不止一个网关工具更是对TypeScript在复杂后端系统中应用能力的深刻认知。我们将重点关注网关如何统一不同模型如OpenAI、Anthropic的API如何实现灵活的路由和负载均衡如何优雅地处理认证、限流和错误以及当出现502错误时代码层面究竟发生了什么本文将从问题出发带你穿透概念直抵实现最终获得一份可落地的技术方案和排错能力。1. 为什么你需要关注一个TypeScript实现的LLM Gateway在LLM应用开发中直接调用模型供应商的API会迅速导致代码臃肿和难以维护。想象一下你的应用需要同时支持OpenAI的GPT-4、Anthropic的Claude以及可能部署在私有云上的开源模型。每个模型的API端点、参数格式、认证方式Bearer Token vs API Key、甚至流式响应的处理都各不相同。没有网关时你的代码可能会充斥着各种if-else分支// 糟糕的示例直接耦合业务逻辑与模型API差异 async function callModel(provider: string, prompt: string) { if (provider openai) { const response await fetch(https://api.openai.com/v1/chat/completions, { method: POST, headers: { Authorization: Bearer ${openaiKey}, Content-Type: application/json }, body: JSON.stringify({ model: gpt-4, messages: [{ role: user, content: prompt }] }) }); // 解析OpenAI格式的响应... } else if (provider anthropic) { const response await fetch(https://api.anthropic.com/v1/messages, { method: POST, headers: { x-api-key: anthropicKey, Content-Type: application/json, anthropic-version: 2023-06-01 }, body: JSON.stringify({ model: claude-3-opus-20240229, max_tokens: 1024, messages: [{ role: user, content: prompt }] }) }); // 解析Anthropic格式的响应... } // ... 更多if-else }这种方式的弊端显而易见高耦合业务逻辑与第三方API细节深度绑定任何一方的变更都会导致大量修改。难扩展每增加一个模型供应商就需要添加新的分支和配置。难维护认证、日志、错误处理、重试逻辑分散在各个分支中。难管控无法统一实施限流、计费、审计等全局策略。一个设计良好的LLM Gateway通过引入“适配器”Adapter和“路由”Routing层将上述复杂性封装起来。它对内提供统一的、标准化的API接口对外负责与各个模型供应商通信。这样你的业务代码只需要和网关对话无需关心底层是哪个模型。而选择TypeScript来实现这样一个网关对于现代开发团队而言具有独特的优势类型安全网关涉及复杂的配置对象路由规则、模型配置、请求/响应体。TypeScript的静态类型检查可以在编译期捕获大量潜在错误如字段拼写错误、类型不匹配等这对于确保网关的稳定性和可维护性至关重要。全栈同构如果你的前端和BFFBackend For Frontend也是TypeScript那么共享类型定义如统一的请求/响应接口将变得非常容易减少上下文切换和序列化错误。丰富的生态Node.js生态有大量成熟的HTTP框架如Express、Fastify、中间件和工具库且大多对TypeScript支持良好能快速搭建高性能网关。易于理解和贡献清晰的接口和类型定义使得代码更易读降低了后续维护和团队协作的成本。接下来我们将深入源码看看这些优势是如何具体体现的。2. 核心概念拆解LLM Gateway的架构与TypeScript类型设计在阅读源码前我们先建立几个关键概念的心智模型。2.1 LLM Gateway的核心组件一个典型的LLM Gateway包含以下核心部分路由Router根据请求的路径、头部信息或内容决定将请求转发到哪个后端模型服务。例如将/v1/chat/completions的请求路由到配置的OpenAI服务。适配器Adapter/Provider封装了与特定模型供应商API交互的所有细节。它负责将网关内部的统一请求格式转换为供应商特定的API调用并将供应商的响应转换回统一格式。认证与鉴权Auth验证客户端请求的合法性例如检查API Key、JWT Token并可能进行权限控制如额度检查。负载均衡与故障转移Load Balancer如果同一个模型配置了多个后端实例网关需要决定将请求分发到哪一个。中间件Middleware处理横切关注点如请求日志记录、指标收集、速率限制、请求/响应转换、错误处理等。配置管理Configuration管理路由规则、模型供应商端点、认证密钥等。2.2 从TypeScript类型看网关设计TypeScript项目的精髓往往在其类型定义中。让我们设想一个简化版的网关核心类型// 定义统一的LLM请求体 interface UnifiedLLMRequest { model: string; // 客户端请求的模型标识如 gpt-4 messages: Array{ role: string; content: string }; stream?: boolean; // 其他通用参数... } // 定义统一的LLM响应体 interface UnifiedLLMResponse { id: string; choices: Array{ message: { role: string; content: string } }; usage?: { prompt_tokens: number; completion_tokens: number }; } // 模型供应商的配置 interface ProviderConfig { name: string; // openai, anthropic apiKey: string; baseURL: string; defaultModel?: string; // 供应商特定配置... } // 路由规则 interface RouteRule { path: string; // 匹配的路径如 /v1/chat/completions provider: string; // 指向哪个ProviderConfig modelMapping?: Recordstring, string; // 将客户端请求的模型映射到供应商的实际模型 // 其他路由条件... } // 网关配置 interface GatewayConfig { providers: ProviderConfig[]; routes: RouteRule[]; port: number; auth?: { /* 认证配置 */ }; rateLimit?: { /* 限流配置 */ }; }这些类型定义清晰地勾勒出了网关的数据结构和边界。优秀的源码会围绕这些核心类型展开确保数据流在严格的类型约束下传递。3. 环境准备搭建TypeScript开发与调试环境在深入代码之前确保你有一个合适的开发环境。我们将使用一个典型的Node.js TypeScript项目设置。3.1 基础环境Node.js: 推荐 LTS 版本如 18.x, 20.x。你可以使用nvm(Node Version Manager) 来管理多个版本。包管理器:npm或yarn或pnpm。本文示例使用npm。代码编辑器: VS Code 是TypeScript开发的首选因为它提供了顶级的TS支持。3.2 初始化项目与安装依赖假设我们要创建一个名为llm-gateway-ts的网关项目。# 创建项目目录并初始化 mkdir llm-gateway-ts cd llm-gateway-ts npm init -y # 安装TypeScript及相关开发依赖 npm install typescript ts-node types/node --save-dev # 安装HTTP服务器框架以Fastify为例它高性能且对TS友好 npm install fastify npm install types/fastify --save-dev # 安装可能的工具库 npm install axios dotenv zod # axios用于向后端发起HTTP请求dotenv用于管理环境变量zod用于运行时类型校验可选但推荐3.3 TypeScript配置创建tsconfig.json文件{ compilerOptions: { target: ES2022, module: commonjs, lib: [ES2022], outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true, declaration: true, declarationMap: true, sourceMap: true }, include: [src/**/*], exclude: [node_modules, dist] }这个配置设定了严格的类型检查将源代码放在src目录编译输出到dist目录。3.4 项目结构创建基本的项目结构llm-gateway-ts/ ├── src/ │ ├── index.ts # 应用入口 │ ├── config/ │ │ └── index.ts # 配置加载与类型定义 │ ├── providers/ # 各模型供应商适配器 │ │ ├── index.ts │ │ ├── openai.ts │ │ └── anthropic.ts │ ├── routers/ # 路由逻辑 │ │ └── index.ts │ ├── middlewares/ # 中间件 │ │ └── index.ts │ └── types/ # 全局类型定义 │ └── index.ts ├── package.json ├── tsconfig.json └── .env.example # 环境变量示例现在环境已经就绪。我们可以开始对照这个结构去理解开源LLM Gateway的源码是如何组织的。4. 源码核心流程拆解请求的一生让我们跟踪一个典型的/v1/chat/completionsPOST请求在网关中的完整生命周期。这个过程清晰地展示了网关各个模块是如何协同工作的。4.1 第一步请求接收与路由匹配src/routers/index.ts网关启动时会根据配置GatewayConfig注册路由。当请求到达时// 伪代码展示路由匹配逻辑 import { FastifyInstance, FastifyRequest, FastifyReply } from fastify; import { findRouteForRequest } from ./route-matcher; import { getProvider } from ../providers; export async function registerLLMRoutes(app: FastifyInstance, config: GatewayConfig) { // 注册统一的LLM API端点 app.post(/v1/chat/completions, async (request: FastifyRequest, reply: FastifyReply) { // 1. 认证中间件可能已通过全局中间件处理 // 2. 根据请求信息路径、头部、body查找匹配的路由规则 const routeRule findRouteForRequest(request, config.routes); if (!routeRule) { return reply.status(404).send({ error: No route matched }); } // 3. 根据路由规则找到对应的供应商配置 const provider getProvider(routeRule.provider, config.providers); if (!provider) { return reply.status(502).send({ error: Provider ${routeRule.provider} not configured }); } // 4. 获取对应的供应商适配器实例 const adapter getAdapter(provider.name); // 5. 准备转发请求可能涉及模型名称映射、参数转换 const upstreamRequest prepareUpstreamRequest(request, routeRule); // 6. 调用适配器将请求发给真正的模型API try { const upstreamResponse await adapter.call(upstreamRequest, provider.config); // 7. 将供应商响应转换回统一格式并返回给客户端 const unifiedResponse transformToUnifiedFormat(upstreamResponse, provider.name); return reply.send(unifiedResponse); } catch (error) { // 8. 错误处理转换错误信息记录日志返回适当的HTTP状态码 return handleUpstreamError(error, reply); } }); }关键点findRouteForRequest是路由的核心。一个高级的实现可能支持基于请求头如X-Model、请求体中的model字段或URL路径参数进行路由。4.2 第二步适配器工作流src/providers/openai.ts适配器是网关与具体模型API对话的桥梁。它的核心职责是“转换”。// OpenAI适配器示例 import axios, { AxiosInstance } from axios; import { ProviderAdapter, UnifiedLLMRequest, UnifiedLLMResponse, ProviderConfig } from ../types; export class OpenAIAdapter implements ProviderAdapter { private client: AxiosInstance; constructor(private config: ProviderConfig) { this.client axios.create({ baseURL: config.baseURL || https://api.openai.com/v1, headers: { Authorization: Bearer ${config.apiKey}, Content-Type: application/json, }, timeout: 60000, // 设置超时 }); } async call(request: UnifiedLLMRequest): PromiseUnifiedLLMResponse { // 1. 将内部统一请求格式转换为OpenAI API期望的格式 const openAIRequest this.transformRequest(request); // 2. 发起HTTP调用 const response await this.client.post(/chat/completions, openAIRequest, { // 处理流式响应如果request.stream为true responseType: request.stream ? stream : json, }); // 3. 将OpenAI的响应格式转换回内部统一格式 return this.transformResponse(response.data); } private transformRequest(req: UnifiedLLMRequest): any { // 映射字段例如确保模型名称是OpenAI支持的 // 处理消息格式的细微差异等 return { model: req.model, // 注意这里可能根据routeRule的modelMapping被替换过 messages: req.messages, stream: req.stream, temperature: req.temperature, // ... 其他参数映射 }; } private transformResponse(resp: any): UnifiedLLMResponse { // 从OpenAI的响应结构中提取信息构建统一的响应对象 return { id: resp.id, choices: resp.choices.map((choice: any) ({ message: choice.message, // ... 其他字段 })), usage: resp.usage, }; } }关键点每个供应商适配器都需要实现ProviderAdapter接口确保网关能以统一的方式调用它们。transformRequest和transformResponse是适配器模式的具体体现封装了所有特定于供应商的细节。4.3 第三步中间件链的介入src/middlewares/index.ts中间件在请求处理流程的特定阶段插入逻辑。例如一个全局的认证和日志中间件可能在路由处理之前执行。// 认证中间件示例 import { FastifyRequest, FastifyReply, HookHandlerDoneFunction } from fastify; import { verifyApiKey } from ../auth; export async function authMiddleware( request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ) { const apiKey request.headers[authorization]?.replace(Bearer , ); if (!apiKey) { return reply.status(401).send({ error: API key missing }); } const isValid await verifyApiKey(apiKey); if (!isValid) { return reply.status(403).send({ error: Invalid API key }); } // 可以将用户/项目信息附加到request对象供后续使用 (request as any).user await getUserFromKey(apiKey); done(); // 继续执行下一个中间件或路由处理器 } // 日志中间件示例 export async function loggingMiddleware( request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction ) { const start Date.now(); request.log.info({ url: request.url, method: request.method }, Incoming request); reply.on(finish, () { const duration Date.now() - start; request.log.info( { statusCode: reply.statusCode, duration }, Request completed ); }); done(); }在Fastify中你可以通过app.addHook或插件方式注册这些中间件确保它们在路由处理前或后执行。5. 完整示例构建一个最小可用的LLM Gateway理论结合实践。让我们构建一个极度简化但功能完整的LLM Gateway支持路由到OpenAI和模拟的“回显”服务。5.1 定义核心类型 (src/types/index.ts)export interface UnifiedLLMRequest { model: string; messages: Array{ role: user | assistant | system; content: string }; stream?: boolean; temperature?: number; max_tokens?: number; } export interface UnifiedLLMResponse { id: string; object: string; created: number; model: string; choices: Array{ index: number; message: { role: string; content: string }; finish_reason: string; }; usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number; }; } export interface ProviderConfig { name: string; apiKey: string; baseURL: string; defaultModel?: string; } export interface RouteRule { path: string; provider: string; modelMapping?: Recordstring, string; // 客户端模型 - 供应商模型 } export interface GatewayConfig { providers: ProviderConfig[]; routes: RouteRule[]; port: number; }5.2 实现适配器工厂与具体适配器 (src/providers/index.ts)import { ProviderAdapter, UnifiedLLMRequest, UnifiedLLMResponse, ProviderConfig } from ../types; import { OpenAIAdapter } from ./openai; import { EchoAdapter } from ./echo; export interface ProviderAdapter { call(request: UnifiedLLMRequest): PromiseUnifiedLLMResponse; } export function createAdapter(providerName: string, config: ProviderConfig): ProviderAdapter { switch (providerName.toLowerCase()) { case openai: return new OpenAIAdapter(config); case echo: // 一个用于测试的模拟适配器原样返回输入 return new EchoAdapter(config); default: throw new Error(Unsupported provider: ${providerName}); } }5.3 实现OpenAI适配器 (src/providers/openai.ts)import axios from axios; import { ProviderAdapter, UnifiedLLMRequest, UnifiedLLMResponse, ProviderConfig } from ../types; export class OpenAIAdapter implements ProviderAdapter { private client; constructor(private config: ProviderConfig) { this.client axios.create({ baseURL: config.baseURL || https://api.openai.com/v1, headers: { Authorization: Bearer ${config.apiKey}, Content-Type: application/json, }, timeout: 30000, }); } async call(request: UnifiedLLMRequest): PromiseUnifiedLLMResponse { // 简单转换实际可能需要更复杂的逻辑 const openAIRequest { model: request.model, messages: request.messages, stream: request.stream || false, temperature: request.temperature, max_tokens: request.max_tokens, }; try { const response await this.client.post(/chat/completions, openAIRequest); // 将OpenAI响应映射到统一格式 const data response.data; return { id: data.id, object: data.object, created: data.created, model: data.model, choices: data.choices.map((choice: any) ({ index: choice.index, message: choice.message, finish_reason: choice.finish_reason, })), usage: data.usage, }; } catch (error: any) { // 统一错误处理抛出网关能识别的错误 console.error(OpenAI API call failed:, error.response?.data || error.message); throw new Error(OpenAI provider error: ${error.response?.data?.error?.message || error.message}); } } }5.4 实现路由匹配与主应用 (src/index.ts)import Fastify from fastify; import { GatewayConfig, UnifiedLLMRequest } from ./types; import { createAdapter } from ./providers; // 从环境变量或配置文件加载配置此处硬编码示例 const config: GatewayConfig { port: 3000, providers: [ { name: openai, apiKey: process.env.OPENAI_API_KEY || your-openai-key, baseURL: https://api.openai.com/v1, }, { name: echo, apiKey: dummy-key, baseURL: http://localhost:9999, // 模拟服务地址 }, ], routes: [ { path: /v1/chat/completions, provider: openai }, // 默认路由到OpenAI // 可以添加更复杂的路由规则例如基于请求头或模型名称 ], }; const app Fastify({ logger: true }); // 注册统一的LLM端点 app.post(/v1/chat/completions, async (request, reply) { const body request.body as UnifiedLLMRequest; // 1. 简单的路由决策逻辑这里直接使用第一个路由规则 // 实际项目应根据path, headers, body.model等做复杂匹配 const route config.routes[0]; if (!route) { return reply.status(404).send({ error: No route configured }); } // 2. 查找供应商配置 const providerConfig config.providers.find(p p.name route.provider); if (!providerConfig) { return reply.status(502).send({ error: Provider ${route.provider} not found }); } // 3. 创建适配器并调用 try { const adapter createAdapter(providerConfig.name, providerConfig); // 可选根据modelMapping转换请求中的模型名称 const finalRequest { ...body }; if (route.modelMapping body.model in route.modelMapping) { finalRequest.model route.modelMapping[body.model]; } const response await adapter.call(finalRequest); return reply.send(response); } catch (error: any) { // 4. 错误处理 app.log.error(error); // 根据错误类型返回不同的状态码 const statusCode error.message.includes(provider error) ? 502 : 500; return reply.status(statusCode).send({ error: error.message }); } }); // 启动服务器 const start async () { try { await app.listen({ port: config.port }); console.log(LLM Gateway running on http://localhost:${config.port}); } catch (err) { app.log.error(err); process.exit(1); } }; start();5.5 添加一个模拟的Echo适配器用于测试 (src/providers/echo.ts)import { ProviderAdapter, UnifiedLLMRequest, UnifiedLLMResponse, ProviderConfig } from ../types; export class EchoAdapter implements ProviderAdapter { constructor(private config: ProviderConfig) {} async call(request: UnifiedLLMRequest): PromiseUnifiedLLMResponse { // 模拟处理延迟 await new Promise(resolve setTimeout(resolve, 100)); // 简单地“回显”最后一条用户消息 const lastUserMessage request.messages .filter(m m.role user) .pop()?.content || Hello?; return { id: echo-${Date.now()}, object: chat.completion, created: Math.floor(Date.now() / 1000), model: request.model, choices: [ { index: 0, message: { role: assistant, content: Echo: ${lastUserMessage} }, finish_reason: stop, }, ], usage: { prompt_tokens: 10, // 模拟值 completion_tokens: 5, total_tokens: 15, }, }; } }6. 运行、测试与效果验证6.1 运行网关确保已安装所有依赖 (npm install)。设置环境变量创建.env文件OPENAI_API_KEYsk-your-actual-openai-key-here使用ts-node直接运行或编译后运行# 开发模式运行 npx ts-node src/index.ts # 或者先编译 npx tsc node dist/index.js如果一切正常控制台会输出LLM Gateway running on http://localhost:30006.2 测试网关接口使用curl或 Postman 等工具测试网关。测试回显适配器无需真实API Key:修改src/index.ts中的config.routes将 provider 暂时改为echo然后重启服务。curl -X POST http://localhost:3000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: gpt-3.5-turbo, messages: [ {role: user, content: Hello, gateway!} ], temperature: 0.7 }预期成功响应:{ id: echo-1645678901234, object: chat.completion, created: 1645678901, model: gpt-3.5-turbo, choices: [ { index: 0, message: { role: assistant, content: Echo: Hello, gateway! }, finish_reason: stop } ], usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 } }测试OpenAI适配器需要有效API Key:将路由改回openai并确保OPENAI_API_KEY正确。curl -X POST http://localhost:3000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: gpt-3.5-turbo, messages: [ {role: user, content: Say hello in French.} ], temperature: 0.7 }预期成功响应将收到真实的OpenAI响应:{ id: chatcmpl-..., object: chat.completion, created: 1677652288, model: gpt-3.5-turbo, choices: [ { index: 0, message: { role: assistant, content: Bonjour! }, finish_reason: stop } ], usage: { prompt_tokens: 10, completion_tokens: 2, total_tokens: 12 } }6.3 验证网关核心价值通过这个简单的网关你已经实现了统一接口客户端只需向http://localhost:3000/v1/chat/completions发送固定格式的请求。供应商解耦业务代码不关心底层是OpenAI还是Echo服务。要新增Anthropic支持只需实现一个新的AnthropicAdapter并在配置中添加。集中管理API密钥、端点URL等敏感和易变配置集中在网关管理。7. 常见问题与排查思路在开发和运行LLM Gateway时你会遇到各种问题。下面是一个基于真实场景的排查指南。问题现象可能原因排查方式解决方案启动失败端口被占用端口3000已被其他进程使用。1. 查看日志错误信息。2. 使用lsof -i :3000(Mac/Linux) 或netstat -ano | findstr :3000(Windows) 查找占用进程。1. 终止占用进程。2. 修改config.port为其他端口。请求返回502 Bad Gateway网关无法连接到上游供应商服务或供应商服务返回错误。这是最常见的错误之一。1.检查网关日志查看adapter.call()抛出的具体错误。2.检查供应商配置baseURL和apiKey是否正确。3.网络连通性网关服务器是否能访问供应商API如api.openai.com。4.供应商API状态检查供应商服务是否正常。1. 修正错误的配置。2. 检查网络代理或防火墙设置。3. 在适配器中增加更详细的错误日志和重试逻辑。4. 实现熔断机制避免持续向故障服务发送请求。请求返回401 Unauthorized客户端未提供API Key或Key无效。1. 检查客户端请求头Authorization: Bearer key是否正确。2. 检查网关的认证中间件逻辑。3. 确认使用的API Key是否有权限访问目标模型。1. 确保客户端发送正确的认证头。2. 在网关实现API Key的白名单或数据库校验。请求返回404 Not Found请求路径未在网关中注册。1. 检查客户端请求的URL路径。2. 检查网关应用注册的路由。1. 确保客户端调用正确的网关端点。2. 在网关中添加对应的路由规则。请求超时上游模型API响应慢或网络延迟高。1. 检查网关和适配器中设置的timeout值。2. 查看网关服务器和供应商API之间的网络状况。1. 适当增加axios或fetch的超时时间。2. 考虑在网关层面实现异步任务和轮询避免HTTP长连接超时。流式响应 (streamtrue) 不工作网关没有正确处理流式响应或者没有正确设置响应头。1. 检查适配器是否将responseType设置为stream。2. 检查网关是否正确地以流式方式将数据块转发给客户端设置Content-Type: text/event-stream等。3. 查看浏览器开发者工具或curl输出确认收到的是数据流还是完整JSON。1. 在适配器中正确处理ReadableStream或axios的响应流。2. 确保网关的响应头正确并逐块chunk写入响应体。TypeScript编译错误类型不匹配、缺少类型定义或配置错误。1. 阅读具体的TS错误信息。2. 检查tsconfig.json配置。3. 确认所有第三方库的types/包已安装。1. 根据错误修正类型。2. 使用any或类型断言 (as) 作为临时方案但需谨慎。3. 运行npm install types/库名 --save-dev。针对高频错误unexpected status 502 bad gateway的深度排查这个错误表明网关作为代理从上游服务器收到了一个无效的响应。在你的网关代码中这通常发生在adapter.call()内部。在适配器的catch块中打印详细日志catch (error: any) { console.error(Upstream API Error Details:); console.error(- Status:, error.response?.status); console.error(- Status Text:, error.response?.statusText); console.error(- Headers:, error.response?.headers); console.error(- Data:, error.response?.data); // 这里常有具体错误信息 console.error(- Request Config:, error.config?.url, error.config?.method); throw new Error(Provider error: ${error.response?.data?.error?.message || error.message}); }检查上游URL确保baseURL拼接正确没有多余的斜杠。检查API密钥和配额很多502错误实际上是上游服务返回了429 Too Many Requests或401 Unauthorized但被网关统一解释为502。检查请求体格式确保transformRequest生成的请求体完全符合上游API的规范特别是消息数组的格式、角色名称等。8. 最佳实践与工程化建议将一个小型网关发展为生产就绪的系统需要考虑更多工程化因素。8.1 配置管理不要硬编码将所有配置API密钥、端点、路由规则外置。使用环境变量与配置文件结合敏感信息如API Key用环境变量其他配置用JSON或YAML文件。考虑使用dotenv和convict等库。支持热重载在不重启服务的情况下更新路由规则可以使用文件监听或集成配置中心如Consul, Apollo。8.2 可观测性结构化日志使用pino或winston记录每个请求的详细信息请求ID、用户、模型、token用量、耗时、状态码。这对计费、调试和监控至关重要。指标收集集成监控系统如Prometheus暴露指标端点。关键指标包括请求速率、延迟分布P50, P95, P99、错误率按供应商和模型分类、Token消耗速率。分布式追踪为每个请求生成唯一ID (X-Request-ID)并在网关和所有下游调用中传递便于在复杂链路中定位问题。8.3 稳定性与弹性重试机制对于网络抖动或上游服务的瞬时故障实现带退避backoff的智能重试如指数退避。注意对于非幂等请求如某些写操作要谨慎。熔断器模式当某个供应商的失败率超过阈值时快速失败避免资源耗尽和服务雪崩。可以使用opossum或brakes库。限流在网关入口实施速率限制保护下游供应商API不被滥用并公平分配资源。可以使用express-rate-limit如果使用Express或fastify-rate-limit。负载均衡如果一个模型对应多个后端实例如自部署的多个开源模型副本在网关层实现简单的轮询、随机或一致性哈希负载均衡。8.4 安全严格的输入验证使用zod或joi验证所有入参防止注入攻击或畸形请求导致网关或下游服务崩溃。API密钥管理不要将密钥明文记录在日志中。使用密钥管理服务KMS或至少进行部分掩码。请求配额与鉴权实现基于用户/项目的配额管理每日调用次数、Token限额。这通常需要与用户系统集成。8.5 性能优化连接池HTTP客户端如axios保持连接池复用减少TCP握手开销。响应缓存对于某些重复的、非实时的提示词可以考虑在网关层缓存响应但要注意缓存失效和模型随机性的问题。异步处理对于耗时长或需要轮询的任务可以改为异步接口立即返回一个任务ID客户端通过另一个端点查询结果。8.6 TypeScript工程实践使用严格模式tsconfig.json中设置strict: true最大化类型安全。定义清晰的接口就像我们之前做的为请求、响应、配置、适配器等定义明确的interface或type。依赖注入考虑使用IoC容器如tsyringe来管理适配器、服务等依赖提高可测试性和可维护性。编写单元测试为适配器的转换逻辑、路由匹配函数等核心单元编写测试。使用jest或mocha。通过阅读和动手实现一个TypeScript LLM Gateway你获得的不仅仅是一个工具。你深入理解了网关在微服务和AI架构中的核心价值掌握了用TypeScript构建类型安全、可扩展的后端服务的实践并具备了诊断和解决类似“502 Bad Gateway”等典型问题的能力。这个模式可以扩展到任何需要聚合、转换和路由多源API的场景。你可以从本文的简化示例出发逐步添加上述高级特性最终构建出一个满足你业务需求的、健壮的生产级LLM网关。建议将代码托管到GitHub结合CI/CD流程实现自动化测试和部署。