






















现在ai盛行,各家api gateway 纷纷实现了ai gateway 的功能,作为强大的卖点
2025 年个人用c#造了个api gateway的轮子VKProxy,现在各项功能或扩展能力齐备,性能也不差,个人时间精力也有限,ai gateway 就搞个简单的 demo 做为此轮子篇章的结尾吧
AI Gateway(AI 网关)是专门为 AI/LLM 型服务设计的网关层,负责把应用和底层模型提供者(或自建模型)之间的请求做统一管理、路由、识别、控制与观测。它把模型接入、权限、合规、性能优化和成本控制等横切关注点抽象出来,供上层应用以统一、安全和可控的方式调用 AI 功能。
这里用 VKProxy 作为实现基础, 不过为了演示简单,不做流式响应处理, 所以大家也可以非常简单用 asp.net + client 或 yarp 或任意语言任意框架实现
作为ai gateway , 我们可以直接统一各个 ai 服务的配置,这样用户无需重复配置各项ai 服务
为了演示简单,这里就演示最简单 统一适配各项ai服务,提供统一api的方式,定义的配置就可以像如下
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"ServerOptions": {
"AddServerHeader": false
},
"ReverseProxy": {
"ConnectionTimeout": "00:00:01.000",
"Listen": { // 服务监听地址
"http": {
"Protocols": [ "Http1" ],
"Address": [ "127.0.0.1:5001", "[::1]:5001" ]
}
},
"Routes": {
"ai": { // 路由配置,为了简单 我们只做 非流式响应的 chat
"Match": {
"Hosts": [ "*" ],
"Paths": [ "/v1/chat/completions" ]
},
"ClusterId": "openai",
"Timeout": "00:10:00",
"Metadata": {
"AiMapping": "{\"openai\":{\"DefaultModel\": \"llama-3.3-70b-versatile\",\"Driver\":\"openai\"},\"deepseek\":{}}" // 这里用最简单的配置
}
}
},
"Clusters": { // 可复用的ai 服务配置
"deepseek": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": [
{
"Address": "https://api.deepseek.com"
}
]
},
"openai": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": [
{
"Address": "https://api.groq.com/groq"
}
]
}
}
}
}
现在各家 ai 请求都很统一相似,这里就演示简单的固定结构,这样大家理解也简单,不过实现最好以比较动态的方式实现,这样适配各家差异也比较容易
public class AiRequest
{
public string? ApiKey { get; set; } // ai 服务key, 这里放请求其实是为了避免个人不小心上传key到 GitHub,至于大家是否需要可自己考虑
public bool? Stream { get; set; } // 是否流式响应,当然这里不会演示,不然就太多了
public string? Provider { get; set; } // 哪家服务
public string? Model { get; set; } // 哪个模型
public List<AiMessage>? Messages { get; set; } // 你想让ai干什么
}
public class AiMessage
{
public string? Role { get; set; }
public string? Content { get; set; }
}
public interface IAIProvider
{
Task Request(HttpContext context, AiRequest req, AiMapping ai);
}
public class OpenAiProvider : IAIProvider
{
public virtual async Task Request(HttpContext context, AiRequest req, AiMapping ai)
{
if (req.Model == null)
{
req.Model = ai.DefaultModel;
}
context.Request.Headers.Authorization = $"Bearer {req.ApiKey ?? ai.ApiKey}"; // 设置授权,测试的是这家是这样格式,所以为了演示简单,写死了
req.Provider = null;
req.ApiKey = null; // 通过json 忽略 多余字段,有些ai 服务商校验比较严格,多一个它没有的字段都不行
var r = new MemoryStream(Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(req, AiGatewayMiddleware.jsonOptions)));
context.Request.Body = r;
context.Request.ContentLength = r.Length; // 替换body 和 ContentLength
// 产线最好严格处理 Accept / Accept-Encoding 等等影响格式的内容,避免压缩等等各种情况带来的解析问题,这里就不做了
}
}
public class AiGatewayMiddleware : IMiddleware
{
private readonly ILogger<AiGatewayMiddleware> logger;
private readonly IConfigSource<IProxyConfig> configSource;
private readonly IServiceProvider provider;
private readonly ILoadBalancingPolicyFactory loadBalancing;
public static readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions() { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
public AiGatewayMiddleware(ILogger<AiGatewayMiddleware> logger, IConfigSource<IProxyConfig> configSource, IServiceProvider provider, ILoadBalancingPolicyFactory loadBalancing)
{
this.logger = logger;
this.configSource = configSource;
this.provider = provider;
this.loadBalancing = loadBalancing;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var feature = context.Features.Get<IReverseProxyFeature>();
if (feature is not null)
{
var route = feature.Route;
if (route is not null && route.Metadata is not null
&& route.Metadata.TryGetValue("AiMapping", out var b)) // 这里 VKProxy 已经解析过配置,匹配上对应路由了,只需判断是否开启了ai 功能, 如果你用 asp.net core , 则需要自己处理,当然也可以直接用 controller + httpclient 直接写,虽然少了些优化点,不过ai 场景这点性能不太重要了大多
{
var req = await context.Request.ReadFromJsonAsync<AiRequest>(context.RequestAborted); // 读取请求
if (req is null)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
var mapping = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, AiMapping>>(b); // 读取配置,这里demo 为了简单就不写什么缓存优化了
IAIProvider driver;
if (mapping.TryGetValue(req.Provider ?? "openai", out var ai)) // 获取ai 调用实现
{
driver = provider.GetKeyedService<IAIProvider>(ai.Driver);
}
else
driver = null;
if (driver is null)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
await context.Response.WriteAsJsonAsync(new { error = $"Unsupported AI provider: {req.Provider}" }, context.RequestAborted);
return;
}
if (configSource.CurrentSnapshot.Clusters.TryGetValue(ai.Driver, out var cluster)) // 获取 服务地址
{
feature.SelectedDestination = loadBalancing.PickDestination(feature, cluster); // 获取有效地址
if (feature.SelectedDestination is null)
{
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
await context.Response.WriteAsJsonAsync(new { error = $"{req.Provider} service unavailable" }, context.RequestAborted);
return;
}
}
await SendAi(context, req, ai, driver); // 处理请求
// 由于VKProxy 再后续已经实现非常高效的代理处理,这里就简单实现复制内容,这样respone 会第一时间就已经送回的 client端,即使后续我们token 记录出问题也不影响用户, 不过要修改就要麻烦点了,所以不在乎性能的话,client 直接call 是最简单的
var origin = context.Response.Body;
using var buf = new ResponseCachingStream(origin, 64 * 1024 * 1024 * 10, 81920, () => ValueTask.CompletedTask); // demo这里直接用内存实现的,产线如果也想这么做,推荐 小的用内存,大的用磁盘,或者直接镜像复制发送给其他程序做分析统计 等等
context.Response.Body = buf;
await next(context); // 代理处理
// 结果解析,并记录 token 使用, 这里demo 就简单记录 log 了, 真实场景就可以通过log 解析或者 直接存入统计db 等,这样可以达到ai gateway最重要的功能之一: token 统计,找出账单超支的罪魁祸首
var resp = buf.GetCachedResponseBody();
using var u = new MemoryStream(resp.Segments.SelectMany(i => i).ToArray());
var aiResp = JsonSerializer.Deserialize<AiResponse>(u, jsonOptions);
if (aiResp?.Usage != null)
{
logger.LogWarning("AI TotalTokens: {Usage}", aiResp.Usage.TotalTokens);
}
context.Response.Body = origin;
return;
}
}
await next(context);
}
private async Task SendAi(HttpContext context, AiRequest req, AiMapping ai, IAIProvider driver)
{
await driver.Request(context, req, ai);
}
}
除开 SSE , 基本的实现非常简单,其实这里代理实现VKProxy 已经为了性能引入一些复杂度, 如果不在乎性能,比如用httpclient call,你们的写法还可以更简单,懒得自己写,还可以叫ai 代劳
全部代码也放在了 VKProxy AiGatewayDemo
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。