| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- package claude
- import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "one-api/common"
- "one-api/dto"
- "one-api/relay/channel/openrouter"
- relaycommon "one-api/relay/common"
- "one-api/relay/helper"
- "one-api/service"
- "one-api/setting/model_setting"
- "strings"
- "github.com/gin-gonic/gin"
- )
- func stopReasonClaude2OpenAI(reason string) string {
- switch reason {
- case "stop_sequence":
- return "stop"
- case "end_turn":
- return "stop"
- case "max_tokens":
- return "max_tokens"
- case "tool_use":
- return "tool_calls"
- default:
- return reason
- }
- }
- func RequestOpenAI2ClaudeComplete(textRequest dto.GeneralOpenAIRequest) *dto.ClaudeRequest {
- claudeRequest := dto.ClaudeRequest{
- Model: textRequest.Model,
- Prompt: "",
- StopSequences: nil,
- Temperature: textRequest.Temperature,
- TopP: textRequest.TopP,
- TopK: textRequest.TopK,
- Stream: textRequest.Stream,
- }
- if claudeRequest.MaxTokensToSample == 0 {
- claudeRequest.MaxTokensToSample = 4096
- }
- prompt := ""
- for _, message := range textRequest.Messages {
- if message.Role == "user" {
- prompt += fmt.Sprintf("\n\nHuman: %s", message.StringContent())
- } else if message.Role == "assistant" {
- prompt += fmt.Sprintf("\n\nAssistant: %s", message.StringContent())
- } else if message.Role == "system" {
- if prompt == "" {
- prompt = message.StringContent()
- }
- }
- }
- prompt += "\n\nAssistant:"
- claudeRequest.Prompt = prompt
- return &claudeRequest
- }
- func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*dto.ClaudeRequest, error) {
- claudeTools := make([]dto.Tool, 0, len(textRequest.Tools))
- for _, tool := range textRequest.Tools {
- if params, ok := tool.Function.Parameters.(map[string]any); ok {
- claudeTool := dto.Tool{
- Name: tool.Function.Name,
- Description: tool.Function.Description,
- }
- claudeTool.InputSchema = make(map[string]interface{})
- if params["type"] != nil {
- claudeTool.InputSchema["type"] = params["type"].(string)
- }
- claudeTool.InputSchema["properties"] = params["properties"]
- claudeTool.InputSchema["required"] = params["required"]
- for s, a := range params {
- if s == "type" || s == "properties" || s == "required" {
- continue
- }
- claudeTool.InputSchema[s] = a
- }
- claudeTools = append(claudeTools, claudeTool)
- }
- }
- claudeRequest := dto.ClaudeRequest{
- Model: textRequest.Model,
- MaxTokens: textRequest.MaxTokens,
- StopSequences: nil,
- Temperature: textRequest.Temperature,
- TopP: textRequest.TopP,
- TopK: textRequest.TopK,
- Stream: textRequest.Stream,
- Tools: claudeTools,
- }
- if claudeRequest.MaxTokens == 0 {
- claudeRequest.MaxTokens = uint(model_setting.GetClaudeSettings().GetDefaultMaxTokens(textRequest.Model))
- }
- if model_setting.GetClaudeSettings().ThinkingAdapterEnabled &&
- strings.HasSuffix(textRequest.Model, "-thinking") {
- // 因为BudgetTokens 必须大于1024
- if claudeRequest.MaxTokens < 1280 {
- claudeRequest.MaxTokens = 1280
- }
- // BudgetTokens 为 max_tokens 的 80%
- claudeRequest.Thinking = &dto.Thinking{
- Type: "enabled",
- BudgetTokens: common.GetPointer[int](int(float64(claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)),
- }
- // TODO: 临时处理
- // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking
- claudeRequest.TopP = 0
- claudeRequest.Temperature = common.GetPointer[float64](1.0)
- claudeRequest.Model = strings.TrimSuffix(textRequest.Model, "-thinking")
- }
- if textRequest.Reasoning != nil {
- var reasoning openrouter.RequestReasoning
- if err := common.UnmarshalJson(textRequest.Reasoning, &reasoning); err != nil {
- return nil, err
- }
- budgetTokens := reasoning.MaxTokens
- if budgetTokens > 0 {
- claudeRequest.Thinking = &dto.Thinking{
- Type: "enabled",
- BudgetTokens: &budgetTokens,
- }
- }
- }
- if textRequest.Stop != nil {
- // stop maybe string/array string, convert to array string
- switch textRequest.Stop.(type) {
- case string:
- claudeRequest.StopSequences = []string{textRequest.Stop.(string)}
- case []interface{}:
- stopSequences := make([]string, 0)
- for _, stop := range textRequest.Stop.([]interface{}) {
- stopSequences = append(stopSequences, stop.(string))
- }
- claudeRequest.StopSequences = stopSequences
- }
- }
- formatMessages := make([]dto.Message, 0)
- lastMessage := dto.Message{
- Role: "tool",
- }
- for i, message := range textRequest.Messages {
- if message.Role == "" {
- textRequest.Messages[i].Role = "user"
- }
- fmtMessage := dto.Message{
- Role: message.Role,
- Content: message.Content,
- }
- if message.Role == "tool" {
- fmtMessage.ToolCallId = message.ToolCallId
- }
- if message.Role == "assistant" && message.ToolCalls != nil {
- fmtMessage.ToolCalls = message.ToolCalls
- }
- if lastMessage.Role == message.Role && lastMessage.Role != "tool" {
- if lastMessage.IsStringContent() && message.IsStringContent() {
- fmtMessage.SetStringContent(strings.Trim(fmt.Sprintf("%s %s", lastMessage.StringContent(), message.StringContent()), "\""))
- // delete last message
- formatMessages = formatMessages[:len(formatMessages)-1]
- }
- }
- if fmtMessage.Content == nil {
- fmtMessage.SetStringContent("...")
- }
- formatMessages = append(formatMessages, fmtMessage)
- lastMessage = fmtMessage
- }
- claudeMessages := make([]dto.ClaudeMessage, 0)
- isFirstMessage := true
- for _, message := range formatMessages {
- if message.Role == "system" {
- if message.IsStringContent() {
- claudeRequest.System = message.StringContent()
- } else {
- contents := message.ParseContent()
- content := ""
- for _, ctx := range contents {
- if ctx.Type == "text" {
- content += ctx.Text
- }
- }
- claudeRequest.System = content
- }
- } else {
- if isFirstMessage {
- isFirstMessage = false
- if message.Role != "user" {
- // fix: first message is assistant, add user message
- claudeMessage := dto.ClaudeMessage{
- Role: "user",
- Content: []dto.ClaudeMediaMessage{
- {
- Type: "text",
- Text: common.GetPointer[string]("..."),
- },
- },
- }
- claudeMessages = append(claudeMessages, claudeMessage)
- }
- }
- claudeMessage := dto.ClaudeMessage{
- Role: message.Role,
- }
- if message.Role == "tool" {
- if len(claudeMessages) > 0 && claudeMessages[len(claudeMessages)-1].Role == "user" {
- lastMessage := claudeMessages[len(claudeMessages)-1]
- if content, ok := lastMessage.Content.(string); ok {
- lastMessage.Content = []dto.ClaudeMediaMessage{
- {
- Type: "text",
- Text: common.GetPointer[string](content),
- },
- }
- }
- lastMessage.Content = append(lastMessage.Content.([]dto.ClaudeMediaMessage), dto.ClaudeMediaMessage{
- Type: "tool_result",
- ToolUseId: message.ToolCallId,
- Content: message.Content,
- })
- claudeMessages[len(claudeMessages)-1] = lastMessage
- continue
- } else {
- claudeMessage.Role = "user"
- claudeMessage.Content = []dto.ClaudeMediaMessage{
- {
- Type: "tool_result",
- ToolUseId: message.ToolCallId,
- Content: message.Content,
- },
- }
- }
- } else if message.IsStringContent() && message.ToolCalls == nil {
- claudeMessage.Content = message.StringContent()
- } else {
- claudeMediaMessages := make([]dto.ClaudeMediaMessage, 0)
- for _, mediaMessage := range message.ParseContent() {
- claudeMediaMessage := dto.ClaudeMediaMessage{
- Type: mediaMessage.Type,
- }
- if mediaMessage.Type == "text" {
- claudeMediaMessage.Text = common.GetPointer[string](mediaMessage.Text)
- } else {
- imageUrl := mediaMessage.GetImageMedia()
- claudeMediaMessage.Type = "image"
- claudeMediaMessage.Source = &dto.ClaudeMessageSource{
- Type: "base64",
- }
- // 判断是否是url
- if strings.HasPrefix(imageUrl.Url, "http") {
- // 是url,获取图片的类型和base64编码的数据
- fileData, err := service.GetFileBase64FromUrl(imageUrl.Url)
- if err != nil {
- return nil, fmt.Errorf("get file base64 from url failed: %s", err.Error())
- }
- claudeMediaMessage.Source.MediaType = fileData.MimeType
- claudeMediaMessage.Source.Data = fileData.Base64Data
- } else {
- _, format, base64String, err := service.DecodeBase64ImageData(imageUrl.Url)
- if err != nil {
- return nil, err
- }
- claudeMediaMessage.Source.MediaType = "image/" + format
- claudeMediaMessage.Source.Data = base64String
- }
- }
- claudeMediaMessages = append(claudeMediaMessages, claudeMediaMessage)
- }
- if message.ToolCalls != nil {
- for _, toolCall := range message.ParseToolCalls() {
- inputObj := make(map[string]any)
- if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj); err != nil {
- common.SysError("tool call function arguments is not a map[string]any: " + fmt.Sprintf("%v", toolCall.Function.Arguments))
- continue
- }
- claudeMediaMessages = append(claudeMediaMessages, dto.ClaudeMediaMessage{
- Type: "tool_use",
- Id: toolCall.ID,
- Name: toolCall.Function.Name,
- Input: inputObj,
- })
- }
- }
- claudeMessage.Content = claudeMediaMessages
- }
- claudeMessages = append(claudeMessages, claudeMessage)
- }
- }
- claudeRequest.Prompt = ""
- claudeRequest.Messages = claudeMessages
- return &claudeRequest, nil
- }
- func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto.ChatCompletionsStreamResponse {
- var response dto.ChatCompletionsStreamResponse
- response.Object = "chat.completion.chunk"
- response.Model = claudeResponse.Model
- response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0)
- tools := make([]dto.ToolCallResponse, 0)
- fcIdx := 0
- if claudeResponse.Index != nil {
- fcIdx = *claudeResponse.Index - 1
- if fcIdx < 0 {
- fcIdx = 0
- }
- }
- var choice dto.ChatCompletionsStreamResponseChoice
- if reqMode == RequestModeCompletion {
- choice.Delta.SetContentString(claudeResponse.Completion)
- finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason)
- if finishReason != "null" {
- choice.FinishReason = &finishReason
- }
- } else {
- if claudeResponse.Type == "message_start" {
- response.Id = claudeResponse.Message.Id
- response.Model = claudeResponse.Message.Model
- //claudeUsage = &claudeResponse.Message.Usage
- choice.Delta.SetContentString("")
- choice.Delta.Role = "assistant"
- } else if claudeResponse.Type == "content_block_start" {
- if claudeResponse.ContentBlock != nil {
- //choice.Delta.SetContentString(claudeResponse.ContentBlock.Text)
- if claudeResponse.ContentBlock.Type == "tool_use" {
- tools = append(tools, dto.ToolCallResponse{
- Index: common.GetPointer(fcIdx),
- ID: claudeResponse.ContentBlock.Id,
- Type: "function",
- Function: dto.FunctionResponse{
- Name: claudeResponse.ContentBlock.Name,
- Arguments: "",
- },
- })
- }
- } else {
- return nil
- }
- } else if claudeResponse.Type == "content_block_delta" {
- if claudeResponse.Delta != nil {
- choice.Delta.Content = claudeResponse.Delta.Text
- switch claudeResponse.Delta.Type {
- case "input_json_delta":
- tools = append(tools, dto.ToolCallResponse{
- Type: "function",
- Index: common.GetPointer(fcIdx),
- Function: dto.FunctionResponse{
- Arguments: *claudeResponse.Delta.PartialJson,
- },
- })
- case "signature_delta":
- // 加密的不处理
- signatureContent := "\n"
- choice.Delta.ReasoningContent = &signatureContent
- case "thinking_delta":
- thinkingContent := claudeResponse.Delta.Thinking
- choice.Delta.ReasoningContent = &thinkingContent
- }
- }
- } else if claudeResponse.Type == "message_delta" {
- finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason)
- if finishReason != "null" {
- choice.FinishReason = &finishReason
- }
- //claudeUsage = &claudeResponse.Usage
- } else if claudeResponse.Type == "message_stop" {
- return nil
- } else {
- return nil
- }
- }
- if len(tools) > 0 {
- choice.Delta.Content = nil // compatible with other OpenAI derivative applications, like LobeOpenAICompatibleFactory ...
- choice.Delta.ToolCalls = tools
- }
- response.Choices = append(response.Choices, choice)
- return &response
- }
- func ResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto.OpenAITextResponse {
- choices := make([]dto.OpenAITextResponseChoice, 0)
- fullTextResponse := dto.OpenAITextResponse{
- Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
- Object: "chat.completion",
- Created: common.GetTimestamp(),
- }
- var responseText string
- var responseThinking string
- if len(claudeResponse.Content) > 0 {
- responseText = claudeResponse.Content[0].GetText()
- responseThinking = claudeResponse.Content[0].Thinking
- }
- tools := make([]dto.ToolCallResponse, 0)
- thinkingContent := ""
- if reqMode == RequestModeCompletion {
- choice := dto.OpenAITextResponseChoice{
- Index: 0,
- Message: dto.Message{
- Role: "assistant",
- Content: strings.TrimPrefix(claudeResponse.Completion, " "),
- Name: nil,
- },
- FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
- }
- choices = append(choices, choice)
- } else {
- fullTextResponse.Id = claudeResponse.Id
- for _, message := range claudeResponse.Content {
- switch message.Type {
- case "tool_use":
- args, _ := json.Marshal(message.Input)
- tools = append(tools, dto.ToolCallResponse{
- ID: message.Id,
- Type: "function", // compatible with other OpenAI derivative applications
- Function: dto.FunctionResponse{
- Name: message.Name,
- Arguments: string(args),
- },
- })
- case "thinking":
- // 加密的不管, 只输出明文的推理过程
- thinkingContent = message.Thinking
- case "text":
- responseText = message.GetText()
- }
- }
- }
- choice := dto.OpenAITextResponseChoice{
- Index: 0,
- Message: dto.Message{
- Role: "assistant",
- },
- FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
- }
- choice.SetStringContent(responseText)
- if len(responseThinking) > 0 {
- choice.ReasoningContent = responseThinking
- }
- if len(tools) > 0 {
- choice.Message.SetToolCalls(tools)
- }
- choice.Message.ReasoningContent = thinkingContent
- fullTextResponse.Model = claudeResponse.Model
- choices = append(choices, choice)
- fullTextResponse.Choices = choices
- return &fullTextResponse
- }
- type ClaudeResponseInfo struct {
- ResponseId string
- Created int64
- Model string
- ResponseText strings.Builder
- Usage *dto.Usage
- Done bool
- }
- func FormatClaudeResponseInfo(requestMode int, claudeResponse *dto.ClaudeResponse, oaiResponse *dto.ChatCompletionsStreamResponse, claudeInfo *ClaudeResponseInfo) bool {
- if requestMode == RequestModeCompletion {
- claudeInfo.ResponseText.WriteString(claudeResponse.Completion)
- } else {
- if claudeResponse.Type == "message_start" {
- claudeInfo.ResponseId = claudeResponse.Message.Id
- claudeInfo.Model = claudeResponse.Message.Model
- // message_start, 获取usage
- claudeInfo.Usage.PromptTokens = claudeResponse.Message.Usage.InputTokens
- claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Message.Usage.CacheReadInputTokens
- claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Message.Usage.CacheCreationInputTokens
- claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens
- } else if claudeResponse.Type == "content_block_delta" {
- if claudeResponse.Delta.Text != nil {
- claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Text)
- }
- if claudeResponse.Delta.Thinking != "" {
- claudeInfo.ResponseText.WriteString(claudeResponse.Delta.Thinking)
- }
- } else if claudeResponse.Type == "message_delta" {
- // 最终的usage获取
- if claudeResponse.Usage.InputTokens > 0 {
- // 不叠加,只取最新的
- claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
- }
- claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens
- claudeInfo.Usage.TotalTokens = claudeInfo.Usage.PromptTokens + claudeInfo.Usage.CompletionTokens
- // 判断是否完整
- claudeInfo.Done = true
- } else if claudeResponse.Type == "content_block_start" {
- } else {
- return false
- }
- }
- if oaiResponse != nil {
- oaiResponse.Id = claudeInfo.ResponseId
- oaiResponse.Created = claudeInfo.Created
- oaiResponse.Model = claudeInfo.Model
- }
- return true
- }
- func HandleStreamResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, data string, requestMode int) *dto.OpenAIErrorWithStatusCode {
- var claudeResponse dto.ClaudeResponse
- err := common.UnmarshalJsonStr(data, &claudeResponse)
- if err != nil {
- common.SysError("error unmarshalling stream response: " + err.Error())
- return service.OpenAIErrorWrapper(err, "stream_response_error", http.StatusInternalServerError)
- }
- if claudeResponse.Error != nil && claudeResponse.Error.Type != "" {
- return &dto.OpenAIErrorWithStatusCode{
- Error: dto.OpenAIError{
- Code: "stream_response_error",
- Type: claudeResponse.Error.Type,
- Message: claudeResponse.Error.Message,
- },
- StatusCode: http.StatusInternalServerError,
- }
- }
- if info.RelayFormat == relaycommon.RelayFormatClaude {
- FormatClaudeResponseInfo(requestMode, &claudeResponse, nil, claudeInfo)
- if requestMode == RequestModeCompletion {
- } else {
- if claudeResponse.Type == "message_start" {
- // message_start, 获取usage
- info.UpstreamModelName = claudeResponse.Message.Model
- } else if claudeResponse.Type == "content_block_delta" {
- } else if claudeResponse.Type == "message_delta" {
- }
- }
- helper.ClaudeChunkData(c, claudeResponse, data)
- } else if info.RelayFormat == relaycommon.RelayFormatOpenAI {
- response := StreamResponseClaude2OpenAI(requestMode, &claudeResponse)
- if !FormatClaudeResponseInfo(requestMode, &claudeResponse, response, claudeInfo) {
- return nil
- }
- err = helper.ObjectData(c, response)
- if err != nil {
- common.LogError(c, "send_stream_response_failed: "+err.Error())
- }
- }
- return nil
- }
- func HandleStreamFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, requestMode int) {
- if requestMode == RequestModeCompletion {
- claudeInfo.Usage = service.ResponseText2Usage(claudeInfo.ResponseText.String(), info.UpstreamModelName, info.PromptTokens)
- } else {
- if claudeInfo.Usage.PromptTokens == 0 {
- //上游出错
- }
- if claudeInfo.Usage.CompletionTokens == 0 || !claudeInfo.Done {
- if common.DebugEnabled {
- common.SysError("claude response usage is not complete, maybe upstream error")
- }
- claudeInfo.Usage = service.ResponseText2Usage(claudeInfo.ResponseText.String(), info.UpstreamModelName, claudeInfo.Usage.PromptTokens)
- }
- }
- if info.RelayFormat == relaycommon.RelayFormatClaude {
- //
- } else if info.RelayFormat == relaycommon.RelayFormatOpenAI {
- if info.ShouldIncludeUsage {
- response := helper.GenerateFinalUsageResponse(claudeInfo.ResponseId, claudeInfo.Created, info.UpstreamModelName, *claudeInfo.Usage)
- err := helper.ObjectData(c, response)
- if err != nil {
- common.SysError("send final response failed: " + err.Error())
- }
- }
- helper.Done(c)
- }
- }
- func ClaudeStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, requestMode int) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
- claudeInfo := &ClaudeResponseInfo{
- ResponseId: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
- Created: common.GetTimestamp(),
- Model: info.UpstreamModelName,
- ResponseText: strings.Builder{},
- Usage: &dto.Usage{},
- }
- var err *dto.OpenAIErrorWithStatusCode
- helper.StreamScannerHandler(c, resp, info, func(data string) bool {
- err = HandleStreamResponseData(c, info, claudeInfo, data, requestMode)
- if err != nil {
- return false
- }
- return true
- })
- if err != nil {
- return err, nil
- }
- HandleStreamFinalResponse(c, info, claudeInfo, requestMode)
- return nil, claudeInfo.Usage
- }
- func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, data []byte, requestMode int) *dto.OpenAIErrorWithStatusCode {
- var claudeResponse dto.ClaudeResponse
- err := common.UnmarshalJson(data, &claudeResponse)
- if err != nil {
- return service.OpenAIErrorWrapper(err, "unmarshal_claude_response_failed", http.StatusInternalServerError)
- }
- if claudeResponse.Error != nil && claudeResponse.Error.Type != "" {
- return &dto.OpenAIErrorWithStatusCode{
- Error: dto.OpenAIError{
- Message: claudeResponse.Error.Message,
- Type: claudeResponse.Error.Type,
- Code: claudeResponse.Error.Type,
- },
- StatusCode: http.StatusInternalServerError,
- }
- }
- if requestMode == RequestModeCompletion {
- completionTokens := service.CountTextToken(claudeResponse.Completion, info.OriginModelName)
- claudeInfo.Usage.PromptTokens = info.PromptTokens
- claudeInfo.Usage.CompletionTokens = completionTokens
- claudeInfo.Usage.TotalTokens = info.PromptTokens + completionTokens
- } else {
- claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
- claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens
- claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens
- claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
- claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
- }
- var responseData []byte
- switch info.RelayFormat {
- case relaycommon.RelayFormatOpenAI:
- openaiResponse := ResponseClaude2OpenAI(requestMode, &claudeResponse)
- openaiResponse.Usage = *claudeInfo.Usage
- responseData, err = json.Marshal(openaiResponse)
- if err != nil {
- return service.OpenAIErrorWrapper(err, "marshal_response_body_failed", http.StatusInternalServerError)
- }
- case relaycommon.RelayFormatClaude:
- responseData = data
- }
- common.IOCopyBytesGracefully(c, nil, responseData)
- return nil
- }
- func ClaudeHandler(c *gin.Context, resp *http.Response, requestMode int, info *relaycommon.RelayInfo) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
- defer common.CloseResponseBodyGracefully(resp)
- claudeInfo := &ClaudeResponseInfo{
- ResponseId: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
- Created: common.GetTimestamp(),
- Model: info.UpstreamModelName,
- ResponseText: strings.Builder{},
- Usage: &dto.Usage{},
- }
- responseBody, err := io.ReadAll(resp.Body)
- if err != nil {
- return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
- }
- if common.DebugEnabled {
- println("responseBody: ", string(responseBody))
- }
- handleErr := HandleClaudeResponseData(c, info, claudeInfo, responseBody, requestMode)
- if handleErr != nil {
- return handleErr, nil
- }
- return nil, claudeInfo.Usage
- }
|