Skip to content

Commit

Permalink
✨ feat: Support Claude3 (#86)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
moondie and MartialBE authored Mar 6, 2024
1 parent 07c18df commit e1fcfae
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 77 deletions.
11 changes: 6 additions & 5 deletions common/model-ratio.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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") {
Expand Down
5 changes: 3 additions & 2 deletions modelRatio.json
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
16 changes: 8 additions & 8 deletions providers/claude/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
}
}

Expand All @@ -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
Expand Down
113 changes: 69 additions & 44 deletions providers/claude/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
if claudeRequest.MaxTokensToSample == 0 {
claudeRequest.MaxTokensToSample = 1000000
}
prompt := ""
Model: request.Model,
Messages: nil,
System: "",
MaxTokens: request.MaxTokens,
StopSequences: nil,
Temperature: request.Temperature,
TopP: request.TopP,
Stream: request.Stream,
}
if claudeRequest.MaxTokens == 0 {
claudeRequest.MaxTokens = 4096
}
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,
Expand All @@ -125,78 +125,103 @@ 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

promptTokens := response.Usage.InputTokens

openaiResponse.Usage = response.Usage
openaiResponse.Usage.PromptTokens = promptTokens
openaiResponse.Usage.CompletionTokens = completionTokens
openaiResponse.Usage.TotalTokens = promptTokens + completionTokens

*p.Usage = *response.Usage
*p.Usage = *openaiResponse.Usage

return openaiResponse, nil
}

// 转换为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
}

// 去除前缀
*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)
}
61 changes: 45 additions & 16 deletions providers/claude/type.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package claude

import "one-api/types"

type ClaudeError struct {
Type string `json:"type"`
Message string `json:"message"`
Expand All @@ -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"`
}
4 changes: 2 additions & 2 deletions web/src/views/Channel/type/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},
Expand Down

0 comments on commit e1fcfae

Please sign in to comment.