1
0
Files
nex/openspec/changes/refactor-conversion-engine/tasks.md

50 lines
18 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.
## 1. 基础类型层 — Canonical Model 和核心类型定义
- [ ] 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 接口
- [ ] 1.2 创建 `internal/conversion/interface.go`:定义 InterfaceType 枚举CHAT, MODELS, MODEL_INFO, EMBEDDINGS, RERANK
- [ ] 1.3 创建 `internal/conversion/provider.go`:定义 TargetProvider 结构体BaseURL, APIKey, ModelName, AdapterConfig map[string]any编写测试
- [ ] 1.4 创建 `internal/conversion/canonical/types.go`:定义 CanonicalRequestmodel, system, messages, tools, tool_choice, parameters, thinking, stream, user_id, output_format, parallel_tool_use、CanonicalMessagerole 枚举: system/user/assistant/tool, content []ContentBlock、ContentBlock使用 type 字段的 discriminated uniontext/tool_use/tool_result/thinkingToolInput 使用 json.RawMessage、CanonicalToolname, description, input_schema、ToolChoice 联合体auto/none/any/tool+name、RequestParametersmax_tokens, temperature, top_p, top_k, frequency_penalty, presence_penalty, stop_sequences、ThinkingConfigtype: enabled/disabled/adaptive, budget_tokens, effort、OutputFormatjson_object/json_schema+schema/text、CanonicalResponseid, model, content, stop_reason 枚举, usage、CanonicalUsageinput_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, reasoning_tokens、SystemBlocktext编写构造和序列化测试
- [ ] 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、PingEventdelta 联合体text_delta, input_json_delta, thinking_deltacontent_block 联合体text, tool_use, thinking编写测试
- [ ] 1.6 创建 `internal/conversion/canonical/extended.go`:定义扩展层 Canonical ModelsCanonicalModelList, CanonicalModel, CanonicalModelInfo, CanonicalEmbeddingRequest, CanonicalEmbeddingResponse, CanonicalRerankRequest, CanonicalRerankResponse编写测试
## 2. 接口定义层 — Adapter、Stream、Middleware 接口
- [ ] 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 注册/查询/重复注册测试
- [ ] 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 + StreamEncoderprocessChunk 内部调用 decoder → middleware → encoder 管道);编写 PassthroughStreamConverter 测试
- [ ] 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 门面
- [ ] 3.1 创建 `internal/conversion/engine.go`:定义 HTTPRequestSpecURL, Method string, Headers map[string]string, Body []byte、HTTPResponseSpecStatusCode int, Headers map[string]string, Body []byte、ConversionEngine structregistry, 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 实现
- [ ] 4.1 创建 `internal/conversion/openai/types.go`:从旧 `internal/protocol/openai/types.go` 迁移 OpenAI 线路格式类型补全缺失字段developer role, custom tools, reasoning_effort, reasoning_content, max_completion_tokens, parallel_tool_calls, response_format 的 json_schema 类型, stream_options, 废弃的 functions/function_call编写序列化测试
- [ ] 4.2 创建 `internal/conversion/openai/decoder.go`:实现 decodeRequest对照 conversion_openai.md §4.1decodeSystemPrompt 提取 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.2content/refusal/reasoning_content/tool_calls 解码、finish_reason 映射表、usage 映射含 cached_tokens/reasoning_tokens、扩展层 decodedecodeModelsResponse、decodeEmbeddingRequest/Response、decodeRerankRequest/Response编写完整测试覆盖每类消息和字段映射
- [ ] 4.3 创建 `internal/conversion/openai/encoder.go`:实现 encodeRequest对照 conversion_openai.md §4.2provider.model_name 覆盖、system 注入到 messages[0]、encodeMessage 含 tool_calls 编码到 message 顶层、角色交替合并、encodeTools 含 function 包装、encodeToolChoice 含 any→required、encodeParameters 含 max_completion_tokens、encodeOutputFormat、encodeThinking 含 disabled→"none"、encodeResponse§5.3text→content、tool_use→tool_calls、thinking→reasoning_content、finish_reason 反向映射、usage 编码含 prompt_tokens_details、扩展层 encodeencodeModelsResponse、encodeEmbeddingRequest/Response、encodeRerankRequest/Response编写完整测试
- [ ] 4.4 创建 `internal/conversion/openai/adapter.go`:实现 OpenAI ProtocolAdapterprotocolName→"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 方法;编写测试覆盖所有路径模式和边界情况
- [ ] 4.5 创建 `internal/conversion/openai/stream_decoder.go`:实现 OpenAIStreamDecoder对照 conversion_openai.md §6.2-§6.3processChunk 解析 SSE data 行,维护状态机 messageStarted/openBlocks/toolCallIdMap/toolCallNameMap/toolCallArguments/textBlockStarted/thinkingBlockStarted/utf8Remainder/accumulatedUsage首个 chunk→MessageStartEventdelta.content→text block 生命周期delta.tool_calls→tool_use block 生命周期含索引映射和参数累积delta.reasoning_content→thinking block非标准delta.refusal→text blockfinish_reason→关闭所有 open blocks + MessageDeltaEvent + MessageStopEventusage chunk→MessageDeltaEvent[DONE]→flush 关闭);编写测试覆盖每种 delta 类型和边界情况(空 chunk、多 tool_calls、UTF-8 截断)
- [ ] 4.6 创建 `internal/conversion/openai/stream_encoder.go`:实现 OpenAIStreamEncoder对照 conversion_openai.md §6.4encodeEventContentBlockStart 缓冲策略等待首次 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 并行)
- [ ] 5.1 创建 `internal/conversion/anthropic/types.go`:从旧 `internal/protocol/anthropic/types.go` 迁移 Anthropic 线路格式类型补全缺失字段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编写序列化测试
- [ ] 5.2 创建 `internal/conversion/anthropic/decoder.go`:实现 decodeRequest对照 conversion_anthropic.md §4.1decodeSystem 从顶层 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.2text/tool_use/thinking 块解码、redacted_thinking 丢弃、stop_reason 映射含 pause_turn/refusal、usage 映射含 cache_read_input_tokens/cache_creation_input_tokens、扩展层 decodedecodeModelsResponse 含 RFC3339→Unix 时间戳转换、decodeModelInfoResponse编写完整测试覆盖角色拆分、thinking 三种类型、时间戳转换
- [ ] 5.3 创建 `internal/conversion/anthropic/encoder.go`:实现 encodeRequest对照 conversion_anthropic.md §4.2provider.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.3text/tool_use/thinking 块直接编码、stop_reason 映射含 content_filter→end_turn 降级、usage 编码含 cache_read_input_tokens/cache_creation_input_tokens、扩展层 encodeencodeModelsResponse 含 Unix→RFC3339 转换和 has_more/first_id/last_id 字段、encodeModelInfoResponse编写完整测试覆盖角色合并、首消息注入、降级处理
- [ ] 5.4 创建 `internal/conversion/anthropic/adapter.go`:实现 Anthropic ProtocolAdapterprotocolName→"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}});编写测试覆盖所有路径模式和边界情况
- [ ] 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/accumulatedUsageredacted_thinking 检测后加入 redactedBlocks 并丢弃后续 delta/stopcitations_delta/signature_delta 直接丢弃server_tool_use 等服务端工具块丢弃UTF-8 跨 chunk 安全处理);编写测试覆盖所有事件类型和 redacted_thinking 丢弃
- [ ] 5.6 创建 `internal/conversion/anthropic/stream_encoder.go`:实现 AnthropicStreamEncoder对照 conversion_anthropic.md §6.4:直接映射无缓冲,每个 CanonicalStreamEvent 直接编码为对应的 Anthropic 命名 SSE 事件,格式 event: `<type>`\ndata: `<json>`\n\ndelta 编码 text_delta/input_json_delta/thinking_delta 直接映射);编写测试
## 6. 基础设施改造 — Provider、Handler、Domain
- [ ] 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'
- [ ] 6.2 重写 `internal/provider/client.go`:定义 HTTPRequestSpec 和 HTTPResponseSpec或引用 conversion 包的定义),简化 ProviderClient 接口为 Send(ctx, HTTPRequestSpec) → (*HTTPResponseSpec, error) 和 SendStream(ctx, HTTPRequestSpec) → (<-chan StreamEvent, error),移除所有 openai.Adapter 硬编码依赖Send 方法直接使用 http.NewRequest + spec.URL/Headers/BodySendStream 保留现有 readStream goroutine 逻辑但输入改为 HTTPRequestSpec重写 `provider/client_test.go`:删除所有基于 openai.ChatCompletionRequest 的旧测试用例,基于 HTTPRequestSpec 重写成功/失败/流式测试用例,使用 httptest.Server 验证请求构建和响应解析
- [ ] 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
- [ ] 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. 清理和文档
- [ ] 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`(基于 openai.ChatCompletionRequest 的签名)、重写 `handler_test.go` 为 ProxyHandler 测试(基于新 ProviderClient 接口和 ConversionEngine mock、删除 `internal/protocol/` 空目录、确认所有编译通过且无残留 import
- [ ] 7.2 更新 `README.md`:更新项目结构说明(新增 internal/conversion/、删除 internal/protocol/)、更新 API 接口说明(代理接口变更:`/{protocol}/v1/...`,移除旧路由 `/v1/chat/completions``/v1/messages`、更新配置说明Provider 新增 protocol 字段)
- [ ] 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 创建辅助函数