## 高优先级修复 - stats_service_impl: 使用 strings.SplitN 替代错误的索引分割 - provider_handler: 使用 errors.Is(err, gorm.ErrDuplicatedKey) 替代字符串匹配 - client: 重写 isNetworkError 使用 errors.As/Is 类型安全判断 - proxy_handler: 使用 encoding/json 标准库解析 JSON(extractModelName、isStreamRequest) ## 中优先级修复 - stats_handler: 添加 parseDateParam 辅助函数消除重复日期解析 - pkg/errors: 新增 ErrRequestCreate/Send/ResponseRead 错误类型和 WithCause 方法 - client: 使用结构化错误替代 fmt.Errorf - ConversionEngine: logger 依赖注入,替换所有 zap.L() 调用 ## 低优先级修复 - encoder: 删除 joinStrings,使用 strings.Join - adapter: 删除 modelInfoRegex 正则,使用 isModelInfoPath 字符串函数 ## 文档更新 - README.md: 添加公共库使用指南和编码规范章节 - specs: 同步 delta specs 到 main specs(error-handling、structured-logging、request-validation) ## 归档 - openspec/changes/archive/2026-04-20-refactor-backend-code-quality/
206 lines
6.9 KiB
Go
206 lines
6.9 KiB
Go
package anthropic
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strings"
|
||
|
||
"nex/backend/internal/conversion"
|
||
"nex/backend/internal/conversion/canonical"
|
||
)
|
||
|
||
// Adapter Anthropic 协议适配器
|
||
type Adapter struct{}
|
||
|
||
// NewAdapter 创建 Anthropic 适配器
|
||
func NewAdapter() *Adapter {
|
||
return &Adapter{}
|
||
}
|
||
|
||
// ProtocolName 返回协议名称
|
||
func (a *Adapter) ProtocolName() string { return "anthropic" }
|
||
|
||
// ProtocolVersion 返回协议版本
|
||
func (a *Adapter) ProtocolVersion() string { return "2023-06-01" }
|
||
|
||
// SupportsPassthrough 支持同协议透传
|
||
func (a *Adapter) SupportsPassthrough() bool { return true }
|
||
|
||
// DetectInterfaceType 根据路径检测接口类型
|
||
func (a *Adapter) DetectInterfaceType(nativePath string) conversion.InterfaceType {
|
||
switch {
|
||
case nativePath == "/v1/messages":
|
||
return conversion.InterfaceTypeChat
|
||
case nativePath == "/v1/models":
|
||
return conversion.InterfaceTypeModels
|
||
case isModelInfoPath(nativePath):
|
||
return conversion.InterfaceTypeModelInfo
|
||
default:
|
||
return conversion.InterfaceTypePassthrough
|
||
}
|
||
}
|
||
|
||
// isModelInfoPath 判断是否为模型详情路径(/v1/models/{id})
|
||
func isModelInfoPath(path string) bool {
|
||
if !strings.HasPrefix(path, "/v1/models/") {
|
||
return false
|
||
}
|
||
suffix := path[len("/v1/models/"):]
|
||
return suffix != "" && !strings.Contains(suffix, "/")
|
||
}
|
||
|
||
// BuildUrl 根据接口类型构建 URL
|
||
func (a *Adapter) BuildUrl(nativePath string, interfaceType conversion.InterfaceType) string {
|
||
switch interfaceType {
|
||
case conversion.InterfaceTypeChat:
|
||
return "/v1/messages"
|
||
case conversion.InterfaceTypeModels:
|
||
return "/v1/models"
|
||
default:
|
||
return nativePath
|
||
}
|
||
}
|
||
|
||
// BuildHeaders 构建请求头
|
||
func (a *Adapter) BuildHeaders(provider *conversion.TargetProvider) map[string]string {
|
||
headers := map[string]string{
|
||
"x-api-key": provider.APIKey,
|
||
"anthropic-version": "2023-06-01",
|
||
"Content-Type": "application/json",
|
||
}
|
||
if v, ok := provider.AdapterConfig["anthropic_version"].(string); ok && v != "" {
|
||
headers["anthropic-version"] = v
|
||
}
|
||
if betas, ok := provider.AdapterConfig["anthropic_beta"].([]string); ok && len(betas) > 0 {
|
||
headers["anthropic-beta"] = strings.Join(betas, ",")
|
||
} else if betas, ok := provider.AdapterConfig["anthropic_beta"].([]any); ok && len(betas) > 0 {
|
||
var parts []string
|
||
for _, b := range betas {
|
||
if s, ok := b.(string); ok {
|
||
parts = append(parts, s)
|
||
}
|
||
}
|
||
if len(parts) > 0 {
|
||
headers["anthropic-beta"] = strings.Join(parts, ",")
|
||
}
|
||
}
|
||
return headers
|
||
}
|
||
|
||
// SupportsInterface 检查是否支持接口类型
|
||
func (a *Adapter) SupportsInterface(interfaceType conversion.InterfaceType) bool {
|
||
switch interfaceType {
|
||
case conversion.InterfaceTypeChat,
|
||
conversion.InterfaceTypeModels,
|
||
conversion.InterfaceTypeModelInfo:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// DecodeRequest 解码请求
|
||
func (a *Adapter) DecodeRequest(raw []byte) (*canonical.CanonicalRequest, error) {
|
||
return decodeRequest(raw)
|
||
}
|
||
|
||
// EncodeRequest 编码请求
|
||
func (a *Adapter) EncodeRequest(req *canonical.CanonicalRequest, provider *conversion.TargetProvider) ([]byte, error) {
|
||
return encodeRequest(req, provider)
|
||
}
|
||
|
||
// DecodeResponse 解码响应
|
||
func (a *Adapter) DecodeResponse(raw []byte) (*canonical.CanonicalResponse, error) {
|
||
return decodeResponse(raw)
|
||
}
|
||
|
||
// EncodeResponse 编码响应
|
||
func (a *Adapter) EncodeResponse(resp *canonical.CanonicalResponse) ([]byte, error) {
|
||
return encodeResponse(resp)
|
||
}
|
||
|
||
// CreateStreamDecoder 创建流式解码器
|
||
func (a *Adapter) CreateStreamDecoder() conversion.StreamDecoder {
|
||
return NewStreamDecoder()
|
||
}
|
||
|
||
// CreateStreamEncoder 创建流式编码器
|
||
func (a *Adapter) CreateStreamEncoder() conversion.StreamEncoder {
|
||
return NewStreamEncoder()
|
||
}
|
||
|
||
// EncodeError 编码错误
|
||
func (a *Adapter) EncodeError(err *conversion.ConversionError) ([]byte, int) {
|
||
errType := string(err.Code)
|
||
statusCode := 500
|
||
|
||
errMsg := ErrorResponse{
|
||
Type: "error",
|
||
Error: ErrorDetail{
|
||
Type: errType,
|
||
Message: err.Message,
|
||
},
|
||
}
|
||
body, _ := json.Marshal(errMsg)
|
||
return body, statusCode
|
||
}
|
||
|
||
// DecodeModelsResponse 解码模型列表响应
|
||
func (a *Adapter) DecodeModelsResponse(raw []byte) (*canonical.CanonicalModelList, error) {
|
||
return decodeModelsResponse(raw)
|
||
}
|
||
|
||
// EncodeModelsResponse 编码模型列表响应
|
||
func (a *Adapter) EncodeModelsResponse(list *canonical.CanonicalModelList) ([]byte, error) {
|
||
return encodeModelsResponse(list)
|
||
}
|
||
|
||
// DecodeModelInfoResponse 解码模型详情响应
|
||
func (a *Adapter) DecodeModelInfoResponse(raw []byte) (*canonical.CanonicalModelInfo, error) {
|
||
return decodeModelInfoResponse(raw)
|
||
}
|
||
|
||
// EncodeModelInfoResponse 编码模型详情响应
|
||
func (a *Adapter) EncodeModelInfoResponse(info *canonical.CanonicalModelInfo) ([]byte, error) {
|
||
return encodeModelInfoResponse(info)
|
||
}
|
||
|
||
// DecodeEmbeddingRequest Anthropic 不支持嵌入
|
||
func (a *Adapter) DecodeEmbeddingRequest(raw []byte) (*canonical.CanonicalEmbeddingRequest, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Embeddings 接口")
|
||
}
|
||
|
||
// EncodeEmbeddingRequest Anthropic 不支持嵌入
|
||
func (a *Adapter) EncodeEmbeddingRequest(req *canonical.CanonicalEmbeddingRequest, provider *conversion.TargetProvider) ([]byte, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Embeddings 接口")
|
||
}
|
||
|
||
// DecodeEmbeddingResponse Anthropic 不支持嵌入
|
||
func (a *Adapter) DecodeEmbeddingResponse(raw []byte) (*canonical.CanonicalEmbeddingResponse, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Embeddings 接口")
|
||
}
|
||
|
||
// EncodeEmbeddingResponse Anthropic 不支持嵌入
|
||
func (a *Adapter) EncodeEmbeddingResponse(resp *canonical.CanonicalEmbeddingResponse) ([]byte, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Embeddings 接口")
|
||
}
|
||
|
||
// DecodeRerankRequest Anthropic 不支持重排序
|
||
func (a *Adapter) DecodeRerankRequest(raw []byte) (*canonical.CanonicalRerankRequest, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Rerank 接口")
|
||
}
|
||
|
||
// EncodeRerankRequest Anthropic 不支持重排序
|
||
func (a *Adapter) EncodeRerankRequest(req *canonical.CanonicalRerankRequest, provider *conversion.TargetProvider) ([]byte, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Rerank 接口")
|
||
}
|
||
|
||
// DecodeRerankResponse Anthropic 不支持重排序
|
||
func (a *Adapter) DecodeRerankResponse(raw []byte) (*canonical.CanonicalRerankResponse, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Rerank 接口")
|
||
}
|
||
|
||
// EncodeRerankResponse Anthropic 不支持重排序
|
||
func (a *Adapter) EncodeRerankResponse(resp *canonical.CanonicalRerankResponse) ([]byte, error) {
|
||
return nil, conversion.NewConversionError(conversion.ErrorCodeInterfaceNotSupported, "Anthropic 不支持 Rerank 接口")
|
||
}
|