refactor: Introduce pre-consume quota and unify relay handlers
This commit introduces a major architectural refactoring to improve quota management, centralize logging, and streamline the relay handling logic. Key changes: - **Pre-consume Quota:** Implements a new mechanism to check and reserve user quota *before* making the request to the upstream provider. This ensures more accurate quota deduction and prevents users from exceeding their limits due to concurrent requests. - **Unified Relay Handlers:** Refactors the relay logic to use generic handlers (e.g., `ChatHandler`, `ImageHandler`) instead of provider-specific implementations. This significantly reduces code duplication and simplifies adding new channels. - **Centralized Logger:** A new dedicated `logger` package is introduced, and all system logging calls are migrated to use it, moving this responsibility out of the `common` package. - **Code Reorganization:** DTOs are generalized (e.g., `dalle.go` -> `openai_image.go`) and utility code is moved to more appropriate packages (e.g., `common/http.go` -> `service/http.go`) for better code structure.
This commit is contained in:
18
dto/audio.go
18
dto/audio.go
@@ -1,5 +1,11 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"one-api/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AudioRequest struct {
|
||||
Model string `json:"model"`
|
||||
Input string `json:"input"`
|
||||
@@ -8,6 +14,18 @@ type AudioRequest struct {
|
||||
ResponseFormat string `json:"response_format,omitempty"`
|
||||
}
|
||||
|
||||
func (r *AudioRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
meta := &types.TokenCountMeta{
|
||||
CombineText: r.Input,
|
||||
TokenType: types.TokenTypeTextNumber,
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func (r *AudioRequest) IsStream(c *gin.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type AudioResponse struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
128
dto/claude.go
128
dto/claude.go
@@ -5,6 +5,9 @@ import (
|
||||
"fmt"
|
||||
"one-api/common"
|
||||
"one-api/types"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ClaudeMetadata struct {
|
||||
@@ -81,7 +84,7 @@ func (c *ClaudeMediaMessage) GetStringContent() string {
|
||||
}
|
||||
|
||||
func (c *ClaudeMediaMessage) GetJsonRowString() string {
|
||||
jsonContent, _ := json.Marshal(c)
|
||||
jsonContent, _ := common.Marshal(c)
|
||||
return string(jsonContent)
|
||||
}
|
||||
|
||||
@@ -199,6 +202,129 @@ type ClaudeRequest struct {
|
||||
Thinking *Thinking `json:"thinking,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ClaudeRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var tokenCountMeta = types.TokenCountMeta{
|
||||
TokenType: types.TokenTypeTextNumber,
|
||||
MaxTokens: int(c.MaxTokens),
|
||||
}
|
||||
|
||||
var texts = make([]string, 0)
|
||||
var fileMeta = make([]*types.FileMeta, 0)
|
||||
|
||||
// system
|
||||
if c.System != nil {
|
||||
if c.IsStringSystem() {
|
||||
sys := c.GetStringSystem()
|
||||
if sys != "" {
|
||||
texts = append(texts, sys)
|
||||
}
|
||||
} else {
|
||||
systemMedia := c.ParseSystem()
|
||||
for _, media := range systemMedia {
|
||||
switch media.Type {
|
||||
case "text":
|
||||
texts = append(texts, media.GetText())
|
||||
case "image":
|
||||
if media.Source != nil {
|
||||
data := media.Source.Url
|
||||
if data == "" {
|
||||
data = common.Interface2String(media.Source.Data)
|
||||
}
|
||||
if data != "" {
|
||||
fileMeta = append(fileMeta, &types.FileMeta{FileType: types.FileTypeImage, Data: data})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// messages
|
||||
for _, message := range c.Messages {
|
||||
tokenCountMeta.MessagesCount++
|
||||
texts = append(texts, message.Role)
|
||||
if message.IsStringContent() {
|
||||
content := message.GetStringContent()
|
||||
if content != "" {
|
||||
texts = append(texts, content)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
content, _ := message.ParseContent()
|
||||
for _, media := range content {
|
||||
switch media.Type {
|
||||
case "text":
|
||||
texts = append(texts, media.GetText())
|
||||
case "image":
|
||||
if media.Source != nil {
|
||||
data := media.Source.Url
|
||||
if data == "" {
|
||||
data = common.Interface2String(media.Source.Data)
|
||||
}
|
||||
if data != "" {
|
||||
fileMeta = append(fileMeta, &types.FileMeta{FileType: types.FileTypeImage, Data: data})
|
||||
}
|
||||
}
|
||||
case "tool_use":
|
||||
if media.Name != "" {
|
||||
texts = append(texts, media.Name)
|
||||
}
|
||||
if media.Input != nil {
|
||||
b, _ := common.Marshal(media.Input)
|
||||
texts = append(texts, string(b))
|
||||
}
|
||||
case "tool_result":
|
||||
if media.Content != nil {
|
||||
b, _ := common.Marshal(media.Content)
|
||||
texts = append(texts, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tools
|
||||
if c.Tools != nil {
|
||||
tools := c.GetTools()
|
||||
normalTools, webSearchTools := ProcessTools(tools)
|
||||
if normalTools != nil {
|
||||
for _, t := range normalTools {
|
||||
tokenCountMeta.ToolsCount++
|
||||
if t.Name != "" {
|
||||
texts = append(texts, t.Name)
|
||||
}
|
||||
if t.Description != "" {
|
||||
texts = append(texts, t.Description)
|
||||
}
|
||||
if t.InputSchema != nil {
|
||||
b, _ := common.Marshal(t.InputSchema)
|
||||
texts = append(texts, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
if webSearchTools != nil {
|
||||
for _, t := range webSearchTools {
|
||||
tokenCountMeta.ToolsCount++
|
||||
if t.Name != "" {
|
||||
texts = append(texts, t.Name)
|
||||
}
|
||||
if t.UserLocation != nil {
|
||||
b, _ := common.Marshal(t.UserLocation)
|
||||
texts = append(texts, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokenCountMeta.CombineText = strings.Join(texts, "\n")
|
||||
tokenCountMeta.Files = fileMeta
|
||||
return &tokenCountMeta
|
||||
}
|
||||
|
||||
func (claudeRequest *ClaudeRequest) IsStream(c *gin.Context) bool {
|
||||
return claudeRequest.Stream
|
||||
}
|
||||
|
||||
func (c *ClaudeRequest) SearchToolNameByToolCallId(toolCallId string) string {
|
||||
for _, message := range c.Messages {
|
||||
content, _ := message.ParseContent()
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"one-api/types"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type EmbeddingOptions struct {
|
||||
Seed int `json:"seed,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
@@ -24,9 +31,26 @@ type EmbeddingRequest struct {
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty"`
|
||||
}
|
||||
|
||||
func (r EmbeddingRequest) ParseInput() []string {
|
||||
func (r *EmbeddingRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var texts = make([]string, 0)
|
||||
|
||||
inputs := r.ParseInput()
|
||||
for _, input := range inputs {
|
||||
texts = append(texts, input)
|
||||
}
|
||||
|
||||
return &types.TokenCountMeta{
|
||||
CombineText: strings.Join(texts, "\n"),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *EmbeddingRequest) IsStream(c *gin.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *EmbeddingRequest) ParseInput() []string {
|
||||
if r.Input == nil {
|
||||
return nil
|
||||
return make([]string, 0)
|
||||
}
|
||||
var input []string
|
||||
switch r.Input.(type) {
|
||||
|
||||
@@ -2,7 +2,10 @@ package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"one-api/common"
|
||||
"one-api/logger"
|
||||
"one-api/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -14,19 +17,75 @@ type GeminiChatRequest struct {
|
||||
SystemInstructions *GeminiChatContent `json:"systemInstruction,omitempty"`
|
||||
}
|
||||
|
||||
func (r *GeminiChatRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var files []*types.FileMeta = make([]*types.FileMeta, 0)
|
||||
|
||||
var maxTokens int
|
||||
|
||||
if r.GenerationConfig.MaxOutputTokens > 0 {
|
||||
maxTokens = int(r.GenerationConfig.MaxOutputTokens)
|
||||
}
|
||||
|
||||
var inputTexts []string
|
||||
for _, content := range r.Contents {
|
||||
for _, part := range content.Parts {
|
||||
if part.Text != "" {
|
||||
inputTexts = append(inputTexts, part.Text)
|
||||
}
|
||||
if part.InlineData != nil && part.InlineData.Data != "" {
|
||||
if strings.HasPrefix(part.InlineData.MimeType, "image/") {
|
||||
files = append(files, &types.FileMeta{
|
||||
FileType: types.FileTypeImage,
|
||||
Data: part.InlineData.Data,
|
||||
})
|
||||
} else if strings.HasPrefix(part.InlineData.MimeType, "audio/") {
|
||||
files = append(files, &types.FileMeta{
|
||||
FileType: types.FileTypeAudio,
|
||||
Data: part.InlineData.Data,
|
||||
})
|
||||
} else if strings.HasPrefix(part.InlineData.MimeType, "video/") {
|
||||
files = append(files, &types.FileMeta{
|
||||
FileType: types.FileTypeVideo,
|
||||
Data: part.InlineData.Data,
|
||||
})
|
||||
} else {
|
||||
files = append(files, &types.FileMeta{
|
||||
FileType: types.FileTypeFile,
|
||||
Data: part.InlineData.Data,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputText := strings.Join(inputTexts, "\n")
|
||||
return &types.TokenCountMeta{
|
||||
CombineText: inputText,
|
||||
Files: files,
|
||||
MaxTokens: maxTokens,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *GeminiChatRequest) IsStream(c *gin.Context) bool {
|
||||
if c.Query("alt") == "sse" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *GeminiChatRequest) GetTools() []GeminiChatTool {
|
||||
var tools []GeminiChatTool
|
||||
if strings.HasSuffix(string(r.Tools), "[") {
|
||||
// is array
|
||||
if err := common.Unmarshal(r.Tools, &tools); err != nil {
|
||||
common.LogError(nil, "error_unmarshalling_tools: "+err.Error())
|
||||
logger.LogError(nil, "error_unmarshalling_tools: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
} else if strings.HasPrefix(string(r.Tools), "{") {
|
||||
// is object
|
||||
singleTool := GeminiChatTool{}
|
||||
if err := common.Unmarshal(r.Tools, &singleTool); err != nil {
|
||||
common.LogError(nil, "error_unmarshalling_single_tool: "+err.Error())
|
||||
logger.LogError(nil, "error_unmarshalling_single_tool: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
tools = []GeminiChatTool{singleTool}
|
||||
@@ -43,7 +102,7 @@ func (r *GeminiChatRequest) SetTools(tools []GeminiChatTool) {
|
||||
// Marshal the tools to JSON
|
||||
data, err := common.Marshal(tools)
|
||||
if err != nil {
|
||||
common.LogError(nil, "error_marshalling_tools: "+err.Error())
|
||||
logger.LogError(nil, "error_marshalling_tools: "+err.Error())
|
||||
return
|
||||
}
|
||||
r.Tools = data
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
package dto
|
||||
|
||||
import "encoding/json"
|
||||
import (
|
||||
"encoding/json"
|
||||
"one-api/types"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ImageRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt" binding:"required"`
|
||||
N int `json:"n,omitempty"`
|
||||
N uint `json:"n,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Quality string `json:"quality,omitempty"`
|
||||
ResponseFormat string `json:"response_format,omitempty"`
|
||||
@@ -18,6 +24,42 @@ type ImageRequest struct {
|
||||
Watermark *bool `json:"watermark,omitempty"`
|
||||
}
|
||||
|
||||
func (i *ImageRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var sizeRatio = 1.0
|
||||
var qualityRatio = 1.0
|
||||
|
||||
if strings.HasPrefix(i.Model, "dall-e") {
|
||||
// Size
|
||||
if i.Size == "256x256" {
|
||||
sizeRatio = 0.4
|
||||
} else if i.Size == "512x512" {
|
||||
sizeRatio = 0.45
|
||||
} else if i.Size == "1024x1024" {
|
||||
sizeRatio = 1
|
||||
} else if i.Size == "1024x1792" || i.Size == "1792x1024" {
|
||||
sizeRatio = 2
|
||||
}
|
||||
|
||||
if i.Model == "dall-e-3" && i.Quality == "hd" {
|
||||
qualityRatio = 2.0
|
||||
if i.Size == "1024x1792" || i.Size == "1792x1024" {
|
||||
qualityRatio = 1.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not support token count for dalle
|
||||
return &types.TokenCountMeta{
|
||||
CombineText: i.Prompt,
|
||||
MaxTokens: 1584,
|
||||
ImagePriceRatio: sizeRatio * qualityRatio * float64(i.N),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ImageRequest) IsStream(c *gin.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type ImageResponse struct {
|
||||
Data []ImageData `json:"data"`
|
||||
Created int64 `json:"created"`
|
||||
@@ -2,8 +2,12 @@ package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"one-api/common"
|
||||
"one-api/types"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ResponseFormat struct {
|
||||
@@ -67,6 +71,116 @@ type GeneralOpenAIRequest struct {
|
||||
Extra map[string]json.RawMessage `json:"-"`
|
||||
}
|
||||
|
||||
func (r *GeneralOpenAIRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var tokenCountMeta types.TokenCountMeta
|
||||
var texts = make([]string, 0)
|
||||
var fileMeta = make([]*types.FileMeta, 0)
|
||||
|
||||
if r.Prompt != nil {
|
||||
switch v := r.Prompt.(type) {
|
||||
case string:
|
||||
texts = append(texts, v)
|
||||
case []any:
|
||||
for _, item := range v {
|
||||
if str, ok := item.(string); ok {
|
||||
texts = append(texts, str)
|
||||
}
|
||||
}
|
||||
default:
|
||||
texts = append(texts, fmt.Sprintf("%v", r.Prompt))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Input != nil {
|
||||
inputs := r.ParseInput()
|
||||
texts = append(texts, inputs...)
|
||||
}
|
||||
|
||||
if r.MaxCompletionTokens > r.MaxTokens {
|
||||
tokenCountMeta.MaxTokens = int(r.MaxCompletionTokens)
|
||||
} else {
|
||||
tokenCountMeta.MaxTokens = int(r.MaxTokens)
|
||||
}
|
||||
|
||||
for _, message := range r.Messages {
|
||||
tokenCountMeta.MessagesCount++
|
||||
texts = append(texts, message.Role)
|
||||
if message.Content != nil {
|
||||
if message.Name != nil {
|
||||
tokenCountMeta.NameCount++
|
||||
texts = append(texts, *message.Name)
|
||||
}
|
||||
arrayContent := message.ParseContent()
|
||||
for _, m := range arrayContent {
|
||||
if m.Type == ContentTypeImageURL {
|
||||
imageUrl := m.GetImageMedia()
|
||||
if imageUrl != nil {
|
||||
meta := &types.FileMeta{
|
||||
FileType: types.FileTypeImage,
|
||||
}
|
||||
meta.Data = imageUrl.Url
|
||||
meta.Detail = imageUrl.Detail
|
||||
fileMeta = append(fileMeta, meta)
|
||||
}
|
||||
} else if m.Type == ContentTypeInputAudio {
|
||||
inputAudio := m.GetInputAudio()
|
||||
if inputAudio != nil {
|
||||
meta := &types.FileMeta{
|
||||
FileType: types.FileTypeAudio,
|
||||
}
|
||||
meta.Data = inputAudio.Data
|
||||
fileMeta = append(fileMeta, meta)
|
||||
}
|
||||
} else if m.Type == ContentTypeFile {
|
||||
file := m.GetFile()
|
||||
if file != nil {
|
||||
meta := &types.FileMeta{
|
||||
FileType: types.FileTypeFile,
|
||||
}
|
||||
meta.Data = file.FileData
|
||||
fileMeta = append(fileMeta, meta)
|
||||
}
|
||||
} else if m.Type == ContentTypeVideoUrl {
|
||||
videoUrl := m.GetVideoUrl()
|
||||
if videoUrl != nil {
|
||||
meta := &types.FileMeta{
|
||||
FileType: types.FileTypeVideo,
|
||||
}
|
||||
meta.Data = videoUrl.Url
|
||||
fileMeta = append(fileMeta, meta)
|
||||
}
|
||||
} else {
|
||||
texts = append(texts, m.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Tools != nil {
|
||||
openaiTools := r.Tools
|
||||
for _, tool := range openaiTools {
|
||||
tokenCountMeta.ToolsCount++
|
||||
texts = append(texts, tool.Function.Name)
|
||||
if tool.Function.Description != "" {
|
||||
texts = append(texts, tool.Function.Description)
|
||||
}
|
||||
if tool.Function.Parameters != nil {
|
||||
texts = append(texts, fmt.Sprintf("%v", tool.Function.Parameters))
|
||||
}
|
||||
}
|
||||
//toolTokens := CountTokenInput(countStr, request.Model)
|
||||
//tkm += 8
|
||||
//tkm += toolTokens
|
||||
}
|
||||
tokenCountMeta.CombineText = strings.Join(texts, "\n")
|
||||
tokenCountMeta.Files = fileMeta
|
||||
return &tokenCountMeta
|
||||
}
|
||||
|
||||
func (r *GeneralOpenAIRequest) IsStream(c *gin.Context) bool {
|
||||
return r.Stream
|
||||
}
|
||||
|
||||
func (r *GeneralOpenAIRequest) ToMap() map[string]any {
|
||||
result := make(map[string]any)
|
||||
data, _ := common.Marshal(r)
|
||||
@@ -202,10 +316,25 @@ func (m *MediaContent) GetFile() *MessageFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MediaContent) GetVideoUrl() *MessageVideoUrl {
|
||||
if m.VideoUrl != nil {
|
||||
if _, ok := m.VideoUrl.(*MessageVideoUrl); ok {
|
||||
return m.VideoUrl.(*MessageVideoUrl)
|
||||
}
|
||||
if itemMap, ok := m.VideoUrl.(map[string]any); ok {
|
||||
out := &MessageVideoUrl{
|
||||
Url: common.Interface2String(itemMap["url"]),
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageImageUrl struct {
|
||||
Url string `json:"url"`
|
||||
Detail string `json:"detail"`
|
||||
MimeType string
|
||||
Url string `json:"url"`
|
||||
Detail string `json:"detail"`
|
||||
//MimeType string
|
||||
}
|
||||
|
||||
func (m *MessageImageUrl) IsRemoteImage() bool {
|
||||
@@ -233,6 +362,7 @@ const (
|
||||
ContentTypeInputAudio = "input_audio"
|
||||
ContentTypeFile = "file"
|
||||
ContentTypeVideoUrl = "video_url" // 阿里百炼视频识别
|
||||
//ContentTypeAudioUrl = "audio_url"
|
||||
)
|
||||
|
||||
func (m *Message) GetPrefix() bool {
|
||||
@@ -623,7 +753,7 @@ type WebSearchOptions struct {
|
||||
// https://platform.openai.com/docs/api-reference/responses/create
|
||||
type OpenAIResponsesRequest struct {
|
||||
Model string `json:"model"`
|
||||
Input json.RawMessage `json:"input,omitempty"`
|
||||
Input any `json:"input,omitempty"`
|
||||
Include json.RawMessage `json:"include,omitempty"`
|
||||
Instructions json.RawMessage `json:"instructions,omitempty"`
|
||||
MaxOutputTokens uint `json:"max_output_tokens,omitempty"`
|
||||
@@ -645,28 +775,145 @@ type OpenAIResponsesRequest struct {
|
||||
Prompt json.RawMessage `json:"prompt,omitempty"`
|
||||
}
|
||||
|
||||
func (r *OpenAIResponsesRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var fileMeta = make([]*types.FileMeta, 0)
|
||||
var texts = make([]string, 0)
|
||||
|
||||
if r.Input != nil {
|
||||
inputs := r.ParseInput()
|
||||
for _, input := range inputs {
|
||||
if input.Type == "input_image" {
|
||||
fileMeta = append(fileMeta, &types.FileMeta{
|
||||
FileType: types.FileTypeImage,
|
||||
Data: input.ImageUrl,
|
||||
Detail: input.Detail,
|
||||
})
|
||||
} else if input.Type == "input_file" {
|
||||
fileMeta = append(fileMeta, &types.FileMeta{
|
||||
FileType: types.FileTypeFile,
|
||||
Data: input.FileUrl,
|
||||
})
|
||||
} else {
|
||||
texts = append(texts, input.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Instructions) > 0 {
|
||||
texts = append(texts, string(r.Instructions))
|
||||
}
|
||||
|
||||
if len(r.Metadata) > 0 {
|
||||
texts = append(texts, string(r.Metadata))
|
||||
}
|
||||
|
||||
if len(r.Text) > 0 {
|
||||
texts = append(texts, string(r.Text))
|
||||
}
|
||||
|
||||
if len(r.ToolChoice) > 0 {
|
||||
texts = append(texts, string(r.ToolChoice))
|
||||
}
|
||||
|
||||
if len(r.Prompt) > 0 {
|
||||
texts = append(texts, string(r.Prompt))
|
||||
}
|
||||
|
||||
if len(r.Tools) > 0 {
|
||||
toolStr, _ := common.Marshal(r.Tools)
|
||||
texts = append(texts, string(toolStr))
|
||||
}
|
||||
|
||||
return &types.TokenCountMeta{
|
||||
CombineText: strings.Join(texts, "\n"),
|
||||
Files: fileMeta,
|
||||
MaxTokens: int(r.MaxOutputTokens),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *OpenAIResponsesRequest) IsStream(c *gin.Context) bool {
|
||||
return r.Stream
|
||||
}
|
||||
|
||||
type Reasoning struct {
|
||||
Effort string `json:"effort,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
//type ResponsesToolsCall struct {
|
||||
// Type string `json:"type"`
|
||||
// // Web Search
|
||||
// UserLocation json.RawMessage `json:"user_location,omitempty"`
|
||||
// SearchContextSize string `json:"search_context_size,omitempty"`
|
||||
// // File Search
|
||||
// VectorStoreIds []string `json:"vector_store_ids,omitempty"`
|
||||
// MaxNumResults uint `json:"max_num_results,omitempty"`
|
||||
// Filters json.RawMessage `json:"filters,omitempty"`
|
||||
// // Computer Use
|
||||
// DisplayWidth uint `json:"display_width,omitempty"`
|
||||
// DisplayHeight uint `json:"display_height,omitempty"`
|
||||
// Environment string `json:"environment,omitempty"`
|
||||
// // Function
|
||||
// Name string `json:"name,omitempty"`
|
||||
// Description string `json:"description,omitempty"`
|
||||
// Parameters json.RawMessage `json:"parameters,omitempty"`
|
||||
// Function json.RawMessage `json:"function,omitempty"`
|
||||
// Container json.RawMessage `json:"container,omitempty"`
|
||||
//}
|
||||
type MediaInput struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text,omitempty"`
|
||||
FileUrl string `json:"file_url,omitempty"`
|
||||
ImageUrl string `json:"image_url,omitempty"`
|
||||
Detail string `json:"detail,omitempty"` // 仅 input_image 有效
|
||||
}
|
||||
|
||||
// ParseInput parses the Responses API `input` field into a normalized slice of MediaInput.
|
||||
// Reference implementation mirrors Message.ParseContent:
|
||||
// - input can be a string, treated as an input_text item
|
||||
// - input can be an array of objects with a `type` field
|
||||
// supported types: input_text, input_image, input_file
|
||||
func (r *OpenAIResponsesRequest) ParseInput() []MediaInput {
|
||||
if r.Input == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var inputs []MediaInput
|
||||
|
||||
// Try string first
|
||||
if str, ok := r.Input.(string); ok {
|
||||
inputs = append(inputs, MediaInput{Type: "input_text", Text: str})
|
||||
return inputs
|
||||
}
|
||||
|
||||
// Try array of parts
|
||||
if array, ok := r.Input.([]any); ok {
|
||||
for _, itemAny := range array {
|
||||
// Already parsed MediaInput
|
||||
if media, ok := itemAny.(MediaInput); ok {
|
||||
inputs = append(inputs, media)
|
||||
continue
|
||||
}
|
||||
// Generic map
|
||||
item, ok := itemAny.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
typeVal, ok := item["type"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch typeVal {
|
||||
case "input_text":
|
||||
text, _ := item["text"].(string)
|
||||
inputs = append(inputs, MediaInput{Type: "input_text", Text: text})
|
||||
case "input_image":
|
||||
// image_url may be string or object with url field
|
||||
var imageUrl string
|
||||
switch v := item["image_url"].(type) {
|
||||
case string:
|
||||
imageUrl = v
|
||||
case map[string]any:
|
||||
if url, ok := v["url"].(string); ok {
|
||||
imageUrl = url
|
||||
}
|
||||
}
|
||||
inputs = append(inputs, MediaInput{Type: "input_image", ImageUrl: imageUrl})
|
||||
case "input_file":
|
||||
// file_url may be string or object with url field
|
||||
var fileUrl string
|
||||
switch v := item["file_url"].(type) {
|
||||
case string:
|
||||
fileUrl = v
|
||||
case map[string]any:
|
||||
if url, ok := v["url"].(string); ok {
|
||||
fileUrl = url
|
||||
}
|
||||
}
|
||||
inputs = append(inputs, MediaInput{Type: "input_file", FileUrl: fileUrl})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputs
|
||||
}
|
||||
|
||||
11
dto/request_common.go
Normal file
11
dto/request_common.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"one-api/types"
|
||||
)
|
||||
|
||||
type Request interface {
|
||||
GetTokenCountMeta() *types.TokenCountMeta
|
||||
IsStream(c *gin.Context) bool
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"one-api/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RerankRequest struct {
|
||||
Documents []any `json:"documents"`
|
||||
Query string `json:"query"`
|
||||
@@ -10,6 +17,26 @@ type RerankRequest struct {
|
||||
OverLapTokens int `json:"overlap_tokens,omitempty"`
|
||||
}
|
||||
|
||||
func (r *RerankRequest) IsStream(c *gin.Context) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RerankRequest) GetTokenCountMeta() *types.TokenCountMeta {
|
||||
var texts = make([]string, 0)
|
||||
|
||||
for _, document := range r.Documents {
|
||||
texts = append(texts, fmt.Sprintf("%v", document))
|
||||
}
|
||||
|
||||
if r.Query != "" {
|
||||
texts = append(texts, r.Query)
|
||||
}
|
||||
|
||||
return &types.TokenCountMeta{
|
||||
CombineText: strings.Join(texts, "\n"),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RerankRequest) GetReturnDocuments() bool {
|
||||
if r.ReturnDocuments == nil {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user