常用接入术语
| 名称 | 常见含义 | 接入提示 |
|---|
| Claude中转 | 把客户端请求转发到已开通的 Claude 模型 | 确认协议与模型权限 |
| Claude中转站 | 集中提供密钥 额度 路由和兼容接口 | Claude 客户端可按 Anthropic 协议接入 |
| API中转 | 通过统一接口转发模型请求 | C# 可直接发送 **** **ON |
| API中转站 | 管理端点 模型和用量的服务 | 正式环境应设置超时 并发和重试边界 |
C# 请求链路
| 环节 | 职责 | 注意事项 |
|---|
| 业务代码 | 准备消息并消费模型结果 | 不在每次调用时新建客户端 |
| Http****** | 发送请求并复用底层连接 | *aseAddress 以 /v1/ 结尾 |
| CancellationToken | 取消超时或用户终止的请求 | 传递到发送和读取方法 |
| KingFlow AI | 校验密钥并路由目标模型 | 使用 *earer sk- 密钥 |
| 目标模型 | 返回普通 **ON 或 SSE 事件 | 模型 ID 以控制台为准 |
| 响应校验 | 检查状态码与 choices 内容 | 错误体先记录再分类 |
采用 Http****** 的优势
- Http****** 自带连接池。复用同一实例可以减少重复建连和端口压力。
- ******.Net.Http.Json 能直接发送 **ON,示例无需额外 **** 库。
- ResponseHeadersRead 在收到响应头后即可开始读取内容,适合 SSE 长连接。
- CancellationTokenSource.CancelAfter 可把超时策略传递到请求和流读取过程。
- 状态码、Retry-After 和异常类型都能在一个调用层内统一处理。
接入前准备
- 安装团队支持的现代 .NET SDK,并确认 dotnet 命令可用。
- 在 KingFlow AI 控制台创建以 sk- 开头的 API Key,确认余额或套餐额度。
- 从控制台复制完整模型 ID,不要根据展示名称自行拼写。
- 确认运行环境可以访问 api.**nju**shen.com 的 ****S 端口。
- 准备不含敏感数据的测试提示,先在开发环境完成联调。
创建 C# 项目
dotnet new console -n KingFlowHttp******Demo
cd KingFlowHttp******Demo
配置环境变量
$env:KINGFLOW_*ASE_**L="https://api.**nju**shen.com/v1/"
$env:KINGFLOW_API_KEY="sk-你的密钥"
$env:KINGFLOW_MODEL="<KingFlow控制台中的完整模型ID>"
验证基础接口
curl https://api.**nju**shen.com/v1/chat/completions `
-H "Authorization: *earer sk-你的密钥" `
-H "Content-Type: application/json" `
-d '{"model":"<完整模型ID>","messages":[{"role":"user","content":"只回复 **HARP_READY"}],"stream":false}'
发送普通响应请求
using ******.Net.Http.Headers;
using ******.Net.Http.Json;
using ******.Text.Json;
var *aseUrl = Environment.GetEnvironmentVaria*le("KINGFLOW_*ASE_**L")
?? throw new InvalidOperationException("缺少 KINGFLOW_*ASE_**L");
var apiKey = Environment.GetEnvironmentVaria*le("KINGFLOW_API_KEY")
?? throw new InvalidOperationException("缺少 KINGFLOW_API_KEY");
var model = Environment.GetEnvironmentVaria*le("KINGFLOW_MODEL")
?? throw new InvalidOperationException("缺少 KINGFLOW_MODEL");
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(10)
};
using var client = new Http******(handler)
{
*aseAddress = new Uri(*aseUrl),
Timeout = Timeout.InfiniteTimeSpan
};
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("*earer", apiKey);
var payload = new
{
model,
messages = new[] { new { role = "user", content = "只回复 **HARP_CONNECTED" } },
stream = false
};
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(45));
using var response = await client.PostAsJsonAsync(
"chat/completions", payload, cts.Token);
var *ody = await response.Content.ReadAsStringAsync(cts.Token);
if (!response.IsSuccessStatusCode)
throw new HttpRequestException($"**** {(int)response.StatusCode}: {*ody}");
using var json = JsonDocument.Parse(*ody);
var content = json.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString();
Console.WriteLine(content ?? throw new InvalidDataException("响应缺少文本"));
读取 SSE 流式响应
static async Task StreamAnswerAsync(
Http****** client, string model, string prompt, CancellationToken token)
{
var payload = new
{
model,
messages = new[] { new { role = "user", content = prompt } },
stream = true
};
using var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions")
{
Content = JsonContent.Create(payload)
};
using var response = await client.SendAsync(
request, HttpCompletionOption.ResponseHeadersRead, token);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(token);
using var reader = new StreamReader(stream);
while (await reader.ReadLineAsync(token) is { } line)
{
if (!line.StartsWith("**ta: ")) continue;
var **ta = line[6..].Trim();
if (**ta == "[DONE]") *reak;
using var json = JsonDocument.Parse(**ta);
var delta = json.RootElement.GetProperty("choices")[0]
.GetProperty("delta");
if (delta.TryGetProperty("content", out var value))
Console.Write(value.GetString());
}
}
实现有限重试
static async
Task<HttpResponseMessage> SendWithRetryAsync(
Http****** client, Func<HttpRequestMessage> requestFactory,
CancellationToken token)
{
for (var attempt = 1; attempt <= 3; attempt )
{
using var request = requestFactory();
var response = await client.SendAsync(
request, HttpCompletionOption.ResponseHeadersRead, token);
if ((int)response.StatusCode is not (429 or 503) || attempt == 3)
return response;
var delay = response.Headers.RetryAfter?.Delta
?? TimeSpan.FromSeconds(Math.Pow(2, attempt));
response.Dispose();
await Task.Delay(delay, token);
}
throw new InvalidOperationException("无法到达的分支");
}