引入 Canonical Model 和 ProtocolAdapter 架构,支持 OpenAI/Anthropic 协议间 无缝转换,统一 ProxyHandler 替代分散的 OpenAI/Anthropic Handler,简化 ProviderClient 为协议无关的 HTTP 发送器,Provider 新增 protocol 字段。
50 lines
18 KiB
Markdown
50 lines
18 KiB
Markdown
## 1. 基础类型层 — Canonical Model 和核心类型定义
|
||
|
||
- [x] 1.1 创建 `internal/conversion/errors.go`:定义 ConversionError 结构体(Code, Message, ClientProtocol, ProviderProtocol, InterfaceType, Details, Cause)和 ErrorCode 枚举(INVALID_INPUT, MISSING_REQUIRED_FIELD, INCOMPATIBLE_FEATURE, FIELD_MAPPING_FAILURE, TOOL_CALL_PARSE_ERROR, JSON_PARSE_ERROR, STREAM_STATE_ERROR, UTF8_DECODE_ERROR, PROTOCOL_CONSTRAINT_VIOLATION, ENCODING_FAILURE, INTERFACE_NOT_SUPPORTED),实现 error 接口
|
||
- [x] 1.2 创建 `internal/conversion/interface.go`:定义 InterfaceType 枚举(CHAT, MODELS, MODEL_INFO, EMBEDDINGS, RERANK)
|
||
- [x] 1.3 创建 `internal/conversion/provider.go`:定义 TargetProvider 结构体(BaseURL, APIKey, ModelName, AdapterConfig map[string]any);编写测试
|
||
- [x] 1.4 创建 `internal/conversion/canonical/types.go`:定义 CanonicalRequest(model, system, messages, tools, tool_choice, parameters, thinking, stream, user_id, output_format, parallel_tool_use)、CanonicalMessage(role 枚举: system/user/assistant/tool, content []ContentBlock)、ContentBlock(使用 type 字段的 discriminated union:text/tool_use/tool_result/thinking,ToolInput 使用 json.RawMessage)、CanonicalTool(name, description, input_schema)、ToolChoice 联合体(auto/none/any/tool+name)、RequestParameters(max_tokens, temperature, top_p, top_k, frequency_penalty, presence_penalty, stop_sequences)、ThinkingConfig(type: enabled/disabled/adaptive, budget_tokens, effort)、OutputFormat(json_object/json_schema+schema/text)、CanonicalResponse(id, model, content, stop_reason 枚举, usage)、CanonicalUsage(input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, reasoning_tokens)、SystemBlock(text);编写构造和序列化测试
|
||
- [x] 1.5 创建 `internal/conversion/canonical/stream.go`:定义 CanonicalStreamEvent 联合体(message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop, error, ping)及各事件的具体结构(MessageStartEvent 含 message{id,model,usage}、ContentBlockStartEvent 含 index 和 content_block、ContentBlockDeltaEvent 含 index 和 delta、ContentBlockStopEvent 含 index、MessageDeltaEvent 含 delta{stop_reason} 和 usage、MessageStopEvent、ErrorEvent、PingEvent),delta 联合体(text_delta, input_json_delta, thinking_delta),content_block 联合体(text, tool_use, thinking);编写测试
|
||
- [x] 1.6 创建 `internal/conversion/canonical/extended.go`:定义扩展层 Canonical Models(CanonicalModelList, CanonicalModel, CanonicalModelInfo, CanonicalEmbeddingRequest, CanonicalEmbeddingResponse, CanonicalRerankRequest, CanonicalRerankResponse);编写测试
|
||
|
||
## 2. 接口定义层 — Adapter、Stream、Middleware 接口
|
||
|
||
- [x] 2.1 创建 `internal/conversion/adapter.go`:定义 ProtocolAdapter 接口(protocolName, protocolVersion, supportsPassthrough, detectInterfaceType, buildUrl, buildHeaders, supportsInterface, decodeRequest, encodeRequest, decodeResponse, encodeResponse, createStreamDecoder, createStreamEncoder, encodeError, 扩展层编解码方法:decodeModelsResponse/encodeModelsResponse/decodeModelInfoResponse/encodeModelInfoResponse/decodeEmbeddingRequest/encodeEmbeddingRequest/decodeEmbeddingResponse/encodeEmbeddingResponse/decodeRerankRequest/encodeRerankRequest/decodeRerankResponse/encodeRerankResponse),定义 AdapterRegistry 接口(register, get, listProtocols)和 memoryRegistry 实现(sync.RWMutex 保护的 map);编写 Registry 注册/查询/重复注册测试
|
||
- [x] 2.2 创建 `internal/conversion/stream.go`:定义 StreamDecoder 接口(processChunk(rawChunk []byte) []CanonicalStreamEvent, flush() []CanonicalStreamEvent)、StreamEncoder 接口(encodeEvent(event CanonicalStreamEvent) [][]byte, flush() [][]byte)、StreamConverter 接口(processChunk(rawChunk []byte) [][]byte, flush() [][]byte)、PassthroughStreamConverter 实现(直接传递原始字节)、CanonicalStreamConverter 实现(组合 StreamDecoder + MiddlewareChain + StreamEncoder,processChunk 内部调用 decoder → middleware → encoder 管道);编写 PassthroughStreamConverter 测试
|
||
- [x] 2.3 创建 `internal/conversion/middleware.go`:定义 ConversionMiddleware 接口(intercept(canonical, clientProtocol, providerProtocol, context) (CanonicalRequest, error) 和可选的 interceptStreamEvent(event, clientProtocol, providerProtocol, context) (CanonicalStreamEvent, error))、ConversionContext 结构体(conversionId, interfaceType, timestamp, metadata)、MiddlewareChain 结构体(按注册顺序链式执行,任一返回错误则中断后续);编写链式执行和中断测试
|
||
|
||
## 3. 引擎层 — ConversionEngine 门面
|
||
|
||
- [x] 3.1 创建 `internal/conversion/engine.go`:定义 HTTPRequestSpec(URL, Method string, Headers map[string]string, Body []byte)、HTTPResponseSpec(StatusCode int, Headers map[string]string, Body []byte)、ConversionEngine struct(registry, middlewareChain);实现 registerAdapter、use、isPassthrough、convertHttpRequest(接口识别 → 透传判断 → clientAdapter.decode → middleware → providerAdapter.encode → providerAdapter.buildUrl + buildHeaders)、convertHttpResponse(透传判断 → providerAdapter.decodeResponse → clientAdapter.encodeResponse)、createStreamConverter(透传 → PassthroughStreamConverter,否则 → CanonicalStreamConverter)、内部 convertBody 分发(CHAT 走深度转换,扩展层走轻量映射,默认透传);编写集成测试:使用 mock adapter 测试跨协议转换、同协议透传、未知接口透传
|
||
|
||
## 4. OpenAI Adapter 实现
|
||
|
||
- [x] 4.1 创建 `internal/conversion/openai/types.go`:对照 `docs/conversion_openai.md` 全新定义 OpenAI 线路格式类型(不沿用旧 `internal/protocol/openai/types.go`),包含完整字段(developer role, custom tools, reasoning_effort, reasoning_content, max_completion_tokens, parallel_tool_calls, response_format 的 json_schema 类型, stream_options, 废弃的 functions/function_call);编写序列化测试
|
||
- [x] 4.2 创建 `internal/conversion/openai/decoder.go`:实现 decodeRequest(对照 conversion_openai.md §4.1:decodeSystemPrompt 提取 system+developer 消息、decodeMessage 含 tool_calls/refusal/reasoning_content 解码、tool 消息 tool_call_id→tool_use_id、decodeTools 含 function+custom 类型、decodeToolChoice 含 required→any/allowed_tools 降级、decodeParameters 含 max_completion_tokens 优先、decodeOutputFormat、decodeThinking 含 reasoning_effort→ThinkingConfig、废弃字段 functions→tools 兼容)、decodeResponse(§5.2:content/refusal/reasoning_content/tool_calls 解码、finish_reason 映射表、usage 映射含 cached_tokens/reasoning_tokens)、扩展层 decode(decodeModelsResponse、decodeEmbeddingRequest/Response、decodeRerankRequest/Response);编写完整测试覆盖每类消息和字段映射
|
||
- [x] 4.3 创建 `internal/conversion/openai/encoder.go`:实现 encodeRequest(对照 conversion_openai.md §4.2:provider.model_name 覆盖、system 注入到 messages[0]、encodeMessage 含 tool_calls 编码到 message 顶层、角色交替合并、encodeTools 含 function 包装、encodeToolChoice 含 any→required、encodeParameters 含 max_completion_tokens、encodeOutputFormat、encodeThinking 含 disabled→"none")、encodeResponse(§5.3:text→content、tool_use→tool_calls、thinking→reasoning_content、finish_reason 反向映射、usage 编码含 prompt_tokens_details)、扩展层 encode(encodeModelsResponse、encodeEmbeddingRequest/Response、encodeRerankRequest/Response);编写完整测试
|
||
- [x] 4.4 创建 `internal/conversion/openai/adapter.go`:实现 OpenAI ProtocolAdapter(protocolName→"openai"、supportsPassthrough→true、detectInterfaceType 根据正则匹配识别 /v1/chat/completions→CHAT、/v1/models→MODELS 等、buildHeaders 含 Authorization+Content-Type+OpenAI-Organization、buildUrl 按接口类型映射、supportsInterface 对 CHAT/MODELS/MODEL_INFO/EMBEDDINGS/RERANK 返回 true、encodeError 含 ErrorCode→OpenAI 错误类型映射),组合 decoder 和 encoder 方法;编写测试覆盖所有路径模式和边界情况
|
||
- [x] 4.5 创建 `internal/conversion/openai/stream_decoder.go`:实现 OpenAIStreamDecoder(对照 conversion_openai.md §6.2-§6.3:processChunk 解析 SSE data 行,维护状态机 messageStarted/openBlocks/toolCallIdMap/toolCallNameMap/toolCallArguments/textBlockStarted/thinkingBlockStarted/utf8Remainder/accumulatedUsage,首个 chunk→MessageStartEvent,delta.content→text block 生命周期,delta.tool_calls→tool_use block 生命周期含索引映射和参数累积,delta.reasoning_content→thinking block(非标准),delta.refusal→text block,finish_reason→关闭所有 open blocks + MessageDeltaEvent + MessageStopEvent,usage chunk→MessageDeltaEvent,[DONE]→flush 关闭);编写测试覆盖每种 delta 类型和边界情况(空 chunk、多 tool_calls、UTF-8 截断)
|
||
- [x] 4.6 创建 `internal/conversion/openai/stream_encoder.go`:实现 OpenAIStreamEncoder(对照 conversion_openai.md §6.4:encodeEvent,ContentBlockStart 缓冲策略等待首次 ContentBlockDelta 合并输出,tool_use id/name 在首次 delta 时合并编码,text_delta 直接输出 data: {choices:[{delta:{content}}]},input_json_delta 含 tool_calls 数组编码,thinking_delta 含 reasoning_content 字段,MessageStartEvent→{choices:[{delta:{role:"assistant"}}]},MessageDeltaEvent→{choices:[{delta:{},finish_reason}]},MessageStopEvent→[DONE],PingEvent/ErrorEvent 丢弃,flush 输出缓冲区);编写测试
|
||
|
||
## 5. Anthropic Adapter 实现(与 Layer 4 并行)
|
||
|
||
- [x] 5.1 创建 `internal/conversion/anthropic/types.go`:对照 `docs/conversion_anthropic.md` 全新定义 Anthropic 线路格式类型(不沿用旧 `internal/protocol/anthropic/types.go`),包含完整字段(thinking.type 含 adaptive、output_config.format/effort、disable_parallel_tool_use、metadata.user_id、redacted_thinking、pause_turn/refusal stop_reason、stop_details、container、cache_control);编写序列化测试
|
||
- [x] 5.2 创建 `internal/conversion/anthropic/decoder.go`:实现 decodeRequest(对照 conversion_anthropic.md §4.1:decodeSystem 从顶层 system 提取、decodeMessage 含 tool_result 从 user 消息拆分为独立 tool 角色消息、参数直接映射含 top_k、decodeThinking 含 enabled/disabled/adaptive 三种类型、decodeOutputFormat 仅支持 json_schema、公共字段提取含 metadata.user_id/disable_parallel_tool_use 反转/output_config.effort、协议特有字段 redacted_thinking 丢弃/cache_control 忽略)、decodeResponse(§5.2:text/tool_use/thinking 块解码、redacted_thinking 丢弃、stop_reason 映射含 pause_turn/refusal、usage 映射含 cache_read_input_tokens/cache_creation_input_tokens)、扩展层 decode(decodeModelsResponse 含 RFC3339→Unix 时间戳转换、decodeModelInfoResponse);编写完整测试覆盖角色拆分、thinking 三种类型、时间戳转换
|
||
- [x] 5.3 创建 `internal/conversion/anthropic/encoder.go`:实现 encodeRequest(对照 conversion_anthropic.md §4.2:provider.model_name 覆盖、system 注入为顶层字段、encodeMessages 含 tool→user 合并(优先合并到相邻 user 消息)、首消息 user 保证(自动注入空 user)、角色交替合并、encodeThinkingConfig 含 enabled/disabled/adaptive、encodeOutputFormat 含 json_object→空 schema 降级/text 丢弃、公共字段编码含 metadata.user_id/disable_parallel_tool_use 反转/output_config、参数编码含 max_tokens 必填/top_k 直接映射)、encodeResponse(§5.3:text/tool_use/thinking 块直接编码、stop_reason 映射含 content_filter→end_turn 降级、usage 编码含 cache_read_input_tokens/cache_creation_input_tokens)、扩展层 encode(encodeModelsResponse 含 Unix→RFC3339 转换和 has_more/first_id/last_id 字段、encodeModelInfoResponse);编写完整测试覆盖角色合并、首消息注入、降级处理
|
||
- [x] 5.4 创建 `internal/conversion/anthropic/adapter.go`:实现 Anthropic ProtocolAdapter(protocolName→"anthropic"、supportsPassthrough→true、detectInterfaceType 根据正则匹配识别 /v1/messages→CHAT、/v1/models→MODELS 等、buildHeaders 含 x-api-key + anthropic-version + anthropic-beta + Content-Type、buildUrl 按接口类型映射、supportsInterface 对 CHAT/MODELS/MODEL_INFO 返回 true 对 EMBEDDINGS/RERANK 返回 false、encodeError 返回 {type:"error",error:{type,message}});编写测试覆盖所有路径模式和边界情况
|
||
- [x] 5.5 创建 `internal/conversion/anthropic/stream_decoder.go`:实现 AnthropicStreamDecoder(对照 conversion_anthropic.md §6.2-§6.3:解析命名 SSE 事件 event: message_start/data: {...},1:1 映射到 CanonicalStreamEvent,维护状态 messageStarted/redactedBlocks/utf8Remainder/accumulatedUsage,redacted_thinking 检测后加入 redactedBlocks 并丢弃后续 delta/stop,citations_delta/signature_delta 直接丢弃,server_tool_use 等服务端工具块丢弃,UTF-8 跨 chunk 安全处理);编写测试覆盖所有事件类型和 redacted_thinking 丢弃
|
||
- [x] 5.6 创建 `internal/conversion/anthropic/stream_encoder.go`:实现 AnthropicStreamEncoder(对照 conversion_anthropic.md §6.4:直接映射无缓冲,每个 CanonicalStreamEvent 直接编码为对应的 Anthropic 命名 SSE 事件,格式 event: `<type>`\ndata: `<json>`\n\n,delta 编码 text_delta/input_json_delta/thinking_delta 直接映射);编写测试
|
||
|
||
## 6. 基础设施改造 — Provider、Handler、Domain
|
||
|
||
- [x] 6.1 修改 `internal/domain/provider.go`:Provider 结构体新增 Protocol string 字段;修改 `internal/config/models.go`:GORM Provider 模型同步新增 Protocol 字段(gorm:"column:protocol;default:'openai'");修改 `internal/repository/` 中 toDomainProvider 和 toConfigProvider 转换函数同步 Protocol 字段;修改 `internal/handler/provider_handler.go`:CreateProvider 和 UpdateProvider 的请求结构体新增 Protocol 字段(可选,默认 "openai"),创建/更新 Provider 时赋值 Protocol 字段,List/Get 响应中包含 Protocol 字段;更新 `internal/service/service_test.go` 中所有创建测试 Provider 的地方补充 Protocol 字段;更新 `internal/handler/handler_test.go` 中 Provider CRUD 测试的请求体补充 Protocol 字段;创建数据库迁移文件 `backend/migrations/YYYYMMDDHHMMSS_add_provider_protocol.sql`:ALTER TABLE providers ADD COLUMN protocol TEXT DEFAULT 'openai'
|
||
- [x] 6.2 重写 `internal/provider/client.go`:定义 HTTPRequestSpec 和 HTTPResponseSpec(或引用 conversion 包的定义),简化 ProviderClient 接口为 Send(ctx, HTTPRequestSpec) → (*HTTPResponseSpec, error) 和 SendStream(ctx, HTTPRequestSpec) → (<-chan StreamEvent, error),移除所有旧协议硬编码依赖,Send 方法直接使用 http.NewRequest + spec.URL/Headers/Body,SendStream 保留现有 readStream goroutine 逻辑但输入改为 HTTPRequestSpec;重写 `provider/client_test.go`:删除所有基于旧协议类型的测试用例,基于 HTTPRequestSpec 重写成功/失败/流式测试用例,使用 httptest.Server 验证请求构建和响应解析
|
||
- [x] 6.3 创建 `internal/handler/proxy_handler.go`:实现 ProxyHandler struct(依赖 ConversionEngine、ProviderClient、RoutingService、StatsService),实现 HandleProxy(w, r) 方法:从 URL 提取 clientProtocol(仅支持 `/{protocol}/v1/...` 前缀路由,不支持旧路由)、解析请求体 JSON、调用 RoutingService.Route(modelName) 获取路由结果(含 Provider.Protocol 作为 providerProtocol)、构建 TargetProvider、调用 engine.convertHttpRequest、调用 providerClient.Send/SendStream、调用 engine.convertHttpResponse、设置响应 Content-Type 和状态码、流式处理设置 text/event-stream 并用 StreamConverter 逐块转换写入、错误处理使用 clientAdapter.encodeError、异步调用 StatsService.Record;编写测试使用 httptest + mock engine/client/service
|
||
- [x] 6.4 修改 `cmd/server/main.go`:创建 AdapterRegistry 并注册 OpenAI 和 Anthropic Adapter、创建 ConversionEngine(注入 registry)、创建 ProxyHandler(注入 engine + providerClient + routingService + statsService)、配置 Gin 路由:新增 `/{protocol}/v1/{path:*}` → ProxyHandler.HandleProxy,删除旧路由 `/v1/chat/completions` 和 `/v1/messages`,移除旧的 OpenAIHandler 和 AnthropicHandler 的路由注册,删除旧 Adapter 创建代码
|
||
|
||
## 7. 清理和文档
|
||
|
||
- [x] 7.1 删除旧代码:删除 `internal/protocol/openai/` 目录(types.go, adapter.go, adapter_test.go)、删除 `internal/protocol/anthropic/` 目录(types.go, converter.go, converter_test.go, stream_converter.go, stream_converter_test.go)、删除 `internal/handler/openai_handler.go` 和 `internal/handler/anthropic_handler.go`、删除 `internal/handler/handler_test.go` 中旧 OpenAI/Anthropic handler 测试用例和旧 `mockProviderClient`(基于旧协议类型的签名)、重写 `handler_test.go` 为 ProxyHandler 测试(基于新 ProviderClient 接口和 ConversionEngine mock)、删除 `internal/protocol/` 空目录、确认所有编译通过且无残留 import
|
||
- [x] 7.2 更新 `README.md`:更新项目结构说明(新增 internal/conversion/、删除 internal/protocol/)、更新 API 接口说明(代理接口变更:`/{protocol}/v1/...`,移除旧路由 `/v1/chat/completions` 和 `/v1/messages`)、更新配置说明(Provider 新增 protocol 字段)
|
||
- [x] 7.3 端到端测试:在 `backend/tests/integration/` 中新增 `conversion_test.go`,使用 httptest mock 上游服务器验证完整请求流:OpenAI→OpenAI 同协议透传、Anthropic→Anthropic 同协议透传、OpenAI→Anthropic 跨协议非流式、Anthropic→OpenAI 跨协议非流式、4 种方向的流式转换(含 tool_calls 和 thinking)、Models 接口跨协议转换、错误响应格式验证(各协议格式)、旧路由 `/v1/chat/completions` 和 `/v1/messages` 返回 404;复用 `tests/helpers.go` 中的测试数据库和 Provider/Model 创建辅助函数
|