mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-11-20 23:16:49 +08:00
Compare commits
3 Commits
290931b506
...
bfadc4e2e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfadc4e2e0 | ||
|
|
a587ac145a | ||
|
|
7b9631408f |
@@ -107,10 +107,12 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
|
|||||||
claudeMessage := Message{
|
claudeMessage := Message{
|
||||||
Role: message.Role,
|
Role: message.Role,
|
||||||
}
|
}
|
||||||
var content Content
|
var contents []Content
|
||||||
if message.IsStringContent() {
|
if message.IsStringContent() {
|
||||||
content.Type = "text"
|
content := Content{
|
||||||
content.Text = message.StringContent()
|
Type: "text",
|
||||||
|
Text: message.StringContent(),
|
||||||
|
}
|
||||||
if message.Role == "tool" {
|
if message.Role == "tool" {
|
||||||
claudeMessage.Role = "user"
|
claudeMessage.Role = "user"
|
||||||
content.Type = "tool_result"
|
content.Type = "tool_result"
|
||||||
@@ -118,21 +120,22 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
|
|||||||
content.Text = ""
|
content.Text = ""
|
||||||
content.ToolUseId = message.ToolCallId
|
content.ToolUseId = message.ToolCallId
|
||||||
}
|
}
|
||||||
claudeMessage.Content = append(claudeMessage.Content, content)
|
contents = append(contents, content)
|
||||||
for i := range message.ToolCalls {
|
for i := range message.ToolCalls {
|
||||||
inputParam := make(map[string]any)
|
inputParam := make(map[string]any)
|
||||||
_ = json.Unmarshal([]byte(message.ToolCalls[i].Function.Arguments.(string)), &inputParam)
|
_ = json.Unmarshal([]byte(message.ToolCalls[i].Function.Arguments.(string)), &inputParam)
|
||||||
claudeMessage.Content = append(claudeMessage.Content, Content{
|
contents = append(contents, Content{
|
||||||
Type: "tool_use",
|
Type: "tool_use",
|
||||||
Id: message.ToolCalls[i].Id,
|
Id: message.ToolCalls[i].Id,
|
||||||
Name: message.ToolCalls[i].Function.Name,
|
Name: message.ToolCalls[i].Function.Name,
|
||||||
Input: inputParam,
|
Input: inputParam,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
claudeMessage.Content = MessageContent{value: contents}
|
||||||
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
|
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var contents []Content
|
contents = []Content{} // Reset the slice for reuse
|
||||||
openaiContent := message.ParseContent()
|
openaiContent := message.ParseContent()
|
||||||
for _, part := range openaiContent {
|
for _, part := range openaiContent {
|
||||||
var content Content
|
var content Content
|
||||||
@@ -150,7 +153,7 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
|
|||||||
}
|
}
|
||||||
contents = append(contents, content)
|
contents = append(contents, content)
|
||||||
}
|
}
|
||||||
claudeMessage.Content = contents
|
claudeMessage.Content = MessageContent{value: contents}
|
||||||
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
|
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
|
||||||
}
|
}
|
||||||
return &claudeRequest
|
return &claudeRequest
|
||||||
@@ -220,11 +223,12 @@ func StreamResponseClaude2OpenAI(claudeResponse *StreamResponse) (*openai.ChatCo
|
|||||||
|
|
||||||
func ResponseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse {
|
func ResponseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse {
|
||||||
var responseText string
|
var responseText string
|
||||||
if len(claudeResponse.Content) > 0 {
|
contentArray := claudeResponse.Content.ToContentArray()
|
||||||
responseText = claudeResponse.Content[0].Text
|
if len(contentArray) > 0 {
|
||||||
|
responseText = contentArray[0].Text
|
||||||
}
|
}
|
||||||
tools := make([]model.Tool, 0)
|
tools := make([]model.Tool, 0)
|
||||||
for _, v := range claudeResponse.Content {
|
for _, v := range contentArray {
|
||||||
if v.Type == "tool_use" {
|
if v.Type == "tool_use" {
|
||||||
args, _ := json.Marshal(v.Input)
|
args, _ := json.Marshal(v.Input)
|
||||||
tools = append(tools, model.Tool{
|
tools = append(tools, model.Tool{
|
||||||
|
|||||||
@@ -29,9 +29,80 @@ type Content struct {
|
|||||||
ToolUseId string `json:"tool_use_id,omitempty"`
|
ToolUseId string `json:"tool_use_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MessageContent can handle both string and array formats for the content field
|
||||||
|
type MessageContent struct {
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler to handle both string and array formats
|
||||||
|
func (m *MessageContent) UnmarshalJSON(data []byte) error {
|
||||||
|
// Skip empty data or null
|
||||||
|
if len(data) == 0 || string(data) == "null" {
|
||||||
|
m.value = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to unmarshal as string first
|
||||||
|
var str string
|
||||||
|
if err := json.Unmarshal(data, &str); err == nil {
|
||||||
|
m.value = str
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that fails, try to unmarshal as array of Content
|
||||||
|
var arr []Content
|
||||||
|
if err := json.Unmarshal(data, &arr); err == nil {
|
||||||
|
m.value = arr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For routing purposes, store raw JSON if we can't parse it
|
||||||
|
// This ensures we don't lose any data during forwarding
|
||||||
|
var raw json.RawMessage
|
||||||
|
if err := json.Unmarshal(data, &raw); err == nil {
|
||||||
|
m.value = raw
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("message content must be either a string or an array of content blocks")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler
|
||||||
|
func (m MessageContent) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(m.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToContentArray converts the message content to a []Content array
|
||||||
|
func (m MessageContent) ToContentArray() []Content {
|
||||||
|
if m.value == nil {
|
||||||
|
return []Content{}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := m.value.(type) {
|
||||||
|
case string:
|
||||||
|
// Convert string to a single text content block
|
||||||
|
return []Content{{
|
||||||
|
Type: "text",
|
||||||
|
Text: v,
|
||||||
|
}}
|
||||||
|
case []Content:
|
||||||
|
return v
|
||||||
|
case json.RawMessage:
|
||||||
|
// Try to parse raw JSON as Content array
|
||||||
|
var arr []Content
|
||||||
|
if err := json.Unmarshal(v, &arr); err == nil {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
// If that fails, return empty array to avoid breaking the routing
|
||||||
|
return []Content{}
|
||||||
|
default:
|
||||||
|
return []Content{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content []Content `json:"content"`
|
Content MessageContent `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tool struct {
|
type Tool struct {
|
||||||
@@ -146,15 +217,15 @@ type Error struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content []Content `json:"content"`
|
Content MessageContent `json:"content"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
StopReason *string `json:"stop_reason"`
|
StopReason *string `json:"stop_reason"`
|
||||||
StopSequence *string `json:"stop_sequence"`
|
StopSequence *string `json:"stop_sequence"`
|
||||||
Usage Usage `json:"usage"`
|
Usage Usage `json:"usage"`
|
||||||
Error Error `json:"error"`
|
Error Error `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Delta struct {
|
type Delta struct {
|
||||||
|
|||||||
@@ -87,7 +87,18 @@ func RelayAnthropicHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
|||||||
logger.Debugf(ctx, "Received response - Status: %d", resp.StatusCode)
|
logger.Debugf(ctx, "Received response - Status: %d", resp.StatusCode)
|
||||||
|
|
||||||
if isErrorHappened(meta, resp) {
|
if isErrorHappened(meta, resp) {
|
||||||
logger.Errorf(ctx, "Error detected in response")
|
logger.Errorf(ctx, "Error detected in response - Status: %d", resp.StatusCode)
|
||||||
|
|
||||||
|
// Read response body for detailed error logging
|
||||||
|
if resp.Body != nil {
|
||||||
|
bodyBytes, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr == nil {
|
||||||
|
logger.Errorf(ctx, "Error response body: %s", string(bodyBytes))
|
||||||
|
// Recreate the body for further processing
|
||||||
|
resp.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
billing.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId)
|
billing.ReturnPreConsumedQuota(ctx, preConsumedQuota, meta.TokenId)
|
||||||
return RelayErrorHandler(resp)
|
return RelayErrorHandler(resp)
|
||||||
}
|
}
|
||||||
@@ -111,13 +122,17 @@ func RelayAnthropicHelper(c *gin.Context) *model.ErrorWithStatusCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAndValidateAnthropicRequest(c *gin.Context) (*anthropic.Request, error) {
|
func getAndValidateAnthropicRequest(c *gin.Context) (*anthropic.Request, error) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
anthropicRequest := &anthropic.Request{}
|
anthropicRequest := &anthropic.Request{}
|
||||||
err := common.UnmarshalBodyReusable(c, anthropicRequest)
|
err := common.UnmarshalBodyReusable(c, anthropicRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorf(ctx, "Failed to unmarshal Anthropic request: %s", err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic validation
|
logger.Debugf(ctx, "Successfully parsed Anthropic request with %d messages", len(anthropicRequest.Messages))
|
||||||
|
|
||||||
|
// Basic validation only - minimal intervention
|
||||||
if anthropicRequest.Model == "" {
|
if anthropicRequest.Model == "" {
|
||||||
return nil, fmt.Errorf("model is required")
|
return nil, fmt.Errorf("model is required")
|
||||||
}
|
}
|
||||||
@@ -128,6 +143,26 @@ func getAndValidateAnthropicRequest(c *gin.Context) (*anthropic.Request, error)
|
|||||||
anthropicRequest.MaxTokens = 4096 // default max tokens
|
anthropicRequest.MaxTokens = 4096 // default max tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add detailed logging for debugging without filtering messages
|
||||||
|
logger.Debugf(ctx, "Request details - Model: %s, MaxTokens: %d, Messages: %d",
|
||||||
|
anthropicRequest.Model, anthropicRequest.MaxTokens, len(anthropicRequest.Messages))
|
||||||
|
|
||||||
|
for i, message := range anthropicRequest.Messages {
|
||||||
|
contentArray := message.Content.ToContentArray()
|
||||||
|
logger.Debugf(ctx, "Message[%d] - Role: %s, Content blocks: %d", i, message.Role, len(contentArray))
|
||||||
|
|
||||||
|
for j, content := range contentArray {
|
||||||
|
if content.Type == "tool_use" {
|
||||||
|
logger.Debugf(ctx, " Content[%d] - Type: %s, ID: %s, Name: %s", j, content.Type, content.Id, content.Name)
|
||||||
|
} else if content.Type == "tool_result" {
|
||||||
|
logger.Debugf(ctx, " Content[%d] - Type: %s, ToolUseId: %s, Content length: %d",
|
||||||
|
j, content.Type, content.ToolUseId, len(content.Content))
|
||||||
|
} else {
|
||||||
|
logger.Debugf(ctx, " Content[%d] - Type: %s, Text length: %d", j, content.Type, len(content.Text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return anthropicRequest, nil
|
return anthropicRequest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +170,14 @@ func getAnthropicRequestBody(c *gin.Context, anthropicRequest *anthropic.Request
|
|||||||
// For anthropic native requests, we marshal the request back to JSON
|
// For anthropic native requests, we marshal the request back to JSON
|
||||||
jsonData, err := json.Marshal(anthropicRequest)
|
jsonData, err := json.Marshal(anthropicRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debugf(c.Request.Context(), "anthropic request json_marshal_failed: %s\n", err.Error())
|
logger.Errorf(c.Request.Context(), "Failed to marshal anthropic request: %s", err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logger.Debugf(c.Request.Context(), "anthropic request: \n%s", string(jsonData))
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
logger.Debugf(ctx, "Final request body size: %d bytes", len(jsonData))
|
||||||
|
logger.Debugf(ctx, "Final request body: %s", string(jsonData))
|
||||||
|
|
||||||
return bytes.NewBuffer(jsonData), nil
|
return bytes.NewBuffer(jsonData), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +200,7 @@ func estimateAnthropicTokens(request *anthropic.Request) int {
|
|||||||
|
|
||||||
// Count tokens in messages
|
// Count tokens in messages
|
||||||
for _, message := range request.Messages {
|
for _, message := range request.Messages {
|
||||||
for _, content := range message.Content {
|
for _, content := range message.Content.ToContentArray() {
|
||||||
if content.Type == "text" {
|
if content.Type == "text" {
|
||||||
totalTokens += len(content.Text) / CHARS_PER_TOKEN
|
totalTokens += len(content.Text) / CHARS_PER_TOKEN
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user