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 的核心包:

bash 复制代码
dotnet new web -n AiGateway -f net9.0
cd AiGateway
dotnet add package Yarp.ReverseProxy --version 2.2.0

打开 Program.cs,咱们先搭个最小可用单元。

YARP 的配置分为两大概念:

  • Routes(路由):负责匹配进来的请求长什么样
  • Clusters(集群):负责定义这些请求该转发到哪里去

这就像是快递分拣中心的"识别规则"和"发货渠道"。

csharp 复制代码
using Yarp.ReverseProxy.Configuration;

var builder = WebApplication.CreateBuilder(args);

// 先定义三个后端集群,分别对应三个AI提供商
var clusters = new[]
{
    new ClusterConfig
    {
        ClusterId = "openai-cluster",
        Destinations = new Dictionary<string, DestinationConfig>
        {
            { "openai-primary", new DestinationConfig { Address = "https://api.openai.com/v1/" } }
        },
        LoadBalancingPolicy = LoadBalancingPolicies.RoundRobin
    },
    new ClusterConfig
    {
        ClusterId = "anthropic-cluster",
        Destinations = new Dictionary<string, DestinationConfig>
        {
            { "claude-primary", new DestinationConfig { Address = "https://api.anthropic.com/v1/" } }
        }
    },
    new ClusterConfig
    {
        ClusterId = "gemini-cluster",
        Destinations = new Dictionary<string, DestinationConfig>
        {
            { "gemini-primary", new DestinationConfig { Address = "https://generativelanguage.googleapis.com/v1beta/" } }
        }
    }
};

// 再定义路由规则,根据路径前缀分发
var routes = new[]
{
    new RouteConfig
    {
        RouteId = "openai-route",
        ClusterId = "openai-cluster",
        Match = new RouteMatch { Path = "/llm/openai/{**catch-all}" },
        Transforms = new List<Dictionary<string, string>>
        {
            new() { { "PathRemovePrefix", "/llm/openai" } }
        }
    },
    new RouteConfig
    {
        RouteId = "anthropic-route",
        ClusterId = "anthropic-cluster",
        Match = new RouteMatch { Path = "/llm/claude/{**catch-all}" },
        Transforms = new List<Dictionary<string, string>>
        {
            new() { { "PathRemovePrefix", "/llm/claude" } }
        }
    },
    new RouteConfig
    {
        RouteId = "gemini-route",
        ClusterId = "gemini-cluster",
        Match = new RouteMatch { Path = "/llm/gemini/{**catch-all}" },
        Transforms = new List<Dictionary<string, string>>
        {
            new() { { "PathRemovePrefix", "/llm/gemini" } }
        }
    }
};

// 把配置灌进YARP
builder.Services.AddReverseProxy()
       .LoadFromMemory(routes, clusters);

var app = 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 里插入一个"模型识别层":

csharp 复制代码
app.MapReverseProxy(proxyPipeline =>
{
    proxyPipeline.Use(async (context, next) =>
    {
        var feature = context.GetReverseProxyFeature();
        var route = feature.Route.Config;

        // 从请求体里读出model字段
        context.Request.EnableBuffering();
        using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
        var body = await reader.ReadToEndAsync();
        context.Request.Body.Position = 0;

        // 简单JSON提取,生产建议用System.Text.Json
        var modelMatch = System.Text.RegularExpressions.Regex.Match(body, "\"model\"\\s*:\\s*\"([^\"]+)\"");
        var requestedModel = modelMatch.Success ? modelMatch.Groups[1].Value : "";

        // 根据模型名称动态切换Cluster
        var targetCluster = requestedModel switch
        {
            var m when m.StartsWith("gpt-") || m.StartsWith("o1") || m.StartsWith("o3") 
                => "openai-cluster",
            var m when m.StartsWith("claude-") 
                => "anthropic-cluster",
            var m when m.StartsWith("gemini-") || m.StartsWith("models/gemini") 
                => "gemini-cluster",
            _ => route.ClusterId
        };

        // 动态切换目标集群
        if (targetCluster != route.ClusterId)
        {
            var newCluster = feature.ProxyConfiguration.Clusters
                .FirstOrDefault(c => c.ClusterId == targetCluster);
            if (newCluster != null)
            {
                feature.Cluster = newCluster;
                // 重写Authorization头,换成对应渠道的Key
                context.Request.Headers.Authorization = $"Bearer {GetApiKeyForCluster(targetCluster)}";
            }
        }

        await next();
    });

    proxyPipeline.UseSessionAffinity();
    proxyPipeline.UseLoadBalancing();
});

核心技巧:

  • context.GetReverseProxyFeature():拿到当前路由与集群上下文
  • 读取请求体 model 字段,自动识别厂商
  • 动态切换集群,并自动替换对应 API Key

业务端只需要改 model 参数,无需修改任何业务代码。


