package handler import ( "bufio" "fmt" "net/http" "github.com/gin-gonic/gin" appErrors "nex/backend/pkg/errors" "nex/backend/internal/domain" "nex/backend/internal/protocol/anthropic" "nex/backend/internal/protocol/openai" "nex/backend/internal/provider" "nex/backend/internal/service" ) // AnthropicHandler Anthropic 协议处理器 type AnthropicHandler struct { client provider.ProviderClient routingService service.RoutingService statsService service.StatsService } // NewAnthropicHandler 创建 Anthropic 处理器 func NewAnthropicHandler(client provider.ProviderClient, routingService service.RoutingService, statsService service.StatsService) *AnthropicHandler { return &AnthropicHandler{ client: client, routingService: routingService, statsService: statsService, } } // HandleMessages 处理 Messages 请求 func (h *AnthropicHandler) HandleMessages(c *gin.Context) { var req anthropic.MessagesRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "invalid_request_error", Message: "无效的请求格式: " + err.Error(), }, }) return } // 请求验证 if validationErrors := anthropic.ValidateRequest(&req); validationErrors != nil { errMsg := formatValidationErrors(validationErrors) c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "invalid_request_error", Message: errMsg, }, }) return } if err := h.checkMultimodalContent(&req); err != nil { c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "invalid_request_error", Message: err.Error(), }, }) return } openaiReq, err := anthropic.ConvertRequest(&req) if err != nil { c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "invalid_request_error", Message: "请求转换失败: " + err.Error(), }, }) return } routeResult, err := h.routingService.Route(openaiReq.Model) if err != nil { h.handleError(c, err) return } if req.Stream { h.handleStreamRequest(c, openaiReq, routeResult) } else { h.handleNonStreamRequest(c, openaiReq, routeResult) } } func (h *AnthropicHandler) handleNonStreamRequest(c *gin.Context, openaiReq *openai.ChatCompletionRequest, routeResult *domain.RouteResult) { openaiResp, err := h.client.SendRequest(c.Request.Context(), openaiReq, routeResult.Provider.APIKey, routeResult.Provider.BaseURL) if err != nil { c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "api_error", Message: "供应商请求失败: " + err.Error(), }, }) return } anthropicResp, err := anthropic.ConvertResponse(openaiResp) if err != nil { c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "api_error", Message: "响应转换失败: " + err.Error(), }, }) return } go func() { _ = h.statsService.Record(routeResult.Provider.ID, openaiReq.Model) }() c.JSON(http.StatusOK, anthropicResp) } func (h *AnthropicHandler) handleStreamRequest(c *gin.Context, openaiReq *openai.ChatCompletionRequest, routeResult *domain.RouteResult) { eventChan, err := h.client.SendStreamRequest(c.Request.Context(), openaiReq, routeResult.Provider.APIKey, routeResult.Provider.BaseURL) if err != nil { c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "api_error", Message: "供应商请求失败: " + err.Error(), }, }) return } c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") writer := bufio.NewWriter(c.Writer) converter := anthropic.NewStreamConverter( fmt.Sprintf("msg_%s", routeResult.Provider.ID), openaiReq.Model, ) for event := range eventChan { if event.Error != nil { break } if event.Done { break } chunk, err := openai.NewAdapter().ParseStreamChunk(event.Data) if err != nil { continue } anthropicEvents, err := converter.ConvertChunk(chunk) if err != nil { continue } for _, ae := range anthropicEvents { eventStr, err := anthropic.SerializeEvent(ae) if err != nil { continue } writer.WriteString(eventStr) writer.Flush() } } go func() { _ = h.statsService.Record(routeResult.Provider.ID, openaiReq.Model) }() } func (h *AnthropicHandler) checkMultimodalContent(req *anthropic.MessagesRequest) error { for _, msg := range req.Messages { for _, block := range msg.Content { if block.Type == "image" { return fmt.Errorf("MVP 不支持多模态内容(图片)") } } } return nil } func (h *AnthropicHandler) handleError(c *gin.Context, err error) { if appErr, ok := appErrors.AsAppError(err); ok { c.JSON(appErr.HTTPStatus, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "not_found_error", Message: appErr.Message, }, }) return } c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{ Type: "error", Error: anthropic.ErrorDetail{ Type: "internal_error", Message: "内部错误: " + err.Error(), }, }) }