✨ feat: Support Claude3 (#86)
* Claude改用messages API,支持Claude3
* 删除新API不支持的模型
* 忘了改请求地址
* 🐛 fix: fix the problem of return format and completion token not being obtained
---------
Co-authored-by: MartialBE <me@xiao5.info>
This commit is contained in:
parent
07c18dfb91
commit
e1fcfae928
@ -87,11 +87,12 @@ func init() {
|
||||
"dall-e-3": {[]float64{20, 20}, ChannelTypeOpenAI},
|
||||
|
||||
// $0.80/million tokens $2.40/million tokens
|
||||
"claude-instant-1": {[]float64{0.4, 1.2}, ChannelTypeAnthropic},
|
||||
"claude-instant-1.2": {[]float64{0.4, 1.2}, ChannelTypeAnthropic},
|
||||
// $8.00/million tokens $24.00/million tokens
|
||||
"claude-2": {[]float64{4, 12}, ChannelTypeAnthropic},
|
||||
"claude-2.0": {[]float64{4, 12}, ChannelTypeAnthropic},
|
||||
"claude-2.1": {[]float64{4, 12}, ChannelTypeAnthropic},
|
||||
"claude-2.0": {[]float64{4, 12}, ChannelTypeAnthropic},
|
||||
"claude-2.1": {[]float64{4, 12}, ChannelTypeAnthropic},
|
||||
"claude-3-opus-20240229": {[]float64{7.5, 22.5}, ChannelTypeAnthropic},
|
||||
"claude-3-sonnet-20240229": {[]float64{1.3, 3.9}, ChannelTypeAnthropic},
|
||||
|
||||
// ¥0.012 / 1k tokens ¥0.012 / 1k tokens
|
||||
"ERNIE-Bot": {[]float64{0.8572, 0.8572}, ChannelTypeBaidu},
|
||||
@ -291,7 +292,7 @@ func GetCompletionRatio(name string) float64 {
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if strings.HasPrefix(name, "claude-instant-1") {
|
||||
if strings.HasPrefix(name, "claude-instant-1.2") {
|
||||
return 3.38
|
||||
}
|
||||
if strings.HasPrefix(name, "claude-2") {
|
||||
|
@ -43,10 +43,11 @@
|
||||
"text-moderation-latest": [0.1, 0.1],
|
||||
"dall-e-2": [8, 8],
|
||||
"dall-e-3": [20, 20],
|
||||
"claude-instant-1": [0.4, 1.2],
|
||||
"claude-2": [4, 12],
|
||||
"claude-instant-1.2": [0.4, 1.2],
|
||||
"claude-2.0": [4, 12],
|
||||
"claude-2.1": [4, 12],
|
||||
"claude-3-opus-20240229": [7.5, 22.5],
|
||||
"claude-3-sonnet-20240229": [1.3, 3.9],
|
||||
"ERNIE-Bot": [0.8572, 0.8572],
|
||||
"ERNIE-Bot-8k": [1.7143, 3.4286],
|
||||
"ERNIE-Bot-turbo": [0.5715, 0.5715],
|
||||
|
@ -29,13 +29,13 @@ type ClaudeProvider struct {
|
||||
func getConfig() base.ProviderConfig {
|
||||
return base.ProviderConfig{
|
||||
BaseURL: "https://api.anthropic.com",
|
||||
ChatCompletions: "/v1/complete",
|
||||
ChatCompletions: "/v1/messages",
|
||||
}
|
||||
}
|
||||
|
||||
// 请求错误处理
|
||||
func requestErrorHandle(resp *http.Response) *types.OpenAIError {
|
||||
claudeError := &ClaudeResponseError{}
|
||||
claudeError := &ClaudeError{}
|
||||
err := json.NewDecoder(resp.Body).Decode(claudeError)
|
||||
if err != nil {
|
||||
return nil
|
||||
@ -45,14 +45,14 @@ func requestErrorHandle(resp *http.Response) *types.OpenAIError {
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
func errorHandle(claudeError *ClaudeResponseError) *types.OpenAIError {
|
||||
if claudeError.Error.Type == "" {
|
||||
func errorHandle(claudeError *ClaudeError) *types.OpenAIError {
|
||||
if claudeError.Type == "" {
|
||||
return nil
|
||||
}
|
||||
return &types.OpenAIError{
|
||||
Message: claudeError.Error.Message,
|
||||
Type: claudeError.Error.Type,
|
||||
Code: claudeError.Error.Type,
|
||||
Message: claudeError.Message,
|
||||
Type: claudeError.Type,
|
||||
Code: claudeError.Type,
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ func (p *ClaudeProvider) GetRequestHeaders() (headers map[string]string) {
|
||||
|
||||
func stopReasonClaude2OpenAI(reason string) string {
|
||||
switch reason {
|
||||
case "stop_sequence":
|
||||
case "end_turn":
|
||||
return types.FinishReasonStop
|
||||
case "max_tokens":
|
||||
return types.FinishReasonLength
|
||||
|
@ -83,36 +83,36 @@ func (p *ClaudeProvider) getChatRequest(request *types.ChatCompletionRequest) (*
|
||||
|
||||
func convertFromChatOpenai(request *types.ChatCompletionRequest) *ClaudeRequest {
|
||||
claudeRequest := ClaudeRequest{
|
||||
Model: request.Model,
|
||||
Prompt: "",
|
||||
MaxTokensToSample: request.MaxTokens,
|
||||
StopSequences: nil,
|
||||
Temperature: request.Temperature,
|
||||
TopP: request.TopP,
|
||||
Stream: request.Stream,
|
||||
Model: request.Model,
|
||||
Messages: nil,
|
||||
System: "",
|
||||
MaxTokens: request.MaxTokens,
|
||||
StopSequences: nil,
|
||||
Temperature: request.Temperature,
|
||||
TopP: request.TopP,
|
||||
Stream: request.Stream,
|
||||
}
|
||||
if claudeRequest.MaxTokensToSample == 0 {
|
||||
claudeRequest.MaxTokensToSample = 1000000
|
||||
if claudeRequest.MaxTokens == 0 {
|
||||
claudeRequest.MaxTokens = 4096
|
||||
}
|
||||
prompt := ""
|
||||
var messages []Message
|
||||
for _, message := range request.Messages {
|
||||
if message.Role == "user" {
|
||||
prompt += fmt.Sprintf("\n\nHuman: %s", message.Content)
|
||||
} else if message.Role == "assistant" {
|
||||
prompt += fmt.Sprintf("\n\nAssistant: %s", message.Content)
|
||||
} else if message.Role == "system" {
|
||||
if prompt == "" {
|
||||
prompt = message.StringContent()
|
||||
}
|
||||
if message.Role != "system" {
|
||||
messages = append(messages, Message{
|
||||
Role: message.Role,
|
||||
Content: message.Content.(string),
|
||||
})
|
||||
claudeRequest.Messages = messages
|
||||
} else {
|
||||
claudeRequest.System = message.Content.(string)
|
||||
}
|
||||
}
|
||||
prompt += "\n\nAssistant:"
|
||||
claudeRequest.Prompt = prompt
|
||||
|
||||
return &claudeRequest
|
||||
}
|
||||
|
||||
func (p *ClaudeProvider) convertToChatOpenai(response *ClaudeResponse, request *types.ChatCompletionRequest) (openaiResponse *types.ChatCompletionResponse, errWithCode *types.OpenAIErrorWithStatusCode) {
|
||||
error := errorHandle(&response.ClaudeResponseError)
|
||||
error := errorHandle(&response.Error)
|
||||
if error != nil {
|
||||
errWithCode = &types.OpenAIErrorWithStatusCode{
|
||||
OpenAIError: *error,
|
||||
@ -125,26 +125,33 @@ func (p *ClaudeProvider) convertToChatOpenai(response *ClaudeResponse, request *
|
||||
Index: 0,
|
||||
Message: types.ChatCompletionMessage{
|
||||
Role: "assistant",
|
||||
Content: strings.TrimPrefix(response.Completion, " "),
|
||||
Content: strings.TrimPrefix(response.Content[0].Text, " "),
|
||||
Name: nil,
|
||||
},
|
||||
FinishReason: stopReasonClaude2OpenAI(response.StopReason),
|
||||
}
|
||||
openaiResponse = &types.ChatCompletionResponse{
|
||||
ID: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
|
||||
ID: response.Id,
|
||||
Object: "chat.completion",
|
||||
Created: common.GetTimestamp(),
|
||||
Choices: []types.ChatCompletionChoice{choice},
|
||||
Model: response.Model,
|
||||
Usage: &types.Usage{
|
||||
CompletionTokens: 0,
|
||||
PromptTokens: 0,
|
||||
TotalTokens: 0,
|
||||
},
|
||||
}
|
||||
|
||||
completionTokens := common.CountTokenText(response.Completion, response.Model)
|
||||
response.Usage.CompletionTokens = completionTokens
|
||||
response.Usage.TotalTokens = response.Usage.PromptTokens + completionTokens
|
||||
completionTokens := response.Usage.OutputTokens
|
||||
|
||||
openaiResponse.Usage = response.Usage
|
||||
promptTokens := response.Usage.InputTokens
|
||||
|
||||
*p.Usage = *response.Usage
|
||||
openaiResponse.Usage.PromptTokens = promptTokens
|
||||
openaiResponse.Usage.CompletionTokens = completionTokens
|
||||
openaiResponse.Usage.TotalTokens = promptTokens + completionTokens
|
||||
|
||||
*p.Usage = *openaiResponse.Usage
|
||||
|
||||
return openaiResponse, nil
|
||||
}
|
||||
@ -152,7 +159,7 @@ func (p *ClaudeProvider) convertToChatOpenai(response *ClaudeResponse, request *
|
||||
// 转换为OpenAI聊天流式请求体
|
||||
func (h *claudeStreamHandler) handlerStream(rawLine *[]byte, dataChan chan string, errChan chan error) {
|
||||
// 如果rawLine 前缀不为data:,则直接返回
|
||||
if !strings.HasPrefix(string(*rawLine), `data: {"type": "completion"`) {
|
||||
if !strings.HasPrefix(string(*rawLine), `data: {"type"`) {
|
||||
*rawLine = nil
|
||||
return
|
||||
}
|
||||
@ -160,43 +167,61 @@ func (h *claudeStreamHandler) handlerStream(rawLine *[]byte, dataChan chan strin
|
||||
// 去除前缀
|
||||
*rawLine = (*rawLine)[6:]
|
||||
|
||||
var claudeResponse *ClaudeResponse
|
||||
err := json.Unmarshal(*rawLine, claudeResponse)
|
||||
var claudeResponse ClaudeStreamResponse
|
||||
err := json.Unmarshal(*rawLine, &claudeResponse)
|
||||
if err != nil {
|
||||
errChan <- common.ErrorToOpenAIError(err)
|
||||
return
|
||||
}
|
||||
|
||||
error := errorHandle(&claudeResponse.ClaudeResponseError)
|
||||
error := errorHandle(&claudeResponse.Error)
|
||||
if error != nil {
|
||||
errChan <- error
|
||||
return
|
||||
}
|
||||
|
||||
if claudeResponse.StopReason == "stop_sequence" {
|
||||
switch claudeResponse.Type {
|
||||
case "message_start":
|
||||
h.Usage.PromptTokens = claudeResponse.Message.InputTokens
|
||||
|
||||
case "message_delta":
|
||||
h.convertToOpenaiStream(&claudeResponse, dataChan, errChan)
|
||||
h.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens
|
||||
h.Usage.TotalTokens = h.Usage.PromptTokens + h.Usage.CompletionTokens
|
||||
|
||||
case "content_block_delta":
|
||||
h.convertToOpenaiStream(&claudeResponse, dataChan, errChan)
|
||||
|
||||
case "message_stop":
|
||||
errChan <- io.EOF
|
||||
*rawLine = requester.StreamClosed
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
h.convertToOpenaiStream(claudeResponse, dataChan, errChan)
|
||||
}
|
||||
|
||||
func (h *claudeStreamHandler) convertToOpenaiStream(claudeResponse *ClaudeResponse, dataChan chan string, errChan chan error) {
|
||||
var choice types.ChatCompletionStreamChoice
|
||||
choice.Delta.Content = claudeResponse.Completion
|
||||
finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason)
|
||||
if finishReason != "null" {
|
||||
func (h *claudeStreamHandler) convertToOpenaiStream(claudeResponse *ClaudeStreamResponse, dataChan chan string, errChan chan error) {
|
||||
choice := types.ChatCompletionStreamChoice{
|
||||
Index: claudeResponse.Index,
|
||||
}
|
||||
|
||||
if claudeResponse.Delta.Text != "" {
|
||||
choice.Delta.Content = claudeResponse.Delta.Text
|
||||
}
|
||||
|
||||
finishReason := stopReasonClaude2OpenAI(claudeResponse.Delta.StopReason)
|
||||
if finishReason != "" {
|
||||
choice.FinishReason = &finishReason
|
||||
}
|
||||
chatCompletion := types.ChatCompletionStreamResponse{
|
||||
ID: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
|
||||
Object: "chat.completion.chunk",
|
||||
Created: common.GetTimestamp(),
|
||||
Model: h.Request.Model,
|
||||
Choices: []types.ChatCompletionStreamChoice{choice},
|
||||
}
|
||||
|
||||
responseBody, _ := json.Marshal(chatCompletion)
|
||||
dataChan <- string(responseBody)
|
||||
|
||||
h.Usage.PromptTokens += common.CountTokenText(claudeResponse.Completion, h.Request.Model)
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package claude
|
||||
|
||||
import "one-api/types"
|
||||
|
||||
type ClaudeError struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
@ -11,25 +9,56 @@ type ClaudeMetadata struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
type ResContent struct {
|
||||
Text string `json:"text"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type ClaudeRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
MaxTokensToSample int `json:"max_tokens_to_sample"`
|
||||
StopSequences []string `json:"stop_sequences,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
TopK int `json:"top_k,omitempty"`
|
||||
Model string `json:"model"`
|
||||
System string `json:"system,omitempty"`
|
||||
Messages []Message `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
StopSequences []string `json:"stop_sequences,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
TopK int `json:"top_k,omitempty"`
|
||||
//ClaudeMetadata `json:"metadata,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
}
|
||||
|
||||
type ClaudeResponseError struct {
|
||||
Error ClaudeError `json:"error,omitempty"`
|
||||
type Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens,omitempty"`
|
||||
}
|
||||
type ClaudeResponse struct {
|
||||
Completion string `json:"completion"`
|
||||
StopReason string `json:"stop_reason"`
|
||||
Model string `json:"model"`
|
||||
Usage *types.Usage `json:"usage,omitempty"`
|
||||
ClaudeResponseError
|
||||
Content []ResContent `json:"content"`
|
||||
Id string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
StopReason string `json:"stop_reason"`
|
||||
StopSequence string `json:"stop_sequence,omitempty"`
|
||||
Model string `json:"model"`
|
||||
Usage `json:"usage,omitempty"`
|
||||
Error ClaudeError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type Delta struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
StopReason string `json:"stop_reason,omitempty"`
|
||||
StopSequence string `json:"stop_sequence,omitempty"`
|
||||
}
|
||||
|
||||
type ClaudeStreamResponse struct {
|
||||
Type string `json:"type"`
|
||||
Message ClaudeResponse `json:"message,omitempty"`
|
||||
Index int `json:"index,omitempty"`
|
||||
Delta Delta `json:"delta,omitempty"`
|
||||
Usage Usage `json:"usage,omitempty"`
|
||||
Error ClaudeError `json:"error,omitempty"`
|
||||
}
|
||||
|
@ -59,8 +59,8 @@ const typeConfig = {
|
||||
},
|
||||
14: {
|
||||
input: {
|
||||
models: ['claude-instant-1', 'claude-2', 'claude-2.0', 'claude-2.1'],
|
||||
test_model: 'claude-2'
|
||||
models: ['claude-instant-1.2', 'claude-2.0', 'claude-2.1','claude-3-opus-20240229','claude-3-sonnet-20240229'],
|
||||
test_model: 'claude-3-sonnet-20240229'
|
||||
},
|
||||
modelGroup: 'Anthropic'
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user