mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-18 01:26:37 +08:00
- Improve error handling across multiple middleware and adapter components, ensuring consistent error response formats in JSON. - Enhance the functionality of request conversion functions by including context parameters and robust error wrapping. - Introduce new features related to reasoning content in the messaging model, providing better customization and explanations in the documentation.
192 lines
5.9 KiB
Go
192 lines
5.9 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/songquanpeng/one-api/common/logger"
|
|
)
|
|
|
|
// ReasoningFormat is the format of reasoning content,
|
|
// can be set by the reasoning_format parameter in the request url.
|
|
type ReasoningFormat string
|
|
|
|
const (
|
|
ReasoningFormatUnspecified ReasoningFormat = ""
|
|
// ReasoningFormatReasoningContent is the reasoning format used by deepseek official API
|
|
ReasoningFormatReasoningContent ReasoningFormat = "reasoning_content"
|
|
// ReasoningFormatReasoning is the reasoning format used by openrouter
|
|
ReasoningFormatReasoning ReasoningFormat = "reasoning"
|
|
|
|
// ReasoningFormatThinkTag is the reasoning format used by 3rd party deepseek-r1 providers.
|
|
//
|
|
// Deprecated: I believe <think> is a very poor format, especially in stream mode, it is difficult to extract and convert.
|
|
// Considering that only a few deepseek-r1 third-party providers use this format, it has been decided to no longer support it.
|
|
// ReasoningFormatThinkTag ReasoningFormat = "think-tag"
|
|
|
|
// ReasoningFormatThinking is the reasoning format used by anthropic
|
|
ReasoningFormatThinking ReasoningFormat = "thinking"
|
|
)
|
|
|
|
type Message struct {
|
|
Role string `json:"role,omitempty"`
|
|
// Content is a string or a list of objects
|
|
Content any `json:"content,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
ToolCalls []Tool `json:"tool_calls,omitempty"`
|
|
ToolCallId string `json:"tool_call_id,omitempty"`
|
|
Audio *messageAudio `json:"audio,omitempty"`
|
|
// -------------------------------------
|
|
// Deepseek 专有的一些字段
|
|
// https://api-docs.deepseek.com/api/create-chat-completion
|
|
// -------------------------------------
|
|
// Prefix forces the model to begin its answer with the supplied prefix in the assistant message.
|
|
// To enable this feature, set base_url to "https://api.deepseek.com/beta".
|
|
Prefix *bool `json:"prefix,omitempty"` // ReasoningContent is Used for the deepseek-reasoner model in the Chat
|
|
// Prefix Completion feature as the input for the CoT in the last assistant message.
|
|
// When using this feature, the prefix parameter must be set to true.
|
|
ReasoningContent *string `json:"reasoning_content,omitempty"`
|
|
// -------------------------------------
|
|
// Openrouter
|
|
// -------------------------------------
|
|
Reasoning *string `json:"reasoning,omitempty"`
|
|
Refusal *bool `json:"refusal,omitempty"`
|
|
// -------------------------------------
|
|
// Anthropic
|
|
// -------------------------------------
|
|
Thinking *string `json:"thinking,omitempty"`
|
|
Signature *string `json:"signature,omitempty"`
|
|
}
|
|
|
|
// SetReasoningContent sets the reasoning content based on the format
|
|
func (m *Message) SetReasoningContent(format string, reasoningContent string) {
|
|
switch ReasoningFormat(strings.ToLower(strings.TrimSpace(format))) {
|
|
case ReasoningFormatReasoningContent:
|
|
m.ReasoningContent = &reasoningContent
|
|
// case ReasoningFormatThinkTag:
|
|
// m.Content = fmt.Sprintf("<think>%s</think>%s", reasoningContent, m.Content)
|
|
case ReasoningFormatThinking:
|
|
m.Thinking = &reasoningContent
|
|
case ReasoningFormatReasoning,
|
|
ReasoningFormatUnspecified:
|
|
m.Reasoning = &reasoningContent
|
|
default:
|
|
logger.Warnf(context.TODO(), "unknown reasoning format: %q", format)
|
|
}
|
|
}
|
|
|
|
type messageAudio struct {
|
|
Id string `json:"id"`
|
|
Data string `json:"data,omitempty"`
|
|
ExpiredAt int `json:"expired_at,omitempty"`
|
|
Transcript string `json:"transcript,omitempty"`
|
|
}
|
|
|
|
func (m Message) IsStringContent() bool {
|
|
_, ok := m.Content.(string)
|
|
return ok
|
|
}
|
|
|
|
func (m Message) StringContent() string {
|
|
content, ok := m.Content.(string)
|
|
if ok {
|
|
return content
|
|
}
|
|
contentList, ok := m.Content.([]any)
|
|
if ok {
|
|
var contentStr string
|
|
for _, contentItem := range contentList {
|
|
contentMap, ok := contentItem.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if contentMap["type"] == ContentTypeText {
|
|
if subStr, ok := contentMap["text"].(string); ok {
|
|
contentStr += subStr
|
|
}
|
|
}
|
|
}
|
|
return contentStr
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (m Message) ParseContent() []MessageContent {
|
|
var contentList []MessageContent
|
|
content, ok := m.Content.(string)
|
|
if ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeText,
|
|
Text: content,
|
|
})
|
|
return contentList
|
|
}
|
|
|
|
anyList, ok := m.Content.([]any)
|
|
if ok {
|
|
for _, contentItem := range anyList {
|
|
contentMap, ok := contentItem.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch contentMap["type"] {
|
|
case ContentTypeText:
|
|
if subStr, ok := contentMap["text"].(string); ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeText,
|
|
Text: subStr,
|
|
})
|
|
}
|
|
case ContentTypeImageURL:
|
|
if subObj, ok := contentMap["image_url"].(map[string]any); ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeImageURL,
|
|
ImageURL: &ImageURL{
|
|
Url: subObj["url"].(string),
|
|
},
|
|
})
|
|
}
|
|
case ContentTypeInputAudio:
|
|
if subObj, ok := contentMap["input_audio"].(map[string]any); ok {
|
|
contentList = append(contentList, MessageContent{
|
|
Type: ContentTypeInputAudio,
|
|
InputAudio: &InputAudio{
|
|
Data: subObj["data"].(string),
|
|
Format: subObj["format"].(string),
|
|
},
|
|
})
|
|
}
|
|
default:
|
|
logger.Warnf(context.TODO(), "unknown content type: %s", contentMap["type"])
|
|
}
|
|
}
|
|
|
|
return contentList
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ImageURL struct {
|
|
Url string `json:"url,omitempty"`
|
|
Detail string `json:"detail,omitempty"`
|
|
}
|
|
|
|
type MessageContent struct {
|
|
// Type should be one of the following: text/input_audio
|
|
Type string `json:"type,omitempty"`
|
|
Text string `json:"text"`
|
|
ImageURL *ImageURL `json:"image_url,omitempty"`
|
|
InputAudio *InputAudio `json:"input_audio,omitempty"`
|
|
}
|
|
|
|
type InputAudio struct {
|
|
// Data is the base64 encoded audio data
|
|
Data string `json:"data" binding:"required"`
|
|
// Format is the audio format, should be one of the
|
|
// following: mp3/mp4/mpeg/mpga/m4a/wav/webm/pcm16.
|
|
// When stream=true, format should be pcm16
|
|
Format string `json:"format"`
|
|
}
|