remove: drop support for claude-2 and claude-1 series models

- Remove claude-instant-1.2, claude-2, claude-2.0, claude-2.1 from model lists
- Remove /v1/complete endpoint support (legacy completion API)
- Remove RequestModeCompletion and related code paths
- Simplify handler functions by removing requestMode parameter
- Update all channel adaptors that referenced claude handlers
This commit is contained in:
CaIon 2026-02-05 17:20:46 +08:00
parent 3b1866b6af
commit 330e1e6395
13 changed files with 183 additions and 288 deletions

View File

@ -224,10 +224,10 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
case types.RelayFormatClaude: case types.RelayFormatClaude:
if supportsAliAnthropicMessages(info.UpstreamModelName) { if supportsAliAnthropicMessages(info.UpstreamModelName) {
if info.IsStream { if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeStreamHandler(c, resp, info)
} }
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeHandler(c, resp, info)
} }
adaptor := openai.Adaptor{} adaptor := openai.Adaptor{}

View File

@ -3,10 +3,7 @@ package aws
import "strings" import "strings"
var awsModelIDMap = map[string]string{ var awsModelIDMap = map[string]string{
"claude-instant-1.2": "anthropic.claude-instant-v1", "claude-3-sonnet-20240229": "anthropic.claude-3-sonnet-20240229-v1:0",
"claude-2.0": "anthropic.claude-v2",
"claude-2.1": "anthropic.claude-v2:1",
"claude-3-sonnet-20240229": "anthropic.claude-3-sonnet-20240229-v1:0",
"claude-3-opus-20240229": "anthropic.claude-3-opus-20240229-v1:0", "claude-3-opus-20240229": "anthropic.claude-3-opus-20240229-v1:0",
"claude-3-haiku-20240307": "anthropic.claude-3-haiku-20240307-v1:0", "claude-3-haiku-20240307": "anthropic.claude-3-haiku-20240307-v1:0",
"claude-3-5-sonnet-20240620": "anthropic.claude-3-5-sonnet-20240620-v1:0", "claude-3-5-sonnet-20240620": "anthropic.claude-3-5-sonnet-20240620-v1:0",

View File

@ -233,7 +233,7 @@ func awsHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (*types
c.Writer.Header().Set("Content-Type", *awsResp.ContentType) c.Writer.Header().Set("Content-Type", *awsResp.ContentType)
} }
handlerErr := claude.HandleClaudeResponseData(c, info, claudeInfo, nil, awsResp.Body, claude.RequestModeMessage) handlerErr := claude.HandleClaudeResponseData(c, info, claudeInfo, nil, awsResp.Body)
if handlerErr != nil { if handlerErr != nil {
return handlerErr, nil return handlerErr, nil
} }
@ -264,7 +264,7 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (
switch v := event.(type) { switch v := event.(type) {
case *bedrockruntimeTypes.ResponseStreamMemberChunk: case *bedrockruntimeTypes.ResponseStreamMemberChunk:
info.SetFirstResponseTime() info.SetFirstResponseTime()
respErr := claude.HandleStreamResponseData(c, info, claudeInfo, string(v.Value.Bytes), claude.RequestModeMessage) respErr := claude.HandleStreamResponseData(c, info, claudeInfo, string(v.Value.Bytes))
if respErr != nil { if respErr != nil {
return respErr, nil return respErr, nil
} }
@ -277,7 +277,7 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (
} }
} }
claude.HandleStreamFinalResponse(c, info, claudeInfo, claude.RequestModeMessage) claude.HandleStreamFinalResponse(c, info, claudeInfo)
return nil, claudeInfo.Usage return nil, claudeInfo.Usage
} }

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings"
"github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/relay/channel" "github.com/QuantumNous/new-api/relay/channel"
@ -16,13 +15,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
const (
RequestModeCompletion = 1
RequestModeMessage = 2
)
type Adaptor struct { type Adaptor struct {
RequestMode int
} }
func (a *Adaptor) ConvertGeminiRequest(*gin.Context, *relaycommon.RelayInfo, *dto.GeminiChatRequest) (any, error) { func (a *Adaptor) ConvertGeminiRequest(*gin.Context, *relaycommon.RelayInfo, *dto.GeminiChatRequest) (any, error) {
@ -45,20 +38,10 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
} }
func (a *Adaptor) Init(info *relaycommon.RelayInfo) { func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
if strings.HasPrefix(info.UpstreamModelName, "claude-2") || strings.HasPrefix(info.UpstreamModelName, "claude-instant") {
a.RequestMode = RequestModeCompletion
} else {
a.RequestMode = RequestModeMessage
}
} }
func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) { func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
baseURL := "" baseURL := fmt.Sprintf("%s/v1/messages", info.ChannelBaseUrl)
if a.RequestMode == RequestModeMessage {
baseURL = fmt.Sprintf("%s/v1/messages", info.ChannelBaseUrl)
} else {
baseURL = fmt.Sprintf("%s/v1/complete", info.ChannelBaseUrl)
}
if info.IsClaudeBetaQuery { if info.IsClaudeBetaQuery {
baseURL = baseURL + "?beta=true" baseURL = baseURL + "?beta=true"
} }
@ -90,11 +73,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
if request == nil { if request == nil {
return nil, errors.New("request is nil") return nil, errors.New("request is nil")
} }
if a.RequestMode == RequestModeCompletion { return RequestOpenAI2ClaudeMessage(c, *request)
return RequestOpenAI2ClaudeComplete(*request), nil
} else {
return RequestOpenAI2ClaudeMessage(c, *request)
}
} }
func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) { func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
@ -117,11 +96,10 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) { func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *types.NewAPIError) {
if info.IsStream { if info.IsStream {
return ClaudeStreamHandler(c, resp, info, a.RequestMode) return ClaudeStreamHandler(c, resp, info)
} else { } else {
return ClaudeHandler(c, resp, info, a.RequestMode) return ClaudeHandler(c, resp, info)
} }
return
} }
func (a *Adaptor) GetModelList() []string { func (a *Adaptor) GetModelList() []string {

View File

@ -1,10 +1,6 @@
package claude package claude
var ModelList = []string{ var ModelList = []string{
"claude-instant-1.2",
"claude-2",
"claude-2.0",
"claude-2.1",
"claude-3-sonnet-20240229", "claude-3-sonnet-20240229",
"claude-3-opus-20240229", "claude-3-opus-20240229",
"claude-3-haiku-20240307", "claude-3-haiku-20240307",

View File

@ -41,37 +41,6 @@ func maybeMarkClaudeRefusal(c *gin.Context, stopReason string) {
} }
} }
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(c *gin.Context, textRequest dto.GeneralOpenAIRequest) (*dto.ClaudeRequest, error) { func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRequest) (*dto.ClaudeRequest, error) {
claudeTools := make([]any, 0, len(textRequest.Tools)) claudeTools := make([]any, 0, len(textRequest.Tools))
@ -411,7 +380,7 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
return &claudeRequest, nil return &claudeRequest, nil
} }
func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto.ChatCompletionsStreamResponse { func StreamResponseClaude2OpenAI(claudeResponse *dto.ClaudeResponse) *dto.ChatCompletionsStreamResponse {
var response dto.ChatCompletionsStreamResponse var response dto.ChatCompletionsStreamResponse
response.Object = "chat.completion.chunk" response.Object = "chat.completion.chunk"
response.Model = claudeResponse.Model response.Model = claudeResponse.Model
@ -425,74 +394,66 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse
} }
} }
var choice dto.ChatCompletionsStreamResponseChoice var choice dto.ChatCompletionsStreamResponseChoice
if reqMode == RequestModeCompletion { if claudeResponse.Type == "message_start" {
choice.Delta.SetContentString(claudeResponse.Completion) if claudeResponse.Message != nil {
finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason) response.Id = claudeResponse.Message.Id
if finishReason != "null" { response.Model = claudeResponse.Message.Model
choice.FinishReason = &finishReason
} }
} else { //claudeUsage = &claudeResponse.Message.Usage
if claudeResponse.Type == "message_start" { choice.Delta.SetContentString("")
if claudeResponse.Message != nil { choice.Delta.Role = "assistant"
response.Id = claudeResponse.Message.Id } else if claudeResponse.Type == "content_block_start" {
response.Model = claudeResponse.Message.Model if claudeResponse.ContentBlock != nil {
// 如果是文本块,尽可能发送首段文本(若存在)
if claudeResponse.ContentBlock.Type == "text" && claudeResponse.ContentBlock.Text != nil {
choice.Delta.SetContentString(*claudeResponse.ContentBlock.Text)
} }
//claudeUsage = &claudeResponse.Message.Usage if claudeResponse.ContentBlock.Type == "tool_use" {
choice.Delta.SetContentString("") tools = append(tools, dto.ToolCallResponse{
choice.Delta.Role = "assistant" Index: common.GetPointer(fcIdx),
} else if claudeResponse.Type == "content_block_start" { ID: claudeResponse.ContentBlock.Id,
if claudeResponse.ContentBlock != nil { Type: "function",
// 如果是文本块,尽可能发送首段文本(若存在) Function: dto.FunctionResponse{
if claudeResponse.ContentBlock.Type == "text" && claudeResponse.ContentBlock.Text != nil { Name: claudeResponse.ContentBlock.Name,
choice.Delta.SetContentString(*claudeResponse.ContentBlock.Text) Arguments: "",
} },
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":
choice.Delta.ReasoningContent = claudeResponse.Delta.Thinking
}
}
} else if claudeResponse.Type == "message_delta" {
if claudeResponse.Delta != nil && claudeResponse.Delta.StopReason != nil {
finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason)
if finishReason != "null" {
choice.FinishReason = &finishReason
}
}
//claudeUsage = &claudeResponse.Usage
} else if claudeResponse.Type == "message_stop" {
return nil
} else { } else {
return nil 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":
choice.Delta.ReasoningContent = claudeResponse.Delta.Thinking
}
}
} else if claudeResponse.Type == "message_delta" {
if claudeResponse.Delta != nil && claudeResponse.Delta.StopReason != nil {
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 { if len(tools) > 0 {
choice.Delta.Content = nil // compatible with other OpenAI derivative applications, like LobeOpenAICompatibleFactory ... choice.Delta.Content = nil // compatible with other OpenAI derivative applications, like LobeOpenAICompatibleFactory ...
@ -503,7 +464,7 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse
return &response return &response
} }
func ResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto.OpenAITextResponse { func ResponseClaude2OpenAI(claudeResponse *dto.ClaudeResponse) *dto.OpenAITextResponse {
choices := make([]dto.OpenAITextResponseChoice, 0) choices := make([]dto.OpenAITextResponseChoice, 0)
fullTextResponse := dto.OpenAITextResponse{ fullTextResponse := dto.OpenAITextResponse{
Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()), Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
@ -521,39 +482,26 @@ func ResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto
tools := make([]dto.ToolCallResponse, 0) tools := make([]dto.ToolCallResponse, 0)
thinkingContent := "" thinkingContent := ""
if reqMode == RequestModeCompletion { fullTextResponse.Id = claudeResponse.Id
choice := dto.OpenAITextResponseChoice{ for _, message := range claudeResponse.Content {
Index: 0, switch message.Type {
Message: dto.Message{ case "tool_use":
Role: "assistant", args, _ := json.Marshal(message.Input)
Content: strings.TrimPrefix(claudeResponse.Completion, " "), tools = append(tools, dto.ToolCallResponse{
Name: nil, ID: message.Id,
}, Type: "function", // compatible with other OpenAI derivative applications
FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason), Function: dto.FunctionResponse{
} Name: message.Name,
choices = append(choices, choice) Arguments: string(args),
} else { },
fullTextResponse.Id = claudeResponse.Id })
for _, message := range claudeResponse.Content { case "thinking":
switch message.Type { // 加密的不管, 只输出明文的推理过程
case "tool_use": if message.Thinking != nil {
args, _ := json.Marshal(message.Input) thinkingContent = *message.Thinking
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":
// 加密的不管, 只输出明文的推理过程
if message.Thinking != nil {
thinkingContent = *message.Thinking
}
case "text":
responseText = message.GetText()
} }
case "text":
responseText = message.GetText()
} }
} }
choice := dto.OpenAITextResponseChoice{ choice := dto.OpenAITextResponseChoice{
@ -586,71 +534,67 @@ type ClaudeResponseInfo struct {
Done bool Done bool
} }
func FormatClaudeResponseInfo(requestMode int, claudeResponse *dto.ClaudeResponse, oaiResponse *dto.ChatCompletionsStreamResponse, claudeInfo *ClaudeResponseInfo) bool { func FormatClaudeResponseInfo(claudeResponse *dto.ClaudeResponse, oaiResponse *dto.ChatCompletionsStreamResponse, claudeInfo *ClaudeResponseInfo) bool {
if claudeInfo == nil { if claudeInfo == nil {
return false return false
} }
if claudeInfo.Usage == nil { if claudeInfo.Usage == nil {
claudeInfo.Usage = &dto.Usage{} claudeInfo.Usage = &dto.Usage{}
} }
if requestMode == RequestModeCompletion { if claudeResponse.Type == "message_start" {
claudeInfo.ResponseText.WriteString(claudeResponse.Completion) if claudeResponse.Message != nil {
} else { claudeInfo.ResponseId = claudeResponse.Message.Id
if claudeResponse.Type == "message_start" { claudeInfo.Model = claudeResponse.Message.Model
if claudeResponse.Message != nil {
claudeInfo.ResponseId = claudeResponse.Message.Id
claudeInfo.Model = claudeResponse.Message.Model
}
// message_start, 获取usage
if claudeResponse.Message != nil && claudeResponse.Message.Usage != nil {
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.ClaudeCacheCreation5mTokens = claudeResponse.Message.Usage.GetCacheCreation5mTokens()
claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Message.Usage.GetCacheCreation1hTokens()
claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens
}
} else if claudeResponse.Type == "content_block_delta" {
if claudeResponse.Delta != nil {
if claudeResponse.Delta.Text != nil {
claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Text)
}
if claudeResponse.Delta.Thinking != nil {
claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Thinking)
}
}
} else if claudeResponse.Type == "message_delta" {
// 最终的usage获取
if claudeResponse.Usage != nil {
if claudeResponse.Usage.InputTokens > 0 {
// 不叠加,只取最新的
claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
}
if claudeResponse.Usage.CacheReadInputTokens > 0 {
claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
}
if claudeResponse.Usage.CacheCreationInputTokens > 0 {
claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
}
if cacheCreation5m := claudeResponse.Usage.GetCacheCreation5mTokens(); cacheCreation5m > 0 {
claudeInfo.Usage.ClaudeCacheCreation5mTokens = cacheCreation5m
}
if cacheCreation1h := claudeResponse.Usage.GetCacheCreation1hTokens(); cacheCreation1h > 0 {
claudeInfo.Usage.ClaudeCacheCreation1hTokens = cacheCreation1h
}
if claudeResponse.Usage.OutputTokens > 0 {
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
} }
// message_start, 获取usage
if claudeResponse.Message != nil && claudeResponse.Message.Usage != nil {
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.ClaudeCacheCreation5mTokens = claudeResponse.Message.Usage.GetCacheCreation5mTokens()
claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Message.Usage.GetCacheCreation1hTokens()
claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens
}
} else if claudeResponse.Type == "content_block_delta" {
if claudeResponse.Delta != nil {
if claudeResponse.Delta.Text != nil {
claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Text)
}
if claudeResponse.Delta.Thinking != nil {
claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Thinking)
}
}
} else if claudeResponse.Type == "message_delta" {
// 最终的usage获取
if claudeResponse.Usage != nil {
if claudeResponse.Usage.InputTokens > 0 {
// 不叠加,只取最新的
claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
}
if claudeResponse.Usage.CacheReadInputTokens > 0 {
claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
}
if claudeResponse.Usage.CacheCreationInputTokens > 0 {
claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
}
if cacheCreation5m := claudeResponse.Usage.GetCacheCreation5mTokens(); cacheCreation5m > 0 {
claudeInfo.Usage.ClaudeCacheCreation5mTokens = cacheCreation5m
}
if cacheCreation1h := claudeResponse.Usage.GetCacheCreation1hTokens(); cacheCreation1h > 0 {
claudeInfo.Usage.ClaudeCacheCreation1hTokens = cacheCreation1h
}
if claudeResponse.Usage.OutputTokens > 0 {
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 { if oaiResponse != nil {
oaiResponse.Id = claudeInfo.ResponseId oaiResponse.Id = claudeInfo.ResponseId
@ -660,7 +604,7 @@ func FormatClaudeResponseInfo(requestMode int, claudeResponse *dto.ClaudeRespons
return true return true
} }
func HandleStreamResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, data string, requestMode int) *types.NewAPIError { func HandleStreamResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, data string) *types.NewAPIError {
var claudeResponse dto.ClaudeResponse var claudeResponse dto.ClaudeResponse
err := common.UnmarshalJsonStr(data, &claudeResponse) err := common.UnmarshalJsonStr(data, &claudeResponse)
if err != nil { if err != nil {
@ -677,24 +621,19 @@ func HandleStreamResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
maybeMarkClaudeRefusal(c, *claudeResponse.Delta.StopReason) maybeMarkClaudeRefusal(c, *claudeResponse.Delta.StopReason)
} }
if info.RelayFormat == types.RelayFormatClaude { if info.RelayFormat == types.RelayFormatClaude {
FormatClaudeResponseInfo(requestMode, &claudeResponse, nil, claudeInfo) FormatClaudeResponseInfo(&claudeResponse, nil, claudeInfo)
if requestMode == RequestModeCompletion { if claudeResponse.Type == "message_start" {
} else { // message_start, 获取usage
if claudeResponse.Type == "message_start" { if claudeResponse.Message != nil {
// message_start, 获取usage info.UpstreamModelName = claudeResponse.Message.Model
if claudeResponse.Message != nil {
info.UpstreamModelName = claudeResponse.Message.Model
}
} else if claudeResponse.Type == "content_block_delta" {
} else if claudeResponse.Type == "message_delta" {
} }
} }
helper.ClaudeChunkData(c, claudeResponse, data) helper.ClaudeChunkData(c, claudeResponse, data)
} else if info.RelayFormat == types.RelayFormatOpenAI { } else if info.RelayFormat == types.RelayFormatOpenAI {
response := StreamResponseClaude2OpenAI(requestMode, &claudeResponse) response := StreamResponseClaude2OpenAI(&claudeResponse)
if !FormatClaudeResponseInfo(requestMode, &claudeResponse, response, claudeInfo) { if !FormatClaudeResponseInfo(&claudeResponse, response, claudeInfo) {
return nil return nil
} }
@ -706,20 +645,15 @@ func HandleStreamResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
return nil return nil
} }
func HandleStreamFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, requestMode int) { func HandleStreamFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo) {
if claudeInfo.Usage.PromptTokens == 0 {
if requestMode == RequestModeCompletion { //上游出错
claudeInfo.Usage = service.ResponseText2Usage(c, claudeInfo.ResponseText.String(), info.UpstreamModelName, info.GetEstimatePromptTokens()) }
} else { if claudeInfo.Usage.CompletionTokens == 0 || !claudeInfo.Done {
if claudeInfo.Usage.PromptTokens == 0 { if common.DebugEnabled {
//上游出错 common.SysLog("claude response usage is not complete, maybe upstream error")
}
if claudeInfo.Usage.CompletionTokens == 0 || !claudeInfo.Done {
if common.DebugEnabled {
common.SysLog("claude response usage is not complete, maybe upstream error")
}
claudeInfo.Usage = service.ResponseText2Usage(c, claudeInfo.ResponseText.String(), info.UpstreamModelName, claudeInfo.Usage.PromptTokens)
} }
claudeInfo.Usage = service.ResponseText2Usage(c, claudeInfo.ResponseText.String(), info.UpstreamModelName, claudeInfo.Usage.PromptTokens)
} }
if info.RelayFormat == types.RelayFormatClaude { if info.RelayFormat == types.RelayFormatClaude {
@ -736,7 +670,7 @@ func HandleStreamFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, clau
} }
} }
func ClaudeStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, requestMode int) (*dto.Usage, *types.NewAPIError) { func ClaudeStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.Usage, *types.NewAPIError) {
claudeInfo := &ClaudeResponseInfo{ claudeInfo := &ClaudeResponseInfo{
ResponseId: helper.GetResponseID(c), ResponseId: helper.GetResponseID(c),
Created: common.GetTimestamp(), Created: common.GetTimestamp(),
@ -746,7 +680,7 @@ func ClaudeStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.
} }
var err *types.NewAPIError var err *types.NewAPIError
helper.StreamScannerHandler(c, resp, info, func(data string) bool { helper.StreamScannerHandler(c, resp, info, func(data string) bool {
err = HandleStreamResponseData(c, info, claudeInfo, data, requestMode) err = HandleStreamResponseData(c, info, claudeInfo, data)
if err != nil { if err != nil {
return false return false
} }
@ -756,11 +690,11 @@ func ClaudeStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.
return nil, err return nil, err
} }
HandleStreamFinalResponse(c, info, claudeInfo, requestMode) HandleStreamFinalResponse(c, info, claudeInfo)
return claudeInfo.Usage, nil return claudeInfo.Usage, nil
} }
func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, httpResp *http.Response, data []byte, requestMode int) *types.NewAPIError { func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, httpResp *http.Response, data []byte) *types.NewAPIError {
var claudeResponse dto.ClaudeResponse var claudeResponse dto.ClaudeResponse
err := common.Unmarshal(data, &claudeResponse) err := common.Unmarshal(data, &claudeResponse)
if err != nil { if err != nil {
@ -770,26 +704,22 @@ func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
return types.WithClaudeError(*claudeError, http.StatusInternalServerError) return types.WithClaudeError(*claudeError, http.StatusInternalServerError)
} }
maybeMarkClaudeRefusal(c, claudeResponse.StopReason) maybeMarkClaudeRefusal(c, claudeResponse.StopReason)
if requestMode == RequestModeCompletion { if claudeInfo.Usage == nil {
claudeInfo.Usage = service.ResponseText2Usage(c, claudeResponse.Completion, info.UpstreamModelName, info.GetEstimatePromptTokens()) claudeInfo.Usage = &dto.Usage{}
} else { }
if claudeInfo.Usage == nil { if claudeResponse.Usage != nil {
claudeInfo.Usage = &dto.Usage{} claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
} claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens
if claudeResponse.Usage != nil { claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens
claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Usage.GetCacheCreation5mTokens()
claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Usage.GetCacheCreation1hTokens()
claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Usage.GetCacheCreation5mTokens()
claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Usage.GetCacheCreation1hTokens()
}
} }
var responseData []byte var responseData []byte
switch info.RelayFormat { switch info.RelayFormat {
case types.RelayFormatOpenAI: case types.RelayFormatOpenAI:
openaiResponse := ResponseClaude2OpenAI(requestMode, &claudeResponse) openaiResponse := ResponseClaude2OpenAI(&claudeResponse)
openaiResponse.Usage = *claudeInfo.Usage openaiResponse.Usage = *claudeInfo.Usage
responseData, err = json.Marshal(openaiResponse) responseData, err = json.Marshal(openaiResponse)
if err != nil { if err != nil {
@ -807,7 +737,7 @@ func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claud
return nil return nil
} }
func ClaudeHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, requestMode int) (*dto.Usage, *types.NewAPIError) { func ClaudeHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.Usage, *types.NewAPIError) {
defer service.CloseResponseBodyGracefully(resp) defer service.CloseResponseBodyGracefully(resp)
claudeInfo := &ClaudeResponseInfo{ claudeInfo := &ClaudeResponseInfo{
@ -824,7 +754,7 @@ func ClaudeHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayI
if common.DebugEnabled { if common.DebugEnabled {
println("responseBody: ", string(responseBody)) println("responseBody: ", string(responseBody))
} }
handleErr := HandleClaudeResponseData(c, info, claudeInfo, resp, responseBody, requestMode) handleErr := HandleClaudeResponseData(c, info, claudeInfo, resp, responseBody)
if handleErr != nil { if handleErr != nil {
return nil, handleErr return nil, handleErr
} }

View File

@ -96,9 +96,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
switch info.RelayFormat { switch info.RelayFormat {
case types.RelayFormatClaude: case types.RelayFormatClaude:
if info.IsStream { if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeStreamHandler(c, resp, info)
} else { } else {
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeHandler(c, resp, info)
} }
default: default:
adaptor := openai.Adaptor{} adaptor := openai.Adaptor{}

View File

@ -103,9 +103,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
switch info.RelayFormat { switch info.RelayFormat {
case types.RelayFormatClaude: case types.RelayFormatClaude:
if info.IsStream { if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeStreamHandler(c, resp, info)
} else { } else {
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeHandler(c, resp, info)
} }
default: default:
adaptor := openai.Adaptor{} adaptor := openai.Adaptor{}

View File

@ -367,7 +367,7 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
if info.IsStream { if info.IsStream {
switch a.RequestMode { switch a.RequestMode {
case RequestModeClaude: case RequestModeClaude:
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeStreamHandler(c, resp, info)
case RequestModeGemini: case RequestModeGemini:
if info.RelayMode == constant.RelayModeGemini { if info.RelayMode == constant.RelayModeGemini {
return gemini.GeminiTextGenerationStreamHandler(c, info, resp) return gemini.GeminiTextGenerationStreamHandler(c, info, resp)
@ -380,7 +380,7 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
} else { } else {
switch a.RequestMode { switch a.RequestMode {
case RequestModeClaude: case RequestModeClaude:
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeHandler(c, resp, info)
case RequestModeGemini: case RequestModeGemini:
if info.RelayMode == constant.RelayModeGemini { if info.RelayMode == constant.RelayModeGemini {
return gemini.GeminiTextGenerationHandler(c, info, resp) return gemini.GeminiTextGenerationHandler(c, info, resp)

View File

@ -348,9 +348,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
if info.RelayFormat == types.RelayFormatClaude { if info.RelayFormat == types.RelayFormatClaude {
if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok { if _, ok := channelconstant.ChannelSpecialBases[info.ChannelBaseUrl]; ok {
if info.IsStream { if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeStreamHandler(c, resp, info)
} }
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeHandler(c, resp, info)
} }
} }

View File

@ -110,9 +110,9 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
switch info.RelayFormat { switch info.RelayFormat {
case types.RelayFormatClaude: case types.RelayFormatClaude:
if info.IsStream { if info.IsStream {
return claude.ClaudeStreamHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeStreamHandler(c, resp, info)
} else { } else {
return claude.ClaudeHandler(c, resp, info, claude.RequestModeMessage) return claude.ClaudeHandler(c, resp, info)
} }
default: default:
if info.RelayMode == relayconstant.RelayModeImagesGenerations { if info.RelayMode == relayconstant.RelayModeImagesGenerations {

View File

@ -131,9 +131,6 @@ var defaultModelRatio = map[string]float64{
"text-search-ada-doc-001": 10, "text-search-ada-doc-001": 10,
"text-moderation-stable": 0.1, "text-moderation-stable": 0.1,
"text-moderation-latest": 0.1, "text-moderation-latest": 0.1,
"claude-instant-1": 0.4, // $0.8 / 1M tokens
"claude-2.0": 4, // $8 / 1M tokens
"claude-2.1": 4, // $8 / 1M tokens
"claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens "claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens
"claude-3-5-haiku-20241022": 0.5, // $1 / 1M tokens "claude-3-5-haiku-20241022": 0.5, // $1 / 1M tokens
"claude-haiku-4-5-20251001": 0.5, // $1 / 1M tokens "claude-haiku-4-5-20251001": 0.5, // $1 / 1M tokens
@ -589,8 +586,6 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) {
return 5, true return 5, true
} else if strings.Contains(name, "claude-sonnet-4") || strings.Contains(name, "claude-opus-4") || strings.Contains(name, "claude-haiku-4") { } else if strings.Contains(name, "claude-sonnet-4") || strings.Contains(name, "claude-opus-4") || strings.Contains(name, "claude-haiku-4") {
return 5, true return 5, true
} else if strings.Contains(name, "claude-instant-1") || strings.Contains(name, "claude-2") {
return 3, true
} }
if strings.HasPrefix(name, "gpt-3.5") { if strings.HasPrefix(name, "gpt-3.5") {

View File

@ -571,7 +571,6 @@ export const modelColorMap = {
'claude-3-opus-20240229': 'rgb(255,132,31)', // 'claude-3-opus-20240229': 'rgb(255,132,31)', //
'claude-3-sonnet-20240229': 'rgb(253,135,93)', // 'claude-3-sonnet-20240229': 'rgb(253,135,93)', //
'claude-3-haiku-20240307': 'rgb(255,175,146)', // 'claude-3-haiku-20240307': 'rgb(255,175,146)', //
'claude-2.1': 'rgb(255,209,190)', //
}; };
export function modelToColor(modelName) { export function modelToColor(modelName) {