负载均衡与故障转移:别在一棵树上吊死

生产环境不能只依赖单点。OpenAI 的 API 偶尔会有区域性故障,这时候如果能自动切到 Azure OpenAI 或者其他区域节点,用户基本无感知。

YARP 内置:

  • 轮询 RoundRobin
  • 最少连接 LeastRequests
  • 随机 Random
  • 被动健康检查

咱们把 OpenAI 集群改成多节点 + 故障检测:

csharp 复制代码
new ClusterConfig
{
    ClusterId = "openai-cluster",
    Destinations = new Dictionary<string, DestinationConfig>
    {
        { "openai-us", new DestinationConfig { Address = "https://api.openai.com/v1/" } },
        { "azure-openai", new DestinationConfig { Address = "https://your-resource.openai.azure.com/openai/deployments/gpt-4o/" } },
        { "openrouter-backup", new DestinationConfig { Address = "https://openrouter.ai/api/v1/" } }
    },
    LoadBalancingPolicy = LoadBalancingPolicies.LeastRequests,
    HealthCheck = new HealthCheckConfig
    {
        Passive = new PassiveHealthCheckConfig
        {
            Enabled = true,
            Policy = HealthCheckConstants.PassivePolicy.TransportFailureRate,
            ReactivationPeriod = TimeSpan.FromMinutes(1)
        }
    },
    HttpRequest = new ForwarderRequestConfig
    {
        ActivityTimeout = TimeSpan.FromSeconds(100)
    }
}
  • 被动健康检查:连续失败自动标记为不健康
  • ReactivationPeriod:1 分钟后自动尝试复活
  • LeastRequests:优先发给并发最少的节点,非常适合 LLM 长耗时场景

限流与成本控制:防止一夜回到解放前

LLM API 按 Token 计费,不做限流很容易账单爆表。

YARP 天然集成 .NET 限流框架:

csharp 复制代码
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();

在路由中绑定策略:

csharp 复制代码
new RouteConfig
{
    RouteId = "openai-route",
    ClusterId = "openai-cluster",
    Match = new RouteMatch { Path = "/llm/{**catch-all}" },
    RateLimiterPolicy = "standard-tier"
}

更进一步,可以在响应中间件里解析 usage.total_tokens,结合 Redis 做全网关 Token 配额与成本统计


统一响应格式与错误处理

不同厂商的 OpenAI 兼容格式总有细节差异:字段名、错误结构、SSE 实现等。

网关层可以统一包装:

csharp 复制代码
proxyPipeline.Use(async (context, next) =>
{
    try
    {
        await next();

        if (context.Response.StatusCode >= 400)
        {
            context.Response.ContentType = "application/json";
            var errorBody = new
            {
                error = new
                {
                    message = $"Gateway detected upstream error: {context.Response.StatusCode}",
                    type = "gateway_proxy_error",
                    code = context.Response.StatusCode
                }
            };
            await context.Response.WriteAsJsonAsync(errorBody);
        }
    }
    catch (Exception ex)
    {
        context.Response.StatusCode = 502;
        await context.Response.WriteAsJsonAsync(new
        {
            error = new
            {
                message = "Bad Gateway: " + ex.Message,
                type = "gateway_exception"
            }
        });
    }
});

让前端只面对一套稳定的标准结构。


部署与性能调优

YARP 本身开销极低,10000 QPS 下 P99 延迟 < 1ms。

生产建议优化:

  1. 调大 SocketsHttpHandler 连接池
  2. 合理设置 ActivityTimeout(LLM 生成长文本需要)
  3. 对 Embedding 等请求启用输出缓存,减少重复计费
csharp 复制代码
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的朋友,否则看看零散的博文就够了。

相关推荐
tinygone1 小时前
FunASR识别独立的语音文件问题
人工智能·经验分享
Qt学视觉2 小时前
AI3-PaddleOCR搭建环境1
c++·人工智能·opencv·ocr·paddlepaddle
nix.gnehc2 小时前
OpenClaw 天气查询Skill开发Demo
人工智能·skill·openclaw
黑客说2 小时前
无限流:从网文想象到AI赋能的沉浸式娱乐新生态
人工智能·娱乐
AI_Auto2 小时前
【人工智能】- OpenClaw本地化安装
大数据·人工智能·机器学习·数据挖掘
RuiBo_Qiu2 小时前
【LLM进阶-Agent】3.ReAct Agent 进阶--如何解决幻觉输出工具调用结果
人工智能·ai-native
skywalk81632 小时前
看到有人提到:有网站使用分解质因数来区分人和机器,一种新兴的“反向CAPTCHA”策略
人工智能
陈天伟教授2 小时前
人工智能应用- 机器做梦:03.回顾卷积神经网络
人工智能·神经网络·cnn
Lw中2 小时前
模型忽略关键实体怎么办?
人工智能·大模型应用基础