free-code-dotnet/docs/核心模块设计/核心模块设计-API提供商路由.md
应文浩wenhao.ying@xiaobao100.com e25ac591a7 init easy-code
2026-04-06 07:24:24 +08:00

201 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 核心模块设计 — API 多提供商路由
> 所属项目: free-code .NET 10 重写
> 原始代码来源: `../../src/services/api/`
> 原始设计意图: 支持五种 API 提供商Anthropic、OpenAI Codex、AWS Bedrock、Google Vertex、Anthropic Foundry的统一路由通过环境变量自动检测活跃提供商并为每个提供商实现 SSE 流式解析
> 上级文档: [核心模块设计总览](核心模块设计.md)
---
## 概述
API 多提供商路由模块是 free-code 多后端能力的核心。它将具体 API 调用细节从 `QueryEngine` 中解耦出来,使查询引擎无需关心当前使用的是哪家服务商的 API。
原始 TypeScript 实现通过 `../../src/services/api/` 目录下的多个文件分别实现各提供商的客户端。.NET 重写引入 `IApiProvider` 接口统一抽象,通过 `ApiProviderRouter` 在运行时选择具体实现。
---
## 9.1 提供商枚举
```csharp
public enum ApiProviderType
{
Anthropic, // 默认,直连 Anthropic API
OpenAICodex, // CLAUDE_CODE_USE_OPENAI=1
AwsBedrock, // CLAUDE_CODE_USE_BEDROCK=1
GoogleVertex, // CLAUDE_CODE_USE_VERTEX=1
AnthropicFoundry // CLAUDE_CODE_USE_FOUNDRY=1
}
```
---
## 9.2 IApiProvider 接口
```csharp
/// <summary>API提供商抽象接口</summary>
public interface IApiProvider
{
/// <summary>发起流式 API 请求,返回 SSE 消息流</summary>
IAsyncEnumerable<SDKMessage> StreamAsync(
ApiRequest request,
CancellationToken ct = default);
}
public record ApiRequest(
string SystemPrompt,
IReadOnlyList<ApiMessage> Messages,
IReadOnlyList<ITool> Tools,
string? Model = null
);
```
---
## 9.3 ApiProviderRouter — 路由器
`ApiProviderRouter` 在构造时检测环境变量,确定活跃提供商,后续每次调用 `GetActiveProvider()` 都返回对应的实例。
**原始设计意图:** 原始代码通过 `process.env` 检查选择不同的 API 客户端模块。.NET 版本将这一逻辑集中在路由器的构造函数中,避免散布在代码各处的条件判断。
```csharp
public class ApiProviderRouter : IApiProviderRouter
{
private readonly IServiceProvider _services;
private ApiProviderType _activeProvider;
public ApiProviderRouter(IServiceProvider services)
{
_services = services;
_activeProvider = DetectProvider(); // 从环境变量自动检测
}
private static ApiProviderType DetectProvider()
{
if (Env("CLAUDE_CODE_USE_OPENAI") == "1") return ApiProviderType.OpenAICodex;
if (Env("CLAUDE_CODE_USE_BEDROCK") == "1") return ApiProviderType.AwsBedrock;
if (Env("CLAUDE_CODE_USE_VERTEX") == "1") return ApiProviderType.GoogleVertex;
if (Env("CLAUDE_CODE_USE_FOUNDRY") == "1") return ApiProviderType.AnthropicFoundry;
return ApiProviderType.Anthropic;
}
public IApiProvider GetActiveProvider() => _activeProvider switch
{
ApiProviderType.Anthropic => _services.GetRequiredService<AnthropicProvider>(),
ApiProviderType.OpenAICodex => _services.GetRequiredService<CodexProvider>(),
ApiProviderType.AwsBedrock => _services.GetRequiredService<BedrockProvider>(),
ApiProviderType.GoogleVertex => _services.GetRequiredService<VertexProvider>(),
ApiProviderType.AnthropicFoundry => _services.GetRequiredService<FoundryProvider>(),
_ => throw new InvalidOperationException()
};
private static string? Env(string name) => Environment.GetEnvironmentVariable(name);
}
```
检测优先级为OpenAI > Bedrock > Vertex > Foundry > Anthropic默认。若同时设置多个环境变量优先级靠前的生效。
---
## 9.4 AnthropicProvider — SSE 流式解析
`AnthropicProvider` 实现直连 Anthropic Messages API 的 SSE 流式解析,是五个提供商中最核心的实现。
**原始设计意图:** 原始 `claude.ts` 使用 Anthropic TypeScript SDK 处理流式响应。.NET 版本直接使用 `HttpClient` 发送 HTTP 请求,手动解析 SSE 事件行,避免引入额外 SDK 依赖。
```csharp
public class AnthropicProvider : IApiProvider
{
private readonly HttpClient _httpClient;
public async IAsyncEnumerable<SDKMessage> StreamAsync(
ApiRequest request, [EnumeratorCancellation] CancellationToken ct = default)
{
var payload = new {
model = request.Model ?? "claude-sonnet-4-6",
max_tokens = 16384,
stream = true,
system = request.SystemPrompt,
messages = request.Messages,
tools = request.Tools
};
var httpReq = new HttpRequestMessage(HttpMethod.Post,
$"{GetBaseUrl()}/v1/messages")
{
Content = JsonContent.Create(payload)
};
httpReq.Headers.Add("x-api-key", GetApiKey());
httpReq.Headers.Add("anthropic-version", "2023-06-01");
using var response = await _httpClient.SendAsync(
httpReq, HttpCompletionOption.ResponseHeadersRead, ct);
response.EnsureSuccessStatusCode();
// SSE 流式解析
using var stream = await response.Content.ReadAsStreamAsync(ct);
using var reader = new StreamReader(stream);
while (!reader.EndOfStream && !ct.IsCancellationRequested)
{
var line = await reader.ReadLineAsync(ct);
if (string.IsNullOrEmpty(line) || !line.StartsWith("data: ")) continue;
var json = line[6..];
var doc = JsonDocument.Parse(json);
var type = doc.RootElement.GetProperty("type").GetString();
switch (type)
{
case "content_block_delta":
yield return new SDKMessage.StreamingDelta(
doc.RootElement.GetProperty("delta")
.GetProperty("text").GetString()!);
break;
case "content_block_start":
var block = doc.RootElement.GetProperty("content_block");
if (block.GetProperty("type").GetString() == "tool_use")
yield return new SDKMessage.ToolUseStart(
block.GetProperty("id").GetString()!,
block.GetProperty("name").GetString()!,
block.GetProperty("input"));
break;
case "message_stop":
yield break;
}
}
}
}
```
### SSE 事件类型映射
| SSE `type` | 映射到 `SDKMessage` 子类 | 说明 |
|------------|--------------------------|------|
| `content_block_delta` | `SDKMessage.StreamingDelta` | 文本流式增量 |
| `content_block_start` (tool_use) | `SDKMessage.ToolUseStart` | 工具调用开始 |
| `message_stop` | `yield break` | 流结束 |
| 其他 | 忽略 | `ping``message_start` 等元数据事件 |
`HttpCompletionOption.ResponseHeadersRead` 确保在响应头返回后立即开始读取流,而不是等待整个响应体下载完成,这是 SSE 流式解析的关键设置。
---
## 9.5 提供商配置汇总
| 提供商 | 环境变量 | 认证方式 | 端点 |
|--------|----------|----------|------|
| Anthropic | 默认 | `ANTHROPIC_API_KEY` 或 OAuth | `https://api.anthropic.com` |
| OpenAI Codex | `CLAUDE_CODE_USE_OPENAI=1` | OAuth via OpenAI | `https://api.openai.com` |
| AWS Bedrock | `CLAUDE_CODE_USE_BEDROCK=1` | AWS 标准凭证链 | `AWS_REGION` 对应端点 |
| Google Vertex | `CLAUDE_CODE_USE_VERTEX=1` | GCP ADC | GCP 项目对应端点 |
| Anthropic Foundry | `CLAUDE_CODE_USE_FOUNDRY=1` | `ANTHROPIC_FOUNDRY_API_KEY` | 自定义部署端点 |
---
## 参考资料
- [核心模块设计总览](核心模块设计.md)
- [查询引擎 (QueryEngine)](核心模块设计-查询引擎-QueryEngine.md)
- [原始代码映射 — 核心模块](reference/原始代码映射-核心模块.md)