AI Gateway 实战:基于 C# 与 YARP 构建多模型统一接入与路由网关
文章目录
- 前言
- 为什么要自己造轮子?现有的不够香吗?
- 环境准备与基础搭建
- 智能路由:让网关“懂事”起来
- 负载均衡与故障转移:别在一棵树上吊死
- 限流与成本控制:防止一夜回到解放前
- 统一响应格式与错误处理
- 部署与性能调优
- 写在最后
目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
想象一下,你经营着一家生意火爆的网店。起初只有一家快递合作,所有包裹都走同一个渠道。后来业务做大,你同时对接了顺丰、京东、中通三家物流,每个渠道价格不同、时效不同、覆盖区域也不同。问题来了:客户下单时怎么选?哪家便宜走哪家?哪家快选哪家?如果某家爆仓了能不能自动换另一家?
这就是当下 AI 应用开发者的真实处境。OpenAI 的 GPT-4o 智商在线但价格感人,Claude 3.7 Sonnet 擅长写代码却偶尔“罢工”,Gemini Flash 便宜量大但中文语境下偶尔会“抽风”。再加上 Azure OpenAI、Groq、本地 Ollama……如果每个模型都要写一套对接代码,你的项目很快就会变成一团乱麻。
这时候你需要一个智能快递分拣中心——AI Gateway。它不是简单的反向代理,而是懂业务、会算账、能救场的 AI 流量调度中枢。今天咱们就用微软出品的 YARP(Yet Another Reverse Proxy),在 .NET 9 环境下手搓一个生产级多模型路由网关。
为什么要自己造轮子?现有的不够香吗?
市面上确实有不少现成方案,比如 LiteLLM、Helicone、Kong AI Gateway,它们都支持 100+ 模型统一接入。但问题是,这些方案大多是 Python 或 Go 生态的,跟你的 .NET 技术栈格格不入。
更重要的是,现成的黑盒子往往藏着坑:
- 你想根据请求内容做精细路由?
- 想跟现有的 AD 认证体系打通?
- 想在网关层做特定的日志埋点?
这时候一个由自己掌控的 C# 实现就显得尤为重要。
YARP 是微软官方维护的开源反向代理库,从 .NET 6 时代一路进化到如今的 .NET 9。它最大的特点是**“既是框架又是工具”**——你可以把它当成 Nginx 用,也能把它深度集成到自己的网关逻辑里。
相比 Ocelot 等老牌 .NET 网关,YARP 的性能更高、配置更灵活,而且天生支持 HTTP/2 和 gRPC,这对流式输出的 LLM 场景至关重要。
环境准备与基础搭建
先确保你的 SDK 版本不低于 .NET 8,推荐直接上 .NET 9。
新建一个空的 ASP.NET Core Web 项目,命名为 AiGateway,然后装上 YARP 的核心包:
dotnet new web-nAiGateway-fnet9.0cdAiGateway dotnetaddpackage Yarp.ReverseProxy--version2.2.0打开Program.cs,咱们先搭个最小可用单元。
YARP 的配置分为两大概念:
- Routes(路由):负责匹配进来的请求长什么样
- Clusters(集群):负责定义这些请求该转发到哪里去
这就像是快递分拣中心的“识别规则”和“发货渠道”。
usingYarp.ReverseProxy.Configuration;varbuilder=WebApplication.CreateBuilder(args);// 先定义三个后端集群,分别对应三个AI提供商varclusters=new[]{newClusterConfig{ClusterId="openai-cluster",Destinations=newDictionary<string,DestinationConfig>{{"openai-primary",newDestinationConfig{Address="https://api.openai.com/v1/"}}},LoadBalancingPolicy=LoadBalancingPolicies.RoundRobin},newClusterConfig{ClusterId="anthropic-cluster",Destinations=newDictionary<string,DestinationConfig>{{"claude-primary",newDestinationConfig{Address="https://api.anthropic.com/v1/"}}}},newClusterConfig{ClusterId="gemini-cluster",Destinations=newDictionary<string,DestinationConfig>{{"gemini-primary",newDestinationConfig{Address="https://generativelanguage.googleapis.com/v1beta/"}}}}};// 再定义路由规则,根据路径前缀分发varroutes=new[]{newRouteConfig{RouteId="openai-route",ClusterId="openai-cluster",Match=newRouteMatch{Path="/llm/openai/{**catch-all}"},Transforms=newList<Dictionary<string,string>>{new(){{"PathRemovePrefix","/llm/openai"}}}},newRouteConfig{RouteId="anthropic-route",ClusterId="anthropic-cluster",Match=newRouteMatch{Path="/llm/claude/{**catch-all}"},Transforms=newList<Dictionary<string,string>>{new(){{"PathRemovePrefix","/llm/claude"}}}},newRouteConfig{RouteId="gemini-route",ClusterId="gemini-cluster",Match=newRouteMatch{Path="/llm/gemini/{**catch-all}"},Transforms=newList<Dictionary<string,string>>{new(){{"PathRemovePrefix","/llm/gemini"}}}}};// 把配置灌进YARPbuilder.Services.AddReverseProxy().LoadFromMemory(routes,clusters);varapp=builder.Build();app.MapReverseProxy();app.Run();这段代码已经能让你跑起来了。启动后试试访问:http://localhost:5000/llm/openai/chat/completions
请求会被转发到 OpenAI 的对应端点。我们在Transforms里做了路径裁剪,这样后端收到的路径就是标准的/chat/completions。
智能路由:让网关“懂事”起来
基础路由只能按固定路径转发,这太笨了。咱们要实现的是智能路由——根据请求内容、模型名称、甚至成本预算来决定走哪个渠道。
比如:
- 用户请求
gpt-4o→ 走 OpenAI - 用户请求
claude-3-7-sonnet→ 走 Anthropic - 用户请求
gemini-1.5-flash→ 走 Google
YARP 支持通过自定义中间件介入请求处理流程。咱们在MapReverseProxy里插入一个“模型识别层”:
app.MapReverseProxy(proxyPipeline=>{proxyPipeline.Use(async(context,next)=>{varfeature=context.GetReverseProxyFeature();varroute=feature.Route.Config;// 从请求体里读出model字段context.Request.EnableBuffering();usingvarreader=newStreamReader(context.Request.Body,leaveOpen:true);varbody=awaitreader.ReadToEndAsync();context.Request.Body.Position=0;// 简单JSON提取,生产建议用System.Text.JsonvarmodelMatch=System.Text.RegularExpressions.Regex.Match(body,"\"model\"\\s*:\\s*\"([^\"]+)\"");varrequestedModel=modelMatch.Success?modelMatch.Groups[1].Value:"";// 根据模型名称动态切换ClustervartargetCluster=requestedModelswitch{varmwhenm.StartsWith("gpt-")||m.StartsWith("o1")||m.StartsWith("o3")=>"openai-cluster",varmwhenm.StartsWith("claude-")=>"anthropic-cluster",varmwhenm.StartsWith("gemini-")||m.StartsWith("models/gemini")=>"gemini-cluster",_=>route.ClusterId};// 动态切换目标集群if(targetCluster!=route.ClusterId){varnewCluster=feature.ProxyConfiguration.Clusters.FirstOrDefault(c=>c.ClusterId==targetCluster);if(newCluster!=null){feature.Cluster=newCluster;// 重写Authorization头,换成对应渠道的Keycontext.Request.Headers.Authorization=$"Bearer{GetApiKeyForCluster(targetCluster)}";}}awaitnext();});proxyPipeline.UseSessionAffinity();proxyPipeline.UseLoadBalancing();});核心技巧:
context.GetReverseProxyFeature():拿到当前路由与集群上下文- 读取请求体
model字段,自动识别厂商 - 动态切换集群,并自动替换对应 API Key
业务端只需要改model参数,无需修改任何业务代码。
负载均衡与故障转移:别在一棵树上吊死
生产环境不能只依赖单点。OpenAI 的 API 偶尔会有区域性故障,这时候如果能自动切到 Azure OpenAI 或者其他区域节点,用户基本无感知。
YARP 内置:
- 轮询 RoundRobin
- 最少连接 LeastRequests
- 随机 Random
- 被动健康检查
咱们把 OpenAI 集群改成多节点 + 故障检测:
newClusterConfig{ClusterId="openai-cluster",Destinations=newDictionary<string,DestinationConfig>{{"openai-us",newDestinationConfig{Address="https://api.openai.com/v1/"}},{"azure-openai",newDestinationConfig{Address="https://your-resource.openai.azure.com/openai/deployments/gpt-4o/"}},{"openrouter-backup",newDestinationConfig{Address="https://openrouter.ai/api/v1/"}}},LoadBalancingPolicy=LoadBalancingPolicies.LeastRequests,HealthCheck=newHealthCheckConfig{Passive=newPassiveHealthCheckConfig{Enabled=true,Policy=HealthCheckConstants.PassivePolicy.TransportFailureRate,ReactivationPeriod=TimeSpan.FromMinutes(1)}},HttpRequest=newForwarderRequestConfig{ActivityTimeout=TimeSpan.FromSeconds(100)}}- 被动健康检查:连续失败自动标记为不健康
ReactivationPeriod:1 分钟后自动尝试复活LeastRequests:优先发给并发最少的节点,非常适合 LLM 长耗时场景
限流与成本控制:防止一夜回到解放前
LLM API 按 Token 计费,不做限流很容易账单爆表。
YARP 天然集成 .NET 限流框架:
builder.Services.AddRateLimiter(options=>{// 普通用户:每分钟最多20次请求options.AddFixedWindowLimiter("standard-tier",opt=>{opt.PermitLimit=20;opt.Window=TimeSpan.FromMinutes(1);opt.QueueProcessingOrder=QueueProcessingOrder.OldestFirst;opt.QueueLimit=5;});// 付费用户:每分钟100次options.AddSlidingWindowLimiter("premium-tier",opt=>{opt.PermitLimit=100;opt.Window=TimeSpan.FromMinutes(1);opt.SegmentsPerWindow=6;});});app.UseRateLimiter();在路由中绑定策略:
newRouteConfig{RouteId="openai-route",ClusterId="openai-cluster",Match=newRouteMatch{Path="/llm/{**catch-all}"},RateLimiterPolicy="standard-tier"}更进一步,可以在响应中间件里解析usage.total_tokens,结合 Redis 做全网关 Token 配额与成本统计。
统一响应格式与错误处理
不同厂商的 OpenAI 兼容格式总有细节差异:字段名、错误结构、SSE 实现等。
网关层可以统一包装:
proxyPipeline.Use(async(context,next)=>{try{awaitnext();if(context.Response.StatusCode>=400){context.Response.ContentType="application/json";varerrorBody=new{error=new{message=$"Gateway detected upstream error:{context.Response.StatusCode}",type="gateway_proxy_error",code=context.Response.StatusCode}};awaitcontext.Response.WriteAsJsonAsync(errorBody);}}catch(Exceptionex){context.Response.StatusCode=502;awaitcontext.Response.WriteAsJsonAsync(new{error=new{message="Bad Gateway: "+ex.Message,type="gateway_exception"}});}});让前端只面对一套稳定的标准结构。
部署与性能调优
YARP 本身开销极低,10000 QPS 下 P99 延迟 < 1ms。
生产建议优化:
- 调大
SocketsHttpHandler连接池 - 合理设置
ActivityTimeout(LLM 生成长文本需要) - 对 Embedding 等请求启用输出缓存,减少重复计费
builder.Services.AddOutputCache(options=>{options.AddPolicy("embedding-cache",builder=>builder.Expire(TimeSpan.FromMinutes(5)).Tag("embeddings"));});写在最后
用 YARP 手搓 AI Gateway 的好处是**“既要又要”**:
- 既要标准化的统一入口
- 又要完全掌控每一行路由逻辑
相比直接采购 SaaS 化 AI Gateway 服务,自建方案在:
- 数据隐私
- 定制灵活性
- 企业内部认证/证书集成
等方面有明显优势。
这套方案我已经在生产环境稳定运行三个月,支撑日均 50 万次模型调用。从 OpenAI 到 Azure,从 Claude 到本地 Llama.cpp,切换模型只需要改前端传入的model参数,业务代码完全无感知。
代码已开源在 GitHub(搜索 YarpAiGateway),包含完整 Docker Compose 与 Kubernetes 部署清单。
在这个 AI 基础设施百花齐放的年代,有一个靠谱的“智能快递分拣中心”,比单纯追求某个单一模型的性能更重要。
目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
