对上个方案进行改进,通过提交参数指定不同的服务商 使用 Yarp.ReverseProxy 转发 ai 调用请求至硅基流动
// // iox
namespace GeneralAPI.ApiService.Components.AiServices;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authorization;
using Yarp.ReverseProxy.Configuration;
using Yarp.ReverseProxy.Transforms;
public static class AiYarpReverseProxyExtention
{
private const string DefaultProvider = "silicon";
// 默认带的 keyvalue
private static readonly Dictionary<string, (string Host, string Key)> Providers = new()
{
{ DefaultProvider, ("https://api.siliconflow.cn", "sk-xxxxx") },
{ "qwen", ("https://dashscope.aliyuncs.com/compatible-mode", "sk-xxxx") }
};
/// <summary>
/// 添加 ai 反射代理服务
/// </summary>
public static IServiceCollection AddAiYarpReverseProxy(this IServiceCollection services, IConfiguration configuration)
{
services.AddReverseProxy()
.LoadFromMemory(
[
new RouteConfig
{
RouteId = "ai_route", // 路由ID
ClusterId = "ai_cluster", // 集群ID
AuthorizationPolicy = "AiAccessPolicy", // 指定访问策略
Match = new RouteMatch { Path = "/api/ai/v1/{**remainder}", }, // 匹配路径, 将链接参数配置到变量 remainder 中
},
],
[
new ClusterConfig
{
ClusterId = "ai_cluster", // 集群ID
Destinations = new Dictionary<string, DestinationConfig>
{
{ "default", new DestinationConfig { Address = Providers[DefaultProvider].Host, } }, // 目的地
},
},
])
.AddTransforms(builderContext =>
{
// builderContext.AddPathRouteValues("/v1/{remainder}"); // 路径重写 /api/ai/v1/chat -> /v1/chat, 如果有多级目录, / 会被转义
// 核心:替换 API Key
builderContext.AddRequestTransform(async transformContext =>
{
// 1. 获取客户端传来的参数 (例如:X-AI-Provider: kimi)
var providerKey = transformContext.HttpContext.Request.Headers["X-AI-Provider"].ToString().ToLower();
if (!Providers.TryGetValue(providerKey, out var config))
{
config = Providers[DefaultProvider]; // 默认硅基流动
}
var remainder = transformContext.HttpContext.GetRouteValue("remainder")?.ToString();
if (!string.IsNullOrEmpty(remainder))
{
// 重新手动构建 RequestUri,避免 YARP 的默认转义逻辑
// 这样得到的路径就是原始的 "audio/transcriptions"
var targetUrl = $"{config.Host.TrimEnd('/')}/v1/{remainder}";
transformContext.ProxyRequest.RequestUri = new Uri(targetUrl);
}
// 替换认证信息
transformContext.ProxyRequest.Headers.Remove("Authorization");
transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", config.Key);
// 保持 Host 为目标地址,否则某些 CDN 会拒绝请求
transformContext.ProxyRequest.Headers.Host = new Uri(config.Host).Host;
await ValueTask.CompletedTask;
});
});
return services;
}
/// <summary>
/// 加入 ai 的反射代理认证
/// </summary>
/// <param name="options"></param>
/// <param name="scheme"></param>
public static void AddAiYarpReverseProxyAuthorizationOptions(this AuthorizationOptions options, string scheme)
{
options.AddPolicy("AiAccessPolicy", policy =>
{
// # "JWT_Selector"
policy.AddAuthenticationSchemes(scheme); // 关键:指定使用你配置的动态选择方案
policy.RequireAuthenticatedUser(); // 要求必须通过 Authelia 或 Introspection 认证
// 如果有组权限要求,可以加上:
// policy.RequireRole("ai-users");
});
}
}