From 280c63e1d42063f55f6abd462c5fdd3737c5ff40 Mon Sep 17 00:00:00 2001
From: "1808837298@qq.com" <1808837298@qq.com>
Date: Thu, 7 Mar 2024 22:41:04 +0800
Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=85=85?=
=?UTF-8?q?=E5=80=BC=E8=AE=B0=E5=BD=95=E6=8C=89=E9=92=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/pages/TopUp/index.js | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/web/src/pages/TopUp/index.js b/web/src/pages/TopUp/index.js
index 92ae57e..76c03e0 100644
--- a/web/src/pages/TopUp/index.js
+++ b/web/src/pages/TopUp/index.js
@@ -3,6 +3,8 @@ import {API, isMobile, showError, showInfo, showSuccess} from '../../helpers';
import {renderNumber, renderQuota} from '../../helpers/render';
import {Col, Layout, Row, Typography, Card, Button, Form, Divider, Space, Modal} from "@douyinfe/semi-ui";
import Title from "@douyinfe/semi-ui/lib/es/typography/title";
+import Text from '@douyinfe/semi-ui/lib/es/typography/text';
+import { Link } from 'react-router-dom';
const TopUp = () => {
const [redemptionCode, setRedemptionCode] = useState('');
@@ -290,6 +292,15 @@ const TopUp = () => {
+
+
+ {
+ window.location.href = '/topup/history'
+ }
+ }>充值记录
+
+
From c2965eb835126f28d7b7260964be9ef723f41eb1 Mon Sep 17 00:00:00 2001
From: CaIon <1808837298@qq.com>
Date: Fri, 8 Mar 2024 18:25:57 +0800
Subject: [PATCH 2/3] feat: support claude3 not stream
---
common/image.go | 7 +-
dto/text_request.go | 14 +--
dto/text_response.go | 6 ++
relay/channel/claude/adaptor.go | 15 ++--
relay/channel/claude/dto.go | 51 ++++++++---
relay/channel/claude/relay-claude.go | 128 ++++++++++++++++++++++-----
service/token_counter.go | 2 +-
7 files changed, 174 insertions(+), 49 deletions(-)
diff --git a/common/image.go b/common/image.go
index e743dde..4f2d012 100644
--- a/common/image.go
+++ b/common/image.go
@@ -12,7 +12,7 @@ import (
"strings"
)
-func DecodeBase64ImageData(base64String string) (image.Config, string, error) {
+func DecodeBase64ImageData(base64String string) (image.Config, string, string, error) {
// 去除base64数据的URL前缀(如果有)
if idx := strings.Index(base64String, ","); idx != -1 {
base64String = base64String[idx+1:]
@@ -22,13 +22,13 @@ func DecodeBase64ImageData(base64String string) (image.Config, string, error) {
decodedData, err := base64.StdEncoding.DecodeString(base64String)
if err != nil {
fmt.Println("Error: Failed to decode base64 string")
- return image.Config{}, "", err
+ return image.Config{}, "", "", err
}
// 创建一个bytes.Buffer用于存储解码后的数据
reader := bytes.NewReader(decodedData)
config, format, err := getImageConfig(reader)
- return config, format, err
+ return config, format, base64String, err
}
func IsImageUrl(url string) (bool, error) {
@@ -42,6 +42,7 @@ func IsImageUrl(url string) (bool, error) {
return true, nil
}
+// GetImageFromUrl 获取图片的类型和base64编码的数据
func GetImageFromUrl(url string) (mimeType string, data string, err error) {
isImage, err := IsImageUrl(url)
if !isImage {
diff --git a/dto/text_request.go b/dto/text_request.go
index 4565d66..627255a 100644
--- a/dto/text_request.go
+++ b/dto/text_request.go
@@ -82,6 +82,14 @@ func (m Message) StringContent() string {
return string(m.Content)
}
+func (m Message) IsStringContent() bool {
+ var stringContent string
+ if err := json.Unmarshal(m.Content, &stringContent); err == nil {
+ return true
+ }
+ return false
+}
+
func (m Message) ParseContent() []MediaMessage {
var contentList []MediaMessage
var stringContent string
@@ -130,9 +138,3 @@ func (m Message) ParseContent() []MediaMessage {
return nil
}
-
-type Usage struct {
- PromptTokens int `json:"prompt_tokens"`
- CompletionTokens int `json:"completion_tokens"`
- TotalTokens int `json:"total_tokens"`
-}
diff --git a/dto/text_response.go b/dto/text_response.go
index 93eb40d..81d0748 100644
--- a/dto/text_response.go
+++ b/dto/text_response.go
@@ -61,3 +61,9 @@ type CompletionsStreamResponse struct {
FinishReason string `json:"finish_reason"`
} `json:"choices"`
}
+
+type Usage struct {
+ PromptTokens int `json:"prompt_tokens"`
+ CompletionTokens int `json:"completion_tokens"`
+ TotalTokens int `json:"total_tokens"`
+}
diff --git a/relay/channel/claude/adaptor.go b/relay/channel/claude/adaptor.go
index 2ed1e2e..b7f4662 100644
--- a/relay/channel/claude/adaptor.go
+++ b/relay/channel/claude/adaptor.go
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"io"
"net/http"
+ "one-api/common"
"one-api/dto"
"one-api/relay/channel"
relaycommon "one-api/relay/common"
@@ -50,15 +51,15 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, info *re
}
func (a *Adaptor) ConvertRequest(c *gin.Context, relayMode int, request *dto.GeneralOpenAIRequest) (any, error) {
+ common.SysLog(fmt.Sprintf("Request mode: %d", a.RequestMode))
if request == nil {
return nil, errors.New("request is nil")
}
- //if a.RequestMode == RequestModeCompletion {
- // return requestOpenAI2ClaudeComplete(*request), nil
- //} else {
- // return requestOpenAI2ClaudeMessage(*request), nil
- //}
- return request, nil
+ if a.RequestMode == RequestModeCompletion {
+ return requestOpenAI2ClaudeComplete(*request), nil
+ } else {
+ return requestOpenAI2ClaudeMessage(*request)
+ }
}
func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) {
@@ -71,7 +72,7 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
err, responseText = claudeStreamHandler(c, resp)
usage = service.ResponseText2Usage(responseText, info.UpstreamModelName, info.PromptTokens)
} else {
- err, usage = claudeHandler(c, resp, info.PromptTokens, info.UpstreamModelName)
+ err, usage = claudeHandler(a.RequestMode, c, resp, info.PromptTokens, info.UpstreamModelName)
}
return
}
diff --git a/relay/channel/claude/dto.go b/relay/channel/claude/dto.go
index 2231e5f..83b9063 100644
--- a/relay/channel/claude/dto.go
+++ b/relay/channel/claude/dto.go
@@ -4,14 +4,34 @@ type ClaudeMetadata struct {
UserId string `json:"user_id"`
}
+type ClaudeMediaMessage struct {
+ Type string `json:"type"`
+ Text string `json:"text,omitempty"`
+ Source *ClaudeMessageSource `json:"source,omitempty"`
+}
+
+type ClaudeMessageSource struct {
+ Type string `json:"type"`
+ MediaType string `json:"media_type"`
+ Data string `json:"data"`
+}
+
+type ClaudeMessage struct {
+ Role string `json:"role"`
+ Content any `json:"content"`
+}
+
type ClaudeRequest struct {
- Model string `json:"model"`
- Prompt string `json:"prompt"`
- MaxTokensToSample uint `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"`
+ Prompt string `json:"prompt,omitempty"`
+ System string `json:"system,omitempty"`
+ Messages []ClaudeMessage `json:"messages,omitempty"`
+ MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"`
+ MaxTokens uint `json:"max_tokens,omitempty"`
+ 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"`
}
@@ -22,8 +42,17 @@ type ClaudeError struct {
}
type ClaudeResponse struct {
- Completion string `json:"completion"`
- StopReason string `json:"stop_reason"`
- Model string `json:"model"`
- Error ClaudeError `json:"error"`
+ Id string `json:"id"`
+ Type string `json:"type"`
+ Content []ClaudeMediaMessage `json:"content"`
+ Completion string `json:"completion"`
+ StopReason string `json:"stop_reason"`
+ Model string `json:"model"`
+ Error ClaudeError `json:"error"`
+ Usage ClaudeUsage `json:"usage"`
+}
+
+type ClaudeUsage struct {
+ InputTokens int `json:"input_tokens"`
+ OutputTokens int `json:"output_tokens"`
}
diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go
index 1a285d5..690908f 100644
--- a/relay/channel/claude/relay-claude.go
+++ b/relay/channel/claude/relay-claude.go
@@ -54,9 +54,68 @@ func requestOpenAI2ClaudeComplete(textRequest dto.GeneralOpenAIRequest) *ClaudeR
return &claudeRequest
}
-//func requestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) *dto.GeneralOpenAIRequest {
-//
-//}
+func requestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeRequest, error) {
+ claudeRequest := ClaudeRequest{
+ Model: textRequest.Model,
+ MaxTokens: textRequest.MaxTokens,
+ StopSequences: nil,
+ Temperature: textRequest.Temperature,
+ TopP: textRequest.TopP,
+ Stream: textRequest.Stream,
+ }
+ claudeMessages := make([]ClaudeMessage, 0)
+ for _, message := range textRequest.Messages {
+ if message.Role == "system" {
+ claudeRequest.System = message.StringContent()
+ } else {
+ claudeMessage := ClaudeMessage{
+ Role: message.Role,
+ }
+ if message.IsStringContent() {
+ claudeMessage.Content = message.StringContent()
+ } else {
+ claudeMediaMessages := make([]ClaudeMediaMessage, 0)
+ for _, mediaMessage := range message.ParseContent() {
+ claudeMediaMessage := ClaudeMediaMessage{
+ Type: mediaMessage.Type,
+ }
+ if mediaMessage.Type == "text" {
+ claudeMediaMessage.Text = mediaMessage.Text
+ } else {
+ imageUrl := mediaMessage.ImageUrl.(dto.MessageImageUrl)
+ claudeMediaMessage.Type = "image"
+ claudeMediaMessage.Source = &ClaudeMessageSource{
+ Type: "base64",
+ }
+ // 判断是否是url
+ if strings.HasPrefix(imageUrl.Url, "http") {
+ // 是url,获取图片的类型和base64编码的数据
+ mimeType, data, _ := common.GetImageFromUrl(imageUrl.Url)
+ claudeMediaMessage.Source.MediaType = mimeType
+ claudeMediaMessage.Source.Data = data
+ } else {
+ _, format, base64String, err := common.DecodeBase64ImageData(imageUrl.Url)
+ if err != nil {
+ return nil, err
+ }
+ claudeMediaMessage.Source.MediaType = "image/" + format
+ claudeMediaMessage.Source.Data = base64String
+ }
+ }
+ claudeMediaMessages = append(claudeMediaMessages, claudeMediaMessage)
+ }
+ claudeMessage.Content = claudeMediaMessages
+ }
+ claudeMessages = append(claudeMessages, claudeMessage)
+ }
+ }
+ claudeRequest.Prompt = ""
+ claudeRequest.Messages = claudeMessages
+ reqJson, _ := json.Marshal(claudeRequest)
+ common.SysLog(fmt.Sprintf("claude request: %s", reqJson))
+
+ return &claudeRequest, nil
+}
func streamResponseClaude2OpenAI(claudeResponse *ClaudeResponse) *dto.ChatCompletionsStreamResponse {
var choice dto.ChatCompletionsStreamResponseChoice
@@ -72,23 +131,42 @@ func streamResponseClaude2OpenAI(claudeResponse *ClaudeResponse) *dto.ChatComple
return &response
}
-func responseClaude2OpenAI(claudeResponse *ClaudeResponse) *dto.OpenAITextResponse {
- content, _ := json.Marshal(strings.TrimPrefix(claudeResponse.Completion, " "))
- choice := dto.OpenAITextResponseChoice{
- Index: 0,
- Message: dto.Message{
- Role: "assistant",
- Content: content,
- Name: nil,
- },
- FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
- }
+func responseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) *dto.OpenAITextResponse {
+ choices := make([]dto.OpenAITextResponseChoice, 0)
fullTextResponse := dto.OpenAITextResponse{
Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
Object: "chat.completion",
Created: common.GetTimestamp(),
- Choices: []dto.OpenAITextResponseChoice{choice},
}
+ if reqMode == RequestModeCompletion {
+ content, _ := json.Marshal(strings.TrimPrefix(claudeResponse.Completion, " "))
+ choice := dto.OpenAITextResponseChoice{
+ Index: 0,
+ Message: dto.Message{
+ Role: "assistant",
+ Content: content,
+ Name: nil,
+ },
+ FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
+ }
+ choices = append(choices, choice)
+ } else {
+ fullTextResponse.Id = claudeResponse.Id
+ for i, message := range claudeResponse.Content {
+ content, _ := json.Marshal(message.Text)
+ choice := dto.OpenAITextResponseChoice{
+ Index: i,
+ Message: dto.Message{
+ Role: "assistant",
+ Content: content,
+ },
+ FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
+ }
+ choices = append(choices, choice)
+ }
+ }
+
+ fullTextResponse.Choices = choices
return &fullTextResponse
}
@@ -157,7 +235,7 @@ func claudeStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorW
return nil, responseText
}
-func claudeHandler(c *gin.Context, resp *http.Response, promptTokens int, model string) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
+func claudeHandler(requestMode int, c *gin.Context, resp *http.Response, promptTokens int, model string) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
@@ -167,10 +245,13 @@ func claudeHandler(c *gin.Context, resp *http.Response, promptTokens int, model
return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
}
var claudeResponse ClaudeResponse
+ common.SysLog(fmt.Sprintf("claude response: %s", responseBody))
err = json.Unmarshal(responseBody, &claudeResponse)
if err != nil {
return service.OpenAIErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
}
+ respJson, _ := json.Marshal(claudeResponse)
+ common.SysLog(fmt.Sprintf("claude response json: %s", respJson))
if claudeResponse.Error.Type != "" {
return &dto.OpenAIErrorWithStatusCode{
Error: dto.OpenAIError{
@@ -182,12 +263,17 @@ func claudeHandler(c *gin.Context, resp *http.Response, promptTokens int, model
StatusCode: resp.StatusCode,
}, nil
}
- fullTextResponse := responseClaude2OpenAI(&claudeResponse)
+ fullTextResponse := responseClaude2OpenAI(requestMode, &claudeResponse)
completionTokens := service.CountTokenText(claudeResponse.Completion, model)
- usage := dto.Usage{
- PromptTokens: promptTokens,
- CompletionTokens: completionTokens,
- TotalTokens: promptTokens + completionTokens,
+ usage := dto.Usage{}
+ if requestMode == RequestModeCompletion {
+ usage.PromptTokens = promptTokens
+ usage.CompletionTokens = completionTokens
+ usage.TotalTokens = promptTokens + completionTokens
+ } else {
+ usage.PromptTokens = claudeResponse.Usage.InputTokens
+ usage.CompletionTokens = claudeResponse.Usage.OutputTokens
+ usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens
}
fullTextResponse.Usage = usage
jsonResponse, err := json.Marshal(fullTextResponse)
diff --git a/service/token_counter.go b/service/token_counter.go
index 2f2e126..a8e82ed 100644
--- a/service/token_counter.go
+++ b/service/token_counter.go
@@ -74,7 +74,7 @@ func getImageToken(imageUrl *dto.MessageImageUrl) (int, error) {
config, format, err = common.DecodeUrlImageData(imageUrl.Url)
} else {
common.SysLog(fmt.Sprintf("decoding image"))
- config, format, err = common.DecodeBase64ImageData(imageUrl.Url)
+ config, format, _, err = common.DecodeBase64ImageData(imageUrl.Url)
}
if err != nil {
return 0, err
From 4a0af1ea3c7396b16ec4fe45941a59dd53d88336 Mon Sep 17 00:00:00 2001
From: CaIon <1808837298@qq.com>
Date: Fri, 8 Mar 2024 19:43:33 +0800
Subject: [PATCH 3/3] feat: support Claude 3
---
relay/channel/claude/adaptor.go | 5 +-
relay/channel/claude/constants.go | 2 +-
relay/channel/claude/dto.go | 16 ++++-
relay/channel/claude/relay-claude.go | 89 ++++++++++++++++++++--------
web/src/pages/Channel/EditChannel.js | 2 +-
5 files changed, 81 insertions(+), 33 deletions(-)
diff --git a/relay/channel/claude/adaptor.go b/relay/channel/claude/adaptor.go
index b7f4662..bf93acc 100644
--- a/relay/channel/claude/adaptor.go
+++ b/relay/channel/claude/adaptor.go
@@ -10,7 +10,6 @@ import (
"one-api/dto"
"one-api/relay/channel"
relaycommon "one-api/relay/common"
- "one-api/service"
"strings"
)
@@ -68,9 +67,7 @@ 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 *dto.Usage, err *dto.OpenAIErrorWithStatusCode) {
if info.IsStream {
- var responseText string
- err, responseText = claudeStreamHandler(c, resp)
- usage = service.ResponseText2Usage(responseText, info.UpstreamModelName, info.PromptTokens)
+ err, usage = claudeStreamHandler(a.RequestMode, info.UpstreamModelName, info.PromptTokens, c, resp)
} else {
err, usage = claudeHandler(a.RequestMode, c, resp, info.PromptTokens, info.UpstreamModelName)
}
diff --git a/relay/channel/claude/constants.go b/relay/channel/claude/constants.go
index 1015fb8..52af49f 100644
--- a/relay/channel/claude/constants.go
+++ b/relay/channel/claude/constants.go
@@ -1,7 +1,7 @@
package claude
var ModelList = []string{
- "claude-instant-1", "claude-2", "claude-2.0", "claude-2.1",
+ "claude-instant-1", "claude-2", "claude-2.0", "claude-2.1", "claude-3-sonnet-20240229", "claude-3-opus-20240229",
}
var ChannelName = "claude"
diff --git a/relay/channel/claude/dto.go b/relay/channel/claude/dto.go
index 83b9063..50513d8 100644
--- a/relay/channel/claude/dto.go
+++ b/relay/channel/claude/dto.go
@@ -5,9 +5,11 @@ type ClaudeMetadata struct {
}
type ClaudeMediaMessage struct {
- Type string `json:"type"`
- Text string `json:"text,omitempty"`
- Source *ClaudeMessageSource `json:"source,omitempty"`
+ Type string `json:"type"`
+ Text string `json:"text,omitempty"`
+ Source *ClaudeMessageSource `json:"source,omitempty"`
+ Usage *ClaudeUsage `json:"usage,omitempty"`
+ StopReason *string `json:"stop_reason,omitempty"`
}
type ClaudeMessageSource struct {
@@ -50,8 +52,16 @@ type ClaudeResponse struct {
Model string `json:"model"`
Error ClaudeError `json:"error"`
Usage ClaudeUsage `json:"usage"`
+ Index int `json:"index"` // stream only
+ Delta *ClaudeMediaMessage `json:"delta"` // stream only
+ Message *ClaudeResponse `json:"message"` // stream only: message_start
}
+//type ClaudeResponseChoice struct {
+// Index int `json:"index"`
+// Type string `json:"type"`
+//}
+
type ClaudeUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
diff --git a/relay/channel/claude/relay-claude.go b/relay/channel/claude/relay-claude.go
index 690908f..f43dfca 100644
--- a/relay/channel/claude/relay-claude.go
+++ b/relay/channel/claude/relay-claude.go
@@ -17,6 +17,8 @@ func stopReasonClaude2OpenAI(reason string) string {
switch reason {
case "stop_sequence":
return "stop"
+ case "end_turn":
+ return "stop"
case "max_tokens":
return "length"
default:
@@ -111,24 +113,41 @@ func requestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR
}
claudeRequest.Prompt = ""
claudeRequest.Messages = claudeMessages
- reqJson, _ := json.Marshal(claudeRequest)
- common.SysLog(fmt.Sprintf("claude request: %s", reqJson))
return &claudeRequest, nil
}
-func streamResponseClaude2OpenAI(claudeResponse *ClaudeResponse) *dto.ChatCompletionsStreamResponse {
- var choice dto.ChatCompletionsStreamResponseChoice
- choice.Delta.Content = claudeResponse.Completion
- finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason)
- if finishReason != "null" {
- choice.FinishReason = &finishReason
- }
+func streamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*dto.ChatCompletionsStreamResponse, *ClaudeUsage) {
var response dto.ChatCompletionsStreamResponse
+ var claudeUsage *ClaudeUsage
response.Object = "chat.completion.chunk"
response.Model = claudeResponse.Model
- response.Choices = []dto.ChatCompletionsStreamResponseChoice{choice}
- return &response
+ response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0)
+ var choice dto.ChatCompletionsStreamResponseChoice
+ if reqMode == RequestModeCompletion {
+ choice.Delta.Content = 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
+ } else if claudeResponse.Type == "content_block_delta" {
+ choice.Index = claudeResponse.Index
+ choice.Delta.Content = claudeResponse.Delta.Text
+ } else if claudeResponse.Type == "message_delta" {
+ finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason)
+ if finishReason != "null" {
+ choice.FinishReason = &finishReason
+ }
+ claudeUsage = &claudeResponse.Usage
+ }
+ }
+ response.Choices = append(response.Choices, choice)
+ return &response, claudeUsage
}
func responseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) *dto.OpenAITextResponse {
@@ -170,17 +189,18 @@ func responseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) *dto.Ope
return &fullTextResponse
}
-func claudeStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, string) {
- responseText := ""
+func claudeStreamHandler(requestMode int, modelName string, promptTokens int, c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
responseId := fmt.Sprintf("chatcmpl-%s", common.GetUUID())
+ var usage dto.Usage
+ responseText := ""
createdTime := common.GetTimestamp()
scanner := bufio.NewScanner(resp.Body)
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
- if i := strings.Index(string(data), "\r\n\r\n"); i >= 0 {
- return i + 4, data[0:i], nil
+ if i := strings.Index(string(data), "\n"); i >= 0 {
+ return i + 1, data[0:i], nil
}
if atEOF {
return len(data), data, nil
@@ -192,10 +212,10 @@ func claudeStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorW
go func() {
for scanner.Scan() {
data := scanner.Text()
- if !strings.HasPrefix(data, "event: completion") {
+ if !strings.HasPrefix(data, "data: ") {
continue
}
- data = strings.TrimPrefix(data, "event: completion\r\ndata: ")
+ data = strings.TrimPrefix(data, "data: ")
dataChan <- data
}
stopChan <- true
@@ -212,10 +232,31 @@ func claudeStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorW
common.SysError("error unmarshalling stream response: " + err.Error())
return true
}
- responseText += claudeResponse.Completion
- response := streamResponseClaude2OpenAI(&claudeResponse)
+
+ response, claudeUsage := streamResponseClaude2OpenAI(requestMode, &claudeResponse)
+ if requestMode == RequestModeCompletion {
+ responseText += claudeResponse.Completion
+ responseId = response.Id
+ } else {
+ if claudeResponse.Type == "message_start" {
+ // message_start, 获取usage
+ responseId = claudeResponse.Message.Id
+ modelName = claudeResponse.Message.Model
+ usage.PromptTokens = claudeUsage.InputTokens
+ } else if claudeResponse.Type == "content_block_delta" {
+ responseText += claudeResponse.Delta.Text
+ } else if claudeResponse.Type == "message_delta" {
+ usage.CompletionTokens = claudeUsage.OutputTokens
+ usage.TotalTokens = claudeUsage.InputTokens + claudeUsage.OutputTokens
+ } else {
+ return true
+ }
+ }
+ //response.Id = responseId
response.Id = responseId
response.Created = createdTime
+ response.Model = modelName
+
jsonStr, err := json.Marshal(response)
if err != nil {
common.SysError("error marshalling stream response: " + err.Error())
@@ -230,9 +271,12 @@ func claudeStreamHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorW
})
err := resp.Body.Close()
if err != nil {
- return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), ""
+ return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
}
- return nil, responseText
+ if requestMode == RequestModeCompletion {
+ usage = *service.ResponseText2Usage(responseText, modelName, promptTokens)
+ }
+ return nil, &usage
}
func claudeHandler(requestMode int, c *gin.Context, resp *http.Response, promptTokens int, model string) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
@@ -245,13 +289,10 @@ func claudeHandler(requestMode int, c *gin.Context, resp *http.Response, promptT
return service.OpenAIErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), nil
}
var claudeResponse ClaudeResponse
- common.SysLog(fmt.Sprintf("claude response: %s", responseBody))
err = json.Unmarshal(responseBody, &claudeResponse)
if err != nil {
return service.OpenAIErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError), nil
}
- respJson, _ := json.Marshal(claudeResponse)
- common.SysLog(fmt.Sprintf("claude response json: %s", respJson))
if claudeResponse.Error.Type != "" {
return &dto.OpenAIErrorWithStatusCode{
Error: dto.OpenAIError{
diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js
index 4c2df66..c792495 100644
--- a/web/src/pages/Channel/EditChannel.js
+++ b/web/src/pages/Channel/EditChannel.js
@@ -63,7 +63,7 @@ const EditChannel = (props) => {
let localModels = [];
switch (value) {
case 14:
- localModels = ['claude-instant-1', 'claude-2'];
+ localModels = ["claude-instant-1", "claude-2", "claude-2.0", "claude-2.1", "claude-3-sonnet-20240229", "claude-3-opus-20240229"];
break;
case 11:
localModels = ['PaLM-2'];