luowuyin commited on
Commit
b066e83
·
1 Parent(s): d9f2cee

25:03:01 18:46:30 v0.4.8.8.3

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +0 -4
  2. README.en.md +2 -0
  3. VERSION +1 -0
  4. common/constants.go +1 -1
  5. common/model-ratio.go +114 -107
  6. common/redis.go +34 -1
  7. common/utils.go +19 -0
  8. constant/channel_setting.go +3 -2
  9. constant/context_key.go +5 -0
  10. constant/env.go +15 -18
  11. controller/channel-test.go +11 -2
  12. controller/midjourney.go +1 -1
  13. controller/model.go +7 -0
  14. controller/relay.go +1 -0
  15. controller/task.go +1 -1
  16. controller/topup.go +1 -1
  17. docs/channel/other_setting.md +5 -0
  18. dto/openai_request.go +59 -40
  19. dto/openai_response.go +30 -12
  20. middleware/auth.go +5 -1
  21. middleware/distributor.go +1 -2
  22. middleware/model-rate-limit.go +172 -0
  23. model/log.go +5 -5
  24. model/option.go +53 -3
  25. model/pricing.go +2 -1
  26. model/user.go +33 -33
  27. model/user_cache.go +10 -0
  28. relay/channel/api_request.go +1 -1
  29. relay/channel/aws/adaptor.go +6 -3
  30. relay/channel/aws/constants.go +2 -1
  31. relay/channel/aws/dto.go +2 -0
  32. relay/channel/claude/adaptor.go +2 -0
  33. relay/channel/claude/constants.go +2 -0
  34. relay/channel/claude/dto.go +12 -3
  35. relay/channel/claude/relay-claude.go +51 -11
  36. relay/channel/dify/adaptor.go +28 -2
  37. relay/channel/gemini/adaptor.go +2 -10
  38. relay/channel/gemini/constant.go +8 -0
  39. relay/channel/gemini/relay-gemini.go +24 -35
  40. relay/channel/jina/adaptor.go +1 -1
  41. relay/channel/jina/relay-jina.go +1 -1
  42. relay/channel/ollama/dto.go +16 -15
  43. relay/channel/ollama/relay-ollama.go +1 -0
  44. relay/channel/openai/adaptor.go +4 -1
  45. relay/channel/openai/constant.go +1 -0
  46. relay/channel/openai/relay-openai.go +62 -17
  47. relay/channel/openrouter/adaptor.go +74 -0
  48. relay/channel/openrouter/constant.go +5 -0
  49. relay/channel/vertex/adaptor.go +2 -2
  50. relay/common/relay_info.go +29 -73
.env.example CHANGED
@@ -50,10 +50,6 @@
50
  # CHANNEL_TEST_FREQUENCY=10
51
  # 生成默认token
52
  # GENERATE_DEFAULT_TOKEN=false
53
- # Gemini 安全设置
54
- # GEMINI_SAFETY_SETTING=BLOCK_NONE
55
- # Gemini版本设置
56
- # GEMINI_MODEL_MAP=gemini-1.0-pro:v1
57
  # Cohere 安全设置
58
  # COHERE_SAFETY_SETTING=NONE
59
  # 是否统计图片token
 
50
  # CHANNEL_TEST_FREQUENCY=10
51
  # 生成默认token
52
  # GENERATE_DEFAULT_TOKEN=false
 
 
 
 
53
  # Cohere 安全设置
54
  # COHERE_SAFETY_SETTING=NONE
55
  # 是否统计图片token
README.en.md CHANGED
@@ -63,6 +63,8 @@
63
  - Add suffix `-high` to set high reasoning effort (e.g., `o3-mini-high`)
64
  - Add suffix `-medium` to set medium reasoning effort
65
  - Add suffix `-low` to set low reasoning effort
 
 
66
 
67
  ## Model Support
68
  This version additionally supports:
 
63
  - Add suffix `-high` to set high reasoning effort (e.g., `o3-mini-high`)
64
  - Add suffix `-medium` to set medium reasoning effort
65
  - Add suffix `-low` to set low reasoning effort
66
+ 17. 🔄 Thinking to content option `thinking_to_content` in `Channel->Edit->Channel Extra Settings`, default is `false`, when `true`, the `reasoning_content` of the thinking content will be converted to `<think>` tags and concatenated to the content returned.
67
+ 18. 🔄 Model rate limit, support setting total request limit and successful request limit in `System Settings->Rate Limit Settings`
68
 
69
  ## Model Support
70
  This version additionally supports:
VERSION CHANGED
@@ -0,0 +1 @@
 
 
1
+ v0.4.8.8.3
common/constants.go CHANGED
@@ -276,7 +276,7 @@ var ChannelBaseURLs = []string{
276
  "https://api.cohere.ai", //34
277
  "https://api.minimax.chat", //35
278
  "", //36
279
- "", //37
280
  "https://api.jina.ai", //38
281
  "https://api.cloudflare.com", //39
282
  "https://api.siliconflow.cn", //40
 
276
  "https://api.cohere.ai", //34
277
  "https://api.minimax.chat", //35
278
  "", //36
279
+ "https://api.dify.ai", //37
280
  "https://api.jina.ai", //38
281
  "https://api.cloudflare.com", //39
282
  "https://api.siliconflow.cn", //40
common/model-ratio.go CHANGED
@@ -50,24 +50,26 @@ var defaultModelRatio = map[string]float64{
50
  "gpt-4o-realtime-preview-2024-12-17": 2.5,
51
  "gpt-4o-mini-realtime-preview": 0.3,
52
  "gpt-4o-mini-realtime-preview-2024-12-17": 0.3,
53
- "o1": 7.5,
54
- "o1-2024-12-17": 7.5,
55
- "o1-preview": 7.5,
56
- "o1-preview-2024-09-12": 7.5,
57
- "o1-mini": 0.55,
58
- "o1-mini-2024-09-12": 0.55,
59
- "o3-mini": 0.55,
60
- "o3-mini-2025-01-31": 0.55,
61
- "o3-mini-high": 0.55,
62
- "o3-mini-2025-01-31-high": 0.55,
63
- "o3-mini-low": 0.55,
64
- "o3-mini-2025-01-31-low": 0.55,
65
- "o3-mini-medium": 0.55,
66
- "o3-mini-2025-01-31-medium": 0.55,
67
- "gpt-4o-mini": 0.075,
68
- "gpt-4o-mini-2024-07-18": 0.075,
69
- "gpt-4-turbo": 5, // $0.01 / 1K tokens
70
- "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens
 
 
71
  //"gpt-3.5-turbo-0301": 0.75, //deprecated
72
  "gpt-3.5-turbo": 0.25,
73
  "gpt-3.5-turbo-0613": 0.75,
@@ -83,92 +85,94 @@ var defaultModelRatio = map[string]float64{
83
  "text-curie-001": 1,
84
  //"text-davinci-002": 10,
85
  //"text-davinci-003": 10,
86
- "text-davinci-edit-001": 10,
87
- "code-davinci-edit-001": 10,
88
- "whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens
89
- "tts-1": 7.5, // 1k characters -> $0.015
90
- "tts-1-1106": 7.5, // 1k characters -> $0.015
91
- "tts-1-hd": 15, // 1k characters -> $0.03
92
- "tts-1-hd-1106": 15, // 1k characters -> $0.03
93
- "davinci": 10,
94
- "curie": 10,
95
- "babbage": 10,
96
- "ada": 10,
97
- "text-embedding-3-small": 0.01,
98
- "text-embedding-3-large": 0.065,
99
- "text-embedding-ada-002": 0.05,
100
- "text-search-ada-doc-001": 10,
101
- "text-moderation-stable": 0.1,
102
- "text-moderation-latest": 0.1,
103
- "claude-instant-1": 0.4, // $0.8 / 1M tokens
104
- "claude-2.0": 4, // $8 / 1M tokens
105
- "claude-2.1": 4, // $8 / 1M tokens
106
- "claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens
107
- "claude-3-5-haiku-20241022": 0.5, // $1 / 1M tokens
108
- "claude-3-sonnet-20240229": 1.5, // $3 / 1M tokens
109
- "claude-3-5-sonnet-20240620": 1.5,
110
- "claude-3-5-sonnet-20241022": 1.5,
111
- "claude-3-opus-20240229": 7.5, // $15 / 1M tokens
112
- "ERNIE-4.0-8K": 0.120 * RMB,
113
- "ERNIE-3.5-8K": 0.012 * RMB,
114
- "ERNIE-3.5-8K-0205": 0.024 * RMB,
115
- "ERNIE-3.5-8K-1222": 0.012 * RMB,
116
- "ERNIE-Bot-8K": 0.024 * RMB,
117
- "ERNIE-3.5-4K-0205": 0.012 * RMB,
118
- "ERNIE-Speed-8K": 0.004 * RMB,
119
- "ERNIE-Speed-128K": 0.004 * RMB,
120
- "ERNIE-Lite-8K-0922": 0.008 * RMB,
121
- "ERNIE-Lite-8K-0308": 0.003 * RMB,
122
- "ERNIE-Tiny-8K": 0.001 * RMB,
123
- "BLOOMZ-7B": 0.004 * RMB,
124
- "Embedding-V1": 0.002 * RMB,
125
- "bge-large-zh": 0.002 * RMB,
126
- "bge-large-en": 0.002 * RMB,
127
- "tao-8k": 0.002 * RMB,
128
- "PaLM-2": 1,
129
- "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
130
- "gemini-pro-vision": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
131
- "gemini-1.0-pro-vision-001": 1,
132
- "gemini-1.0-pro-001": 1,
133
- "gemini-1.5-pro-latest": 1.75, // $3.5 / 1M tokens
134
- "gemini-1.5-pro-exp-0827": 1.75, // $3.5 / 1M tokens
135
- "gemini-1.5-flash-latest": 1,
136
- "gemini-1.5-flash-exp-0827": 1,
137
- "gemini-1.0-pro-latest": 1,
138
- "gemini-1.0-pro-vision-latest": 1,
139
- "gemini-ultra": 1,
140
- "chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
141
- "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
142
- "chatglm_std": 0.3572, // ¥0.005 / 1k tokens
143
- "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens
144
- "glm-4": 7.143, // ¥0.1 / 1k tokens
145
- "glm-4v": 0.05 * RMB, // ¥0.05 / 1k tokens
146
- "glm-4-alltools": 0.1 * RMB, // ¥0.1 / 1k tokens
147
- "glm-3-turbo": 0.3572,
148
- "glm-4-plus": 0.05 * RMB,
149
- "glm-4-0520": 0.1 * RMB,
150
- "glm-4-air": 0.001 * RMB,
151
- "glm-4-airx": 0.01 * RMB,
152
- "glm-4-long": 0.001 * RMB,
153
- "glm-4-flash": 0,
154
- "glm-4v-plus": 0.01 * RMB,
155
- "qwen-turbo": 0.8572, // ¥0.012 / 1k tokens
156
- "qwen-plus": 10, // ¥0.14 / 1k tokens
157
- "text-embedding-v1": 0.05, // ¥0.0007 / 1k tokens
158
- "SparkDesk-v1.1": 1.2858, // ¥0.018 / 1k tokens
159
- "SparkDesk-v2.1": 1.2858, // ¥0.018 / 1k tokens
160
- "SparkDesk-v3.1": 1.2858, // ¥0.018 / 1k tokens
161
- "SparkDesk-v3.5": 1.2858, // ¥0.018 / 1k tokens
162
- "SparkDesk-v4.0": 1.2858,
163
- "360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens
164
- "360gpt-turbo": 0.0858, // ¥0.0012 / 1k tokens
165
- "360gpt-turbo-responsibility-8k": 0.8572, // ¥0.012 / 1k tokens
166
- "360gpt-pro": 0.8572, // ¥0.012 / 1k tokens
167
- "360gpt2-pro": 0.8572, // ¥0.012 / 1k tokens
168
- "embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
169
- "embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
170
- "semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
171
- "hunyuan": 7.143, // ¥0.1 / 1k tokens // https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
 
 
172
  // https://platform.lingyiwanwu.com/docs#-计费单元
173
  // 已经按照 7.2 来换算美元价格
174
  "yi-34b-chat-0205": 0.18,
@@ -313,7 +317,7 @@ func UpdateModelRatioByJSONString(jsonStr string) error {
313
  return json.Unmarshal([]byte(jsonStr), &modelRatioMap)
314
  }
315
 
316
- func GetModelRatio(name string) float64 {
317
  GetModelRatioMap()
318
  if strings.HasPrefix(name, "gpt-4-gizmo") {
319
  name = "gpt-4-gizmo-*"
@@ -321,9 +325,9 @@ func GetModelRatio(name string) float64 {
321
  ratio, ok := modelRatioMap[name]
322
  if !ok {
323
  SysError("model ratio not found: " + name)
324
- return 30
325
  }
326
- return ratio
327
  }
328
 
329
  func DefaultModelRatio2JSONString() string {
@@ -385,6 +389,9 @@ func GetCompletionRatio(name string) float64 {
385
  }
386
  return 4
387
  }
 
 
 
388
  if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") {
389
  return 3
390
  }
 
50
  "gpt-4o-realtime-preview-2024-12-17": 2.5,
51
  "gpt-4o-mini-realtime-preview": 0.3,
52
  "gpt-4o-mini-realtime-preview-2024-12-17": 0.3,
53
+ "o1": 7.5,
54
+ "o1-2024-12-17": 7.5,
55
+ "o1-preview": 7.5,
56
+ "o1-preview-2024-09-12": 7.5,
57
+ "o1-mini": 0.55,
58
+ "o1-mini-2024-09-12": 0.55,
59
+ "o3-mini": 0.55,
60
+ "o3-mini-2025-01-31": 0.55,
61
+ "o3-mini-high": 0.55,
62
+ "o3-mini-2025-01-31-high": 0.55,
63
+ "o3-mini-low": 0.55,
64
+ "o3-mini-2025-01-31-low": 0.55,
65
+ "o3-mini-medium": 0.55,
66
+ "o3-mini-2025-01-31-medium": 0.55,
67
+ "gpt-4o-mini": 0.075,
68
+ "gpt-4o-mini-2024-07-18": 0.075,
69
+ "gpt-4-turbo": 5, // $0.01 / 1K tokens
70
+ "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens
71
+ "gpt-4.5-preview": 37.5,
72
+ "gpt-4.5-preview-2025-02-27": 37.5,
73
  //"gpt-3.5-turbo-0301": 0.75, //deprecated
74
  "gpt-3.5-turbo": 0.25,
75
  "gpt-3.5-turbo-0613": 0.75,
 
85
  "text-curie-001": 1,
86
  //"text-davinci-002": 10,
87
  //"text-davinci-003": 10,
88
+ "text-davinci-edit-001": 10,
89
+ "code-davinci-edit-001": 10,
90
+ "whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens
91
+ "tts-1": 7.5, // 1k characters -> $0.015
92
+ "tts-1-1106": 7.5, // 1k characters -> $0.015
93
+ "tts-1-hd": 15, // 1k characters -> $0.03
94
+ "tts-1-hd-1106": 15, // 1k characters -> $0.03
95
+ "davinci": 10,
96
+ "curie": 10,
97
+ "babbage": 10,
98
+ "ada": 10,
99
+ "text-embedding-3-small": 0.01,
100
+ "text-embedding-3-large": 0.065,
101
+ "text-embedding-ada-002": 0.05,
102
+ "text-search-ada-doc-001": 10,
103
+ "text-moderation-stable": 0.1,
104
+ "text-moderation-latest": 0.1,
105
+ "claude-instant-1": 0.4, // $0.8 / 1M tokens
106
+ "claude-2.0": 4, // $8 / 1M tokens
107
+ "claude-2.1": 4, // $8 / 1M tokens
108
+ "claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens
109
+ "claude-3-5-haiku-20241022": 0.5, // $1 / 1M tokens
110
+ "claude-3-sonnet-20240229": 1.5, // $3 / 1M tokens
111
+ "claude-3-5-sonnet-20240620": 1.5,
112
+ "claude-3-5-sonnet-20241022": 1.5,
113
+ "claude-3-7-sonnet-20250219": 1.5,
114
+ "claude-3-7-sonnet-20250219-thinking": 1.5,
115
+ "claude-3-opus-20240229": 7.5, // $15 / 1M tokens
116
+ "ERNIE-4.0-8K": 0.120 * RMB,
117
+ "ERNIE-3.5-8K": 0.012 * RMB,
118
+ "ERNIE-3.5-8K-0205": 0.024 * RMB,
119
+ "ERNIE-3.5-8K-1222": 0.012 * RMB,
120
+ "ERNIE-Bot-8K": 0.024 * RMB,
121
+ "ERNIE-3.5-4K-0205": 0.012 * RMB,
122
+ "ERNIE-Speed-8K": 0.004 * RMB,
123
+ "ERNIE-Speed-128K": 0.004 * RMB,
124
+ "ERNIE-Lite-8K-0922": 0.008 * RMB,
125
+ "ERNIE-Lite-8K-0308": 0.003 * RMB,
126
+ "ERNIE-Tiny-8K": 0.001 * RMB,
127
+ "BLOOMZ-7B": 0.004 * RMB,
128
+ "Embedding-V1": 0.002 * RMB,
129
+ "bge-large-zh": 0.002 * RMB,
130
+ "bge-large-en": 0.002 * RMB,
131
+ "tao-8k": 0.002 * RMB,
132
+ "PaLM-2": 1,
133
+ "gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
134
+ "gemini-pro-vision": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
135
+ "gemini-1.0-pro-vision-001": 1,
136
+ "gemini-1.0-pro-001": 1,
137
+ "gemini-1.5-pro-latest": 1.75, // $3.5 / 1M tokens
138
+ "gemini-1.5-pro-exp-0827": 1.75, // $3.5 / 1M tokens
139
+ "gemini-1.5-flash-latest": 1,
140
+ "gemini-1.5-flash-exp-0827": 1,
141
+ "gemini-1.0-pro-latest": 1,
142
+ "gemini-1.0-pro-vision-latest": 1,
143
+ "gemini-ultra": 1,
144
+ "chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
145
+ "chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
146
+ "chatglm_std": 0.3572, // ¥0.005 / 1k tokens
147
+ "chatglm_lite": 0.1429, // ¥0.002 / 1k tokens
148
+ "glm-4": 7.143, // ¥0.1 / 1k tokens
149
+ "glm-4v": 0.05 * RMB, // ¥0.05 / 1k tokens
150
+ "glm-4-alltools": 0.1 * RMB, // ¥0.1 / 1k tokens
151
+ "glm-3-turbo": 0.3572,
152
+ "glm-4-plus": 0.05 * RMB,
153
+ "glm-4-0520": 0.1 * RMB,
154
+ "glm-4-air": 0.001 * RMB,
155
+ "glm-4-airx": 0.01 * RMB,
156
+ "glm-4-long": 0.001 * RMB,
157
+ "glm-4-flash": 0,
158
+ "glm-4v-plus": 0.01 * RMB,
159
+ "qwen-turbo": 0.8572, // ¥0.012 / 1k tokens
160
+ "qwen-plus": 10, // ¥0.14 / 1k tokens
161
+ "text-embedding-v1": 0.05, // ¥0.0007 / 1k tokens
162
+ "SparkDesk-v1.1": 1.2858, // ¥0.018 / 1k tokens
163
+ "SparkDesk-v2.1": 1.2858, // ¥0.018 / 1k tokens
164
+ "SparkDesk-v3.1": 1.2858, // ¥0.018 / 1k tokens
165
+ "SparkDesk-v3.5": 1.2858, // 0.018 / 1k tokens
166
+ "SparkDesk-v4.0": 1.2858,
167
+ "360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens
168
+ "360gpt-turbo": 0.0858, // ¥0.0012 / 1k tokens
169
+ "360gpt-turbo-responsibility-8k": 0.8572, // ¥0.012 / 1k tokens
170
+ "360gpt-pro": 0.8572, // ¥0.012 / 1k tokens
171
+ "360gpt2-pro": 0.8572, // ¥0.012 / 1k tokens
172
+ "embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
173
+ "embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
174
+ "semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
175
+ "hunyuan": 7.143, // ¥0.1 / 1k tokens // https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
176
  // https://platform.lingyiwanwu.com/docs#-计费单元
177
  // 已经按照 7.2 来换算美元价格
178
  "yi-34b-chat-0205": 0.18,
 
317
  return json.Unmarshal([]byte(jsonStr), &modelRatioMap)
318
  }
319
 
320
+ func GetModelRatio(name string) (float64, bool) {
321
  GetModelRatioMap()
322
  if strings.HasPrefix(name, "gpt-4-gizmo") {
323
  name = "gpt-4-gizmo-*"
 
325
  ratio, ok := modelRatioMap[name]
326
  if !ok {
327
  SysError("model ratio not found: " + name)
328
+ return 37.5, false
329
  }
330
+ return ratio, true
331
  }
332
 
333
  func DefaultModelRatio2JSONString() string {
 
389
  }
390
  return 4
391
  }
392
+ if strings.HasPrefix(name, "gpt-4.5") {
393
+ return 2
394
+ }
395
  if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") {
396
  return 3
397
  }
common/redis.go CHANGED
@@ -32,6 +32,7 @@ func InitRedisClient() (err error) {
32
  if err != nil {
33
  FatalLog("failed to parse Redis connection string: " + err.Error())
34
  }
 
35
  RDB = redis.NewClient(opt)
36
 
37
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -41,6 +42,10 @@ func InitRedisClient() (err error) {
41
  if err != nil {
42
  FatalLog("Redis ping test failed: " + err.Error())
43
  }
 
 
 
 
44
  return err
45
  }
46
 
@@ -53,13 +58,20 @@ func ParseRedisOption() *redis.Options {
53
  }
54
 
55
  func RedisSet(key string, value string, expiration time.Duration) error {
 
 
 
56
  ctx := context.Background()
57
  return RDB.Set(ctx, key, value, expiration).Err()
58
  }
59
 
60
  func RedisGet(key string) (string, error) {
 
 
 
61
  ctx := context.Background()
62
- return RDB.Get(ctx, key).Result()
 
63
  }
64
 
65
  //func RedisExpire(key string, expiration time.Duration) error {
@@ -73,16 +85,25 @@ func RedisGet(key string) (string, error) {
73
  //}
74
 
75
  func RedisDel(key string) error {
 
 
 
76
  ctx := context.Background()
77
  return RDB.Del(ctx, key).Err()
78
  }
79
 
80
  func RedisHDelObj(key string) error {
 
 
 
81
  ctx := context.Background()
82
  return RDB.HDel(ctx, key).Err()
83
  }
84
 
85
  func RedisHSetObj(key string, obj interface{}, expiration time.Duration) error {
 
 
 
86
  ctx := context.Background()
87
 
88
  data := make(map[string]interface{})
@@ -130,6 +151,9 @@ func RedisHSetObj(key string, obj interface{}, expiration time.Duration) error {
130
  }
131
 
132
  func RedisHGetObj(key string, obj interface{}) error {
 
 
 
133
  ctx := context.Background()
134
 
135
  result, err := RDB.HGetAll(ctx, key).Result()
@@ -208,6 +232,9 @@ func RedisHGetObj(key string, obj interface{}) error {
208
 
209
  // RedisIncr Add this function to handle atomic increments
210
  func RedisIncr(key string, delta int64) error {
 
 
 
211
  // 检查键的剩余生存时间
212
  ttlCmd := RDB.TTL(context.Background(), key)
213
  ttl, err := ttlCmd.Result()
@@ -238,6 +265,9 @@ func RedisIncr(key string, delta int64) error {
238
  }
239
 
240
  func RedisHIncrBy(key, field string, delta int64) error {
 
 
 
241
  ttlCmd := RDB.TTL(context.Background(), key)
242
  ttl, err := ttlCmd.Result()
243
  if err != nil && !errors.Is(err, redis.Nil) {
@@ -262,6 +292,9 @@ func RedisHIncrBy(key, field string, delta int64) error {
262
  }
263
 
264
  func RedisHSetField(key, field string, value interface{}) error {
 
 
 
265
  ttlCmd := RDB.TTL(context.Background(), key)
266
  ttl, err := ttlCmd.Result()
267
  if err != nil && !errors.Is(err, redis.Nil) {
 
32
  if err != nil {
33
  FatalLog("failed to parse Redis connection string: " + err.Error())
34
  }
35
+ opt.PoolSize = GetEnvOrDefault("REDIS_POOL_SIZE", 10)
36
  RDB = redis.NewClient(opt)
37
 
38
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 
42
  if err != nil {
43
  FatalLog("Redis ping test failed: " + err.Error())
44
  }
45
+ if DebugEnabled {
46
+ SysLog(fmt.Sprintf("Redis connected to %s", opt.Addr))
47
+ SysLog(fmt.Sprintf("Redis database: %d", opt.DB))
48
+ }
49
  return err
50
  }
51
 
 
58
  }
59
 
60
  func RedisSet(key string, value string, expiration time.Duration) error {
61
+ if DebugEnabled {
62
+ SysLog(fmt.Sprintf("Redis SET: key=%s, value=%s, expiration=%v", key, value, expiration))
63
+ }
64
  ctx := context.Background()
65
  return RDB.Set(ctx, key, value, expiration).Err()
66
  }
67
 
68
  func RedisGet(key string) (string, error) {
69
+ if DebugEnabled {
70
+ SysLog(fmt.Sprintf("Redis GET: key=%s", key))
71
+ }
72
  ctx := context.Background()
73
+ val, err := RDB.Get(ctx, key).Result()
74
+ return val, err
75
  }
76
 
77
  //func RedisExpire(key string, expiration time.Duration) error {
 
85
  //}
86
 
87
  func RedisDel(key string) error {
88
+ if DebugEnabled {
89
+ SysLog(fmt.Sprintf("Redis DEL: key=%s", key))
90
+ }
91
  ctx := context.Background()
92
  return RDB.Del(ctx, key).Err()
93
  }
94
 
95
  func RedisHDelObj(key string) error {
96
+ if DebugEnabled {
97
+ SysLog(fmt.Sprintf("Redis HDEL: key=%s", key))
98
+ }
99
  ctx := context.Background()
100
  return RDB.HDel(ctx, key).Err()
101
  }
102
 
103
  func RedisHSetObj(key string, obj interface{}, expiration time.Duration) error {
104
+ if DebugEnabled {
105
+ SysLog(fmt.Sprintf("Redis HSET: key=%s, obj=%+v, expiration=%v", key, obj, expiration))
106
+ }
107
  ctx := context.Background()
108
 
109
  data := make(map[string]interface{})
 
151
  }
152
 
153
  func RedisHGetObj(key string, obj interface{}) error {
154
+ if DebugEnabled {
155
+ SysLog(fmt.Sprintf("Redis HGETALL: key=%s", key))
156
+ }
157
  ctx := context.Background()
158
 
159
  result, err := RDB.HGetAll(ctx, key).Result()
 
232
 
233
  // RedisIncr Add this function to handle atomic increments
234
  func RedisIncr(key string, delta int64) error {
235
+ if DebugEnabled {
236
+ SysLog(fmt.Sprintf("Redis INCR: key=%s, delta=%d", key, delta))
237
+ }
238
  // 检查键的剩余生存时间
239
  ttlCmd := RDB.TTL(context.Background(), key)
240
  ttl, err := ttlCmd.Result()
 
265
  }
266
 
267
  func RedisHIncrBy(key, field string, delta int64) error {
268
+ if DebugEnabled {
269
+ SysLog(fmt.Sprintf("Redis HINCRBY: key=%s, field=%s, delta=%d", key, field, delta))
270
+ }
271
  ttlCmd := RDB.TTL(context.Background(), key)
272
  ttl, err := ttlCmd.Result()
273
  if err != nil && !errors.Is(err, redis.Nil) {
 
292
  }
293
 
294
  func RedisHSetField(key, field string, value interface{}) error {
295
+ if DebugEnabled {
296
+ SysLog(fmt.Sprintf("Redis HSET field: key=%s, field=%s, value=%v", key, field, value))
297
+ }
298
  ttlCmd := RDB.TTL(context.Background(), key)
299
  ttl, err := ttlCmd.Result()
300
  if err != nil && !errors.Is(err, redis.Nil) {
common/utils.go CHANGED
@@ -5,6 +5,7 @@ import (
5
  "context"
6
  crand "crypto/rand"
7
  "encoding/base64"
 
8
  "fmt"
9
  "github.com/pkg/errors"
10
  "html/template"
@@ -213,6 +214,24 @@ func RandomSleep() {
213
  time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
214
  }
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  // SaveTmpFile saves data to a temporary file. The filename would be apppended with a random string.
217
  func SaveTmpFile(filename string, data io.Reader) (string, error) {
218
  f, err := os.CreateTemp(os.TempDir(), filename)
 
5
  "context"
6
  crand "crypto/rand"
7
  "encoding/base64"
8
+ "encoding/json"
9
  "fmt"
10
  "github.com/pkg/errors"
11
  "html/template"
 
214
  time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
215
  }
216
 
217
+ func GetPointer[T any](v T) *T {
218
+ return &v
219
+ }
220
+
221
+ func Any2Type[T any](data any) (T, error) {
222
+ var zero T
223
+ bytes, err := json.Marshal(data)
224
+ if err != nil {
225
+ return zero, err
226
+ }
227
+ var res T
228
+ err = json.Unmarshal(bytes, &res)
229
+ if err != nil {
230
+ return zero, err
231
+ }
232
+ return res, nil
233
+ }
234
+
235
  // SaveTmpFile saves data to a temporary file. The filename would be apppended with a random string.
236
  func SaveTmpFile(filename string, data io.Reader) (string, error) {
237
  f, err := os.CreateTemp(os.TempDir(), filename)
constant/channel_setting.go CHANGED
@@ -1,6 +1,7 @@
1
  package constant
2
 
3
  var (
4
- ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式
5
- ChanelSettingProxy = "proxy" // Proxy 代理
 
6
  )
 
1
  package constant
2
 
3
  var (
4
+ ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式
5
+ ChanelSettingProxy = "proxy" // Proxy 代理
6
+ ChannelSettingThinkingToContent = "thinking_to_content" // ThinkingToContent
7
  )
constant/context_key.go CHANGED
@@ -2,4 +2,9 @@ package constant
2
 
3
  const (
4
  ContextKeyRequestStartTime = "request_start_time"
 
 
 
 
 
5
  )
 
2
 
3
  const (
4
  ContextKeyRequestStartTime = "request_start_time"
5
+ ContextKeyUserSetting = "user_setting"
6
+ ContextKeyUserQuota = "user_quota"
7
+ ContextKeyUserStatus = "user_status"
8
+ ContextKeyUserEmail = "user_email"
9
+ ContextKeyUserGroup = "user_group"
10
  )
constant/env.go CHANGED
@@ -1,10 +1,7 @@
1
  package constant
2
 
3
  import (
4
- "fmt"
5
  "one-api/common"
6
- "os"
7
- "strings"
8
  )
9
 
10
  var StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
@@ -23,9 +20,9 @@ var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
23
 
24
  var AzureDefaultAPIVersion = common.GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2024-12-01-preview")
25
 
26
- var GeminiModelMap = map[string]string{
27
- "gemini-1.0-pro": "v1",
28
- }
29
 
30
  var GeminiVisionMaxImageNum = common.GetEnvOrDefault("GEMINI_VISION_MAX_IMAGE_NUM", 16)
31
 
@@ -33,18 +30,18 @@ var NotifyLimitCount = common.GetEnvOrDefault("NOTIFY_LIMIT_COUNT", 2)
33
  var NotificationLimitDurationMinute = common.GetEnvOrDefault("NOTIFICATION_LIMIT_DURATION_MINUTE", 10)
34
 
35
  func InitEnv() {
36
- modelVersionMapStr := strings.TrimSpace(os.Getenv("GEMINI_MODEL_MAP"))
37
- if modelVersionMapStr == "" {
38
- return
39
- }
40
- for _, pair := range strings.Split(modelVersionMapStr, ",") {
41
- parts := strings.Split(pair, ":")
42
- if len(parts) == 2 {
43
- GeminiModelMap[parts[0]] = parts[1]
44
- } else {
45
- common.SysError(fmt.Sprintf("invalid model version map: %s", pair))
46
- }
47
- }
48
  }
49
 
50
  // GenerateDefaultToken 是否生成初始令牌,默认关闭。
 
1
  package constant
2
 
3
  import (
 
4
  "one-api/common"
 
 
5
  )
6
 
7
  var StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
 
20
 
21
  var AzureDefaultAPIVersion = common.GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2024-12-01-preview")
22
 
23
+ //var GeminiModelMap = map[string]string{
24
+ // "gemini-1.0-pro": "v1",
25
+ //}
26
 
27
  var GeminiVisionMaxImageNum = common.GetEnvOrDefault("GEMINI_VISION_MAX_IMAGE_NUM", 16)
28
 
 
30
  var NotificationLimitDurationMinute = common.GetEnvOrDefault("NOTIFICATION_LIMIT_DURATION_MINUTE", 10)
31
 
32
  func InitEnv() {
33
+ //modelVersionMapStr := strings.TrimSpace(os.Getenv("GEMINI_MODEL_MAP"))
34
+ //if modelVersionMapStr == "" {
35
+ // return
36
+ //}
37
+ //for _, pair := range strings.Split(modelVersionMapStr, ",") {
38
+ // parts := strings.Split(pair, ":")
39
+ // if len(parts) == 2 {
40
+ // GeminiModelMap[parts[0]] = parts[1]
41
+ // } else {
42
+ // common.SysError(fmt.Sprintf("invalid model version map: %s", pair))
43
+ // }
44
+ //}
45
  }
46
 
47
  // GenerateDefaultToken 是否生成初始令牌,默认关闭。
controller/channel-test.go CHANGED
@@ -48,7 +48,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
48
  if strings.Contains(strings.ToLower(testModel), "embedding") ||
49
  strings.HasPrefix(testModel, "m3e") || // m3e 系列模型
50
  strings.Contains(testModel, "bge-") || // bge 系列模型
51
- testModel == "text-embedding-v1" ||
52
  channel.Type == common.ChannelTypeMokaAI { // 其他 embedding 模型
53
  requestPath = "/v1/embeddings" // 修改请求路径
54
  }
@@ -84,6 +84,12 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
84
  }
85
  }
86
 
 
 
 
 
 
 
87
  c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
88
  c.Request.Header.Set("Content-Type", "application/json")
89
  c.Set("channel", channel.Type)
@@ -140,7 +146,10 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
140
  return err, nil
141
  }
142
  modelPrice, usePrice := common.GetModelPrice(testModel, false)
143
- modelRatio := common.GetModelRatio(testModel)
 
 
 
144
  completionRatio := common.GetCompletionRatio(testModel)
145
  ratio := modelRatio
146
  quota := 0
 
48
  if strings.Contains(strings.ToLower(testModel), "embedding") ||
49
  strings.HasPrefix(testModel, "m3e") || // m3e 系列模型
50
  strings.Contains(testModel, "bge-") || // bge 系列模型
51
+ strings.Contains(testModel, "embed") ||
52
  channel.Type == common.ChannelTypeMokaAI { // 其他 embedding 模型
53
  requestPath = "/v1/embeddings" // 修改请求路径
54
  }
 
84
  }
85
  }
86
 
87
+ cache, err := model.GetUserCache(1)
88
+ if err != nil {
89
+ return err, nil
90
+ }
91
+ cache.WriteContext(c)
92
+
93
  c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
94
  c.Request.Header.Set("Content-Type", "application/json")
95
  c.Set("channel", channel.Type)
 
146
  return err, nil
147
  }
148
  modelPrice, usePrice := common.GetModelPrice(testModel, false)
149
+ modelRatio, success := common.GetModelRatio(testModel)
150
+ if !usePrice && !success {
151
+ return fmt.Errorf("模型 %s 倍率和价格均未设置", testModel), nil
152
+ }
153
  completionRatio := common.GetCompletionRatio(testModel)
154
  ratio := modelRatio
155
  quota := 0
controller/midjourney.go CHANGED
@@ -159,7 +159,7 @@ func UpdateMidjourneyTaskBulk() {
159
  common.LogError(ctx, "UpdateMidjourneyTask task error: "+err.Error())
160
  } else {
161
  if shouldReturnQuota {
162
- err = model.IncreaseUserQuota(task.UserId, task.Quota)
163
  if err != nil {
164
  common.LogError(ctx, "fail to increase user quota: "+err.Error())
165
  }
 
159
  common.LogError(ctx, "UpdateMidjourneyTask task error: "+err.Error())
160
  } else {
161
  if shouldReturnQuota {
162
+ err = model.IncreaseUserQuota(task.UserId, task.Quota, false)
163
  if err != nil {
164
  common.LogError(ctx, "fail to increase user quota: "+err.Error())
165
  }
controller/model.go CHANGED
@@ -216,6 +216,13 @@ func DashboardListModels(c *gin.Context) {
216
  })
217
  }
218
 
 
 
 
 
 
 
 
219
  func RetrieveModel(c *gin.Context) {
220
  modelId := c.Param("model")
221
  if aiModel, ok := openAIModelsMap[modelId]; ok {
 
216
  })
217
  }
218
 
219
+ func EnabledListModels(c *gin.Context) {
220
+ c.JSON(200, gin.H{
221
+ "success": true,
222
+ "data": model.GetEnabledModels(),
223
+ })
224
+ }
225
+
226
  func RetrieveModel(c *gin.Context) {
227
  modelId := c.Param("model")
228
  if aiModel, ok := openAIModelsMap[modelId]; ok {
controller/relay.go CHANGED
@@ -85,6 +85,7 @@ func Relay(c *gin.Context) {
85
 
86
  if openaiErr != nil {
87
  if openaiErr.StatusCode == http.StatusTooManyRequests {
 
88
  openaiErr.Error.Message = "当前分组上游负载已饱和,请稍后再试"
89
  }
90
  openaiErr.Error.Message = common.MessageWithRequestId(openaiErr.Error.Message, requestId)
 
85
 
86
  if openaiErr != nil {
87
  if openaiErr.StatusCode == http.StatusTooManyRequests {
88
+ common.LogError(c, fmt.Sprintf("origin 429 error: %s", openaiErr.Error.Message))
89
  openaiErr.Error.Message = "当前分组上游负载已饱和,请稍后再试"
90
  }
91
  openaiErr.Error.Message = common.MessageWithRequestId(openaiErr.Error.Message, requestId)
controller/task.go CHANGED
@@ -159,7 +159,7 @@ func updateSunoTaskAll(ctx context.Context, channelId int, taskIds []string, tas
159
  } else {
160
  quota := task.Quota
161
  if quota != 0 {
162
- err = model.IncreaseUserQuota(task.UserId, quota)
163
  if err != nil {
164
  common.LogError(ctx, "fail to increase user quota: "+err.Error())
165
  }
 
159
  } else {
160
  quota := task.Quota
161
  if quota != 0 {
162
+ err = model.IncreaseUserQuota(task.UserId, quota, false)
163
  if err != nil {
164
  common.LogError(ctx, "fail to increase user quota: "+err.Error())
165
  }
controller/topup.go CHANGED
@@ -210,7 +210,7 @@ func EpayNotify(c *gin.Context) {
210
  }
211
  //user, _ := model.GetUserById(topUp.UserId, false)
212
  //user.Quota += topUp.Amount * 500000
213
- err = model.IncreaseUserQuota(topUp.UserId, topUp.Amount*int(common.QuotaPerUnit))
214
  if err != nil {
215
  log.Printf("易支付回调更新用户失败: %v", topUp)
216
  return
 
210
  }
211
  //user, _ := model.GetUserById(topUp.UserId, false)
212
  //user.Quota += topUp.Amount * 500000
213
+ err = model.IncreaseUserQuota(topUp.UserId, topUp.Amount*int(common.QuotaPerUnit), true)
214
  if err != nil {
215
  log.Printf("易支付回调更新用户失败: %v", topUp)
216
  return
docs/channel/other_setting.md CHANGED
@@ -10,6 +10,10 @@
10
  - 用于配置网络代理
11
  - 类型为字符串,填写代理地址(例如 socks5 协议的代理地址)
12
 
 
 
 
 
13
  --------------------------------------------------------------
14
 
15
  ## JSON 格式示例
@@ -19,6 +23,7 @@
19
  ```json
20
  {
21
  "force_format": true,
 
22
  "proxy": "socks5://xxxxxxx"
23
  }
24
  ```
 
10
  - 用于配置网络代理
11
  - 类型为字符串,填写代理地址(例如 socks5 协议的代理地址)
12
 
13
+ 3. thinking_to_content
14
+ - 用于标识是否将思考内容`reasoning_conetnt`转换为`<think>`标签拼接到内容中返回
15
+ - 类型为布尔值,设置为 true 时启用思考内容转换
16
+
17
  --------------------------------------------------------------
18
 
19
  ## JSON 格式示例
 
23
  ```json
24
  {
25
  "force_format": true,
26
+ "thinking_to_content": true,
27
  "proxy": "socks5://xxxxxxx"
28
  }
29
  ```
dto/openai_request.go CHANGED
@@ -1,6 +1,9 @@
1
  package dto
2
 
3
- import "encoding/json"
 
 
 
4
 
5
  type ResponseFormat struct {
6
  Type string `json:"type,omitempty"`
@@ -15,49 +18,52 @@ type FormatJsonSchema struct {
15
  }
16
 
17
  type GeneralOpenAIRequest struct {
18
- Model string `json:"model,omitempty"`
19
- Messages []Message `json:"messages,omitempty"`
20
- Prompt any `json:"prompt,omitempty"`
21
- Prefix any `json:"prefix,omitempty"`
22
- Suffix any `json:"suffix,omitempty"`
23
- Stream bool `json:"stream,omitempty"`
24
- StreamOptions *StreamOptions `json:"stream_options,omitempty"`
25
- MaxTokens uint `json:"max_tokens,omitempty"`
26
- MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
27
- ReasoningEffort string `json:"reasoning_effort,omitempty"`
28
- Temperature *float64 `json:"temperature,omitempty"`
29
- TopP float64 `json:"top_p,omitempty"`
30
- TopK int `json:"top_k,omitempty"`
31
- Stop any `json:"stop,omitempty"`
32
- N int `json:"n,omitempty"`
33
- Input any `json:"input,omitempty"`
34
- Instruction string `json:"instruction,omitempty"`
35
- Size string `json:"size,omitempty"`
36
- Functions any `json:"functions,omitempty"`
37
- FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
38
- PresencePenalty float64 `json:"presence_penalty,omitempty"`
39
- ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
40
- EncodingFormat any `json:"encoding_format,omitempty"`
41
- Seed float64 `json:"seed,omitempty"`
42
- Tools []ToolCall `json:"tools,omitempty"`
43
- ToolChoice any `json:"tool_choice,omitempty"`
44
- User string `json:"user,omitempty"`
45
- LogProbs bool `json:"logprobs,omitempty"`
46
- TopLogProbs int `json:"top_logprobs,omitempty"`
47
- Dimensions int `json:"dimensions,omitempty"`
48
- Modalities any `json:"modalities,omitempty"`
49
- Audio any `json:"audio,omitempty"`
 
50
  }
51
 
52
- type OpenAITools struct {
53
- Type string `json:"type"`
54
- Function OpenAIFunction `json:"function"`
 
55
  }
56
 
57
- type OpenAIFunction struct {
58
  Description string `json:"description,omitempty"`
59
  Name string `json:"name"`
60
  Parameters any `json:"parameters,omitempty"`
 
61
  }
62
 
63
  type StreamOptions struct {
@@ -133,11 +139,11 @@ func (m *Message) SetPrefix(prefix bool) {
133
  m.Prefix = &prefix
134
  }
135
 
136
- func (m *Message) ParseToolCalls() []ToolCall {
137
  if m.ToolCalls == nil {
138
  return nil
139
  }
140
- var toolCalls []ToolCall
141
  if err := json.Unmarshal(m.ToolCalls, &toolCalls); err == nil {
142
  return toolCalls
143
  }
@@ -153,11 +159,24 @@ func (m *Message) StringContent() string {
153
  if m.parsedStringContent != nil {
154
  return *m.parsedStringContent
155
  }
 
156
  var stringContent string
157
  if err := json.Unmarshal(m.Content, &stringContent); err == nil {
 
158
  return stringContent
159
  }
160
- return string(m.Content)
 
 
 
 
 
 
 
 
 
 
 
161
  }
162
 
163
  func (m *Message) SetStringContent(content string) {
 
1
  package dto
2
 
3
+ import (
4
+ "encoding/json"
5
+ "strings"
6
+ )
7
 
8
  type ResponseFormat struct {
9
  Type string `json:"type,omitempty"`
 
18
  }
19
 
20
  type GeneralOpenAIRequest struct {
21
+ Model string `json:"model,omitempty"`
22
+ Messages []Message `json:"messages,omitempty"`
23
+ Prompt any `json:"prompt,omitempty"`
24
+ Prefix any `json:"prefix,omitempty"`
25
+ Suffix any `json:"suffix,omitempty"`
26
+ Stream bool `json:"stream,omitempty"`
27
+ StreamOptions *StreamOptions `json:"stream_options,omitempty"`
28
+ MaxTokens uint `json:"max_tokens,omitempty"`
29
+ MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
30
+ ReasoningEffort string `json:"reasoning_effort,omitempty"`
31
+ Temperature *float64 `json:"temperature,omitempty"`
32
+ TopP float64 `json:"top_p,omitempty"`
33
+ TopK int `json:"top_k,omitempty"`
34
+ Stop any `json:"stop,omitempty"`
35
+ N int `json:"n,omitempty"`
36
+ Input any `json:"input,omitempty"`
37
+ Instruction string `json:"instruction,omitempty"`
38
+ Size string `json:"size,omitempty"`
39
+ Functions any `json:"functions,omitempty"`
40
+ FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
41
+ PresencePenalty float64 `json:"presence_penalty,omitempty"`
42
+ ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
43
+ EncodingFormat any `json:"encoding_format,omitempty"`
44
+ Seed float64 `json:"seed,omitempty"`
45
+ Tools []ToolCallRequest `json:"tools,omitempty"`
46
+ ToolChoice any `json:"tool_choice,omitempty"`
47
+ User string `json:"user,omitempty"`
48
+ LogProbs bool `json:"logprobs,omitempty"`
49
+ TopLogProbs int `json:"top_logprobs,omitempty"`
50
+ Dimensions int `json:"dimensions,omitempty"`
51
+ Modalities any `json:"modalities,omitempty"`
52
+ Audio any `json:"audio,omitempty"`
53
+ ExtraBody any `json:"extra_body,omitempty"`
54
  }
55
 
56
+ type ToolCallRequest struct {
57
+ ID string `json:"id,omitempty"`
58
+ Type string `json:"type"`
59
+ Function FunctionRequest `json:"function"`
60
  }
61
 
62
+ type FunctionRequest struct {
63
  Description string `json:"description,omitempty"`
64
  Name string `json:"name"`
65
  Parameters any `json:"parameters,omitempty"`
66
+ Arguments string `json:"arguments,omitempty"`
67
  }
68
 
69
  type StreamOptions struct {
 
139
  m.Prefix = &prefix
140
  }
141
 
142
+ func (m *Message) ParseToolCalls() []ToolCallRequest {
143
  if m.ToolCalls == nil {
144
  return nil
145
  }
146
+ var toolCalls []ToolCallRequest
147
  if err := json.Unmarshal(m.ToolCalls, &toolCalls); err == nil {
148
  return toolCalls
149
  }
 
159
  if m.parsedStringContent != nil {
160
  return *m.parsedStringContent
161
  }
162
+
163
  var stringContent string
164
  if err := json.Unmarshal(m.Content, &stringContent); err == nil {
165
+ m.parsedStringContent = &stringContent
166
  return stringContent
167
  }
168
+
169
+ contentStr := new(strings.Builder)
170
+ arrayContent := m.ParseContent()
171
+ for _, content := range arrayContent {
172
+ if content.Type == ContentTypeText {
173
+ contentStr.WriteString(content.Text)
174
+ }
175
+ }
176
+ stringContent = contentStr.String()
177
+ m.parsedStringContent = &stringContent
178
+
179
+ return stringContent
180
  }
181
 
182
  func (m *Message) SetStringContent(content string) {
dto/openai_response.go CHANGED
@@ -62,10 +62,10 @@ type ChatCompletionsStreamResponseChoice struct {
62
  }
63
 
64
  type ChatCompletionsStreamResponseChoiceDelta struct {
65
- Content *string `json:"content,omitempty"`
66
- ReasoningContent *string `json:"reasoning_content,omitempty"`
67
- Role string `json:"role,omitempty"`
68
- ToolCalls []ToolCall `json:"tool_calls,omitempty"`
69
  }
70
 
71
  func (c *ChatCompletionsStreamResponseChoiceDelta) SetContentString(s string) {
@@ -86,24 +86,28 @@ func (c *ChatCompletionsStreamResponseChoiceDelta) GetReasoningContent() string
86
  return *c.ReasoningContent
87
  }
88
 
89
- type ToolCall struct {
 
 
 
 
90
  // Index is not nil only in chat completion chunk object
91
- Index *int `json:"index,omitempty"`
92
- ID string `json:"id,omitempty"`
93
- Type any `json:"type"`
94
- Function FunctionCall `json:"function"`
95
  }
96
 
97
- func (c *ToolCall) SetIndex(i int) {
98
  c.Index = &i
99
  }
100
 
101
- type FunctionCall struct {
102
  Description string `json:"description,omitempty"`
103
  Name string `json:"name,omitempty"`
104
  // call function with arguments in JSON format
105
  Parameters any `json:"parameters,omitempty"` // request
106
- Arguments string `json:"arguments,omitempty"`
107
  }
108
 
109
  type ChatCompletionsStreamResponse struct {
@@ -116,6 +120,20 @@ type ChatCompletionsStreamResponse struct {
116
  Usage *Usage `json:"usage"`
117
  }
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  func (c *ChatCompletionsStreamResponse) GetSystemFingerprint() string {
120
  if c.SystemFingerprint == nil {
121
  return ""
 
62
  }
63
 
64
  type ChatCompletionsStreamResponseChoiceDelta struct {
65
+ Content *string `json:"content,omitempty"`
66
+ ReasoningContent *string `json:"reasoning_content,omitempty"`
67
+ Role string `json:"role,omitempty"`
68
+ ToolCalls []ToolCallResponse `json:"tool_calls,omitempty"`
69
  }
70
 
71
  func (c *ChatCompletionsStreamResponseChoiceDelta) SetContentString(s string) {
 
86
  return *c.ReasoningContent
87
  }
88
 
89
+ func (c *ChatCompletionsStreamResponseChoiceDelta) SetReasoningContent(s string) {
90
+ c.ReasoningContent = &s
91
+ }
92
+
93
+ type ToolCallResponse struct {
94
  // Index is not nil only in chat completion chunk object
95
+ Index *int `json:"index,omitempty"`
96
+ ID string `json:"id,omitempty"`
97
+ Type any `json:"type"`
98
+ Function FunctionResponse `json:"function"`
99
  }
100
 
101
+ func (c *ToolCallResponse) SetIndex(i int) {
102
  c.Index = &i
103
  }
104
 
105
+ type FunctionResponse struct {
106
  Description string `json:"description,omitempty"`
107
  Name string `json:"name,omitempty"`
108
  // call function with arguments in JSON format
109
  Parameters any `json:"parameters,omitempty"` // request
110
+ Arguments string `json:"arguments"` // response
111
  }
112
 
113
  type ChatCompletionsStreamResponse struct {
 
120
  Usage *Usage `json:"usage"`
121
  }
122
 
123
+ func (c *ChatCompletionsStreamResponse) Copy() *ChatCompletionsStreamResponse {
124
+ choices := make([]ChatCompletionsStreamResponseChoice, len(c.Choices))
125
+ copy(choices, c.Choices)
126
+ return &ChatCompletionsStreamResponse{
127
+ Id: c.Id,
128
+ Object: c.Object,
129
+ Created: c.Created,
130
+ Model: c.Model,
131
+ SystemFingerprint: c.SystemFingerprint,
132
+ Choices: choices,
133
+ Usage: c.Usage,
134
+ }
135
+ }
136
+
137
  func (c *ChatCompletionsStreamResponse) GetSystemFingerprint() string {
138
  if c.SystemFingerprint == nil {
139
  return ""
middleware/auth.go CHANGED
@@ -199,15 +199,19 @@ func TokenAuth() func(c *gin.Context) {
199
  abortWithOpenAiMessage(c, http.StatusUnauthorized, err.Error())
200
  return
201
  }
202
- userEnabled, err := model.IsUserEnabled(token.UserId, false)
203
  if err != nil {
204
  abortWithOpenAiMessage(c, http.StatusInternalServerError, err.Error())
205
  return
206
  }
 
207
  if !userEnabled {
208
  abortWithOpenAiMessage(c, http.StatusForbidden, "用户已被封禁")
209
  return
210
  }
 
 
 
211
  c.Set("id", token.UserId)
212
  c.Set("token_id", token.Id)
213
  c.Set("token_key", token.Key)
 
199
  abortWithOpenAiMessage(c, http.StatusUnauthorized, err.Error())
200
  return
201
  }
202
+ userCache, err := model.GetUserCache(token.UserId)
203
  if err != nil {
204
  abortWithOpenAiMessage(c, http.StatusInternalServerError, err.Error())
205
  return
206
  }
207
+ userEnabled := userCache.Status == common.UserStatusEnabled
208
  if !userEnabled {
209
  abortWithOpenAiMessage(c, http.StatusForbidden, "用户已被封禁")
210
  return
211
  }
212
+
213
+ userCache.WriteContext(c)
214
+
215
  c.Set("id", token.UserId)
216
  c.Set("token_id", token.Id)
217
  c.Set("token_key", token.Key)
middleware/distributor.go CHANGED
@@ -32,7 +32,6 @@ func Distribute() func(c *gin.Context) {
32
  return
33
  }
34
  }
35
- userId := c.GetInt("id")
36
  var channel *model.Channel
37
  channelId, ok := c.Get("specific_channel_id")
38
  modelRequest, shouldSelectChannel, err := getModelRequest(c)
@@ -40,7 +39,7 @@ func Distribute() func(c *gin.Context) {
40
  abortWithOpenAiMessage(c, http.StatusBadRequest, "Invalid request, "+err.Error())
41
  return
42
  }
43
- userGroup, _ := model.GetUserGroup(userId, false)
44
  tokenGroup := c.GetString("token_group")
45
  if tokenGroup != "" {
46
  // check common.UserUsableGroups[userGroup]
 
32
  return
33
  }
34
  }
 
35
  var channel *model.Channel
36
  channelId, ok := c.Get("specific_channel_id")
37
  modelRequest, shouldSelectChannel, err := getModelRequest(c)
 
39
  abortWithOpenAiMessage(c, http.StatusBadRequest, "Invalid request, "+err.Error())
40
  return
41
  }
42
+ userGroup := c.GetString(constant.ContextKeyUserGroup)
43
  tokenGroup := c.GetString("token_group")
44
  if tokenGroup != "" {
45
  // check common.UserUsableGroups[userGroup]
middleware/model-rate-limit.go ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package middleware
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "net/http"
7
+ "one-api/common"
8
+ "one-api/setting"
9
+ "strconv"
10
+ "time"
11
+
12
+ "github.com/gin-gonic/gin"
13
+ "github.com/go-redis/redis/v8"
14
+ )
15
+
16
+ const (
17
+ ModelRequestRateLimitCountMark = "MRRL"
18
+ ModelRequestRateLimitSuccessCountMark = "MRRLS"
19
+ )
20
+
21
+ // 检查Redis中的请求限制
22
+ func checkRedisRateLimit(ctx context.Context, rdb *redis.Client, key string, maxCount int, duration int64) (bool, error) {
23
+ // 如果maxCount为0,表示不限制
24
+ if maxCount == 0 {
25
+ return true, nil
26
+ }
27
+
28
+ // 获取当前计数
29
+ length, err := rdb.LLen(ctx, key).Result()
30
+ if err != nil {
31
+ return false, err
32
+ }
33
+
34
+ // 如果未达到限制,允许请求
35
+ if length < int64(maxCount) {
36
+ return true, nil
37
+ }
38
+
39
+ // 检查时间窗口
40
+ oldTimeStr, _ := rdb.LIndex(ctx, key, -1).Result()
41
+ oldTime, err := time.Parse(timeFormat, oldTimeStr)
42
+ if err != nil {
43
+ return false, err
44
+ }
45
+
46
+ nowTimeStr := time.Now().Format(timeFormat)
47
+ nowTime, err := time.Parse(timeFormat, nowTimeStr)
48
+ if err != nil {
49
+ return false, err
50
+ }
51
+ // 如果在时间窗口内已达到限制,拒绝请求
52
+ subTime := nowTime.Sub(oldTime).Seconds()
53
+ if int64(subTime) < duration {
54
+ rdb.Expire(ctx, key, common.RateLimitKeyExpirationDuration)
55
+ return false, nil
56
+ }
57
+
58
+ return true, nil
59
+ }
60
+
61
+ // 记录Redis请求
62
+ func recordRedisRequest(ctx context.Context, rdb *redis.Client, key string, maxCount int) {
63
+ // 如果maxCount为0,不记录请求
64
+ if maxCount == 0 {
65
+ return
66
+ }
67
+
68
+ now := time.Now().Format(timeFormat)
69
+ rdb.LPush(ctx, key, now)
70
+ rdb.LTrim(ctx, key, 0, int64(maxCount-1))
71
+ rdb.Expire(ctx, key, common.RateLimitKeyExpirationDuration)
72
+ }
73
+
74
+ // Redis限流处理器
75
+ func redisRateLimitHandler(duration int64, totalMaxCount, successMaxCount int) gin.HandlerFunc {
76
+ return func(c *gin.Context) {
77
+ userId := strconv.Itoa(c.GetInt("id"))
78
+ ctx := context.Background()
79
+ rdb := common.RDB
80
+
81
+ // 1. 检查总请求数限制(当totalMaxCount为0时会自动跳过)
82
+ totalKey := fmt.Sprintf("rateLimit:%s:%s", ModelRequestRateLimitCountMark, userId)
83
+ allowed, err := checkRedisRateLimit(ctx, rdb, totalKey, totalMaxCount, duration)
84
+ if err != nil {
85
+ fmt.Println("检查总请求数限制失败:", err.Error())
86
+ abortWithOpenAiMessage(c, http.StatusInternalServerError, "rate_limit_check_failed")
87
+ return
88
+ }
89
+ if !allowed {
90
+ abortWithOpenAiMessage(c, http.StatusTooManyRequests, fmt.Sprintf("您已达到总请求数限制:%d分钟内最多请求%d次,包括失败次数,请检查您的请求是否正确", setting.ModelRequestRateLimitDurationMinutes, totalMaxCount))
91
+ }
92
+
93
+ // 2. 检查成功请求数限制
94
+ successKey := fmt.Sprintf("rateLimit:%s:%s", ModelRequestRateLimitSuccessCountMark, userId)
95
+ allowed, err = checkRedisRateLimit(ctx, rdb, successKey, successMaxCount, duration)
96
+ if err != nil {
97
+ fmt.Println("检查成功请求数限制失败:", err.Error())
98
+ abortWithOpenAiMessage(c, http.StatusInternalServerError, "rate_limit_check_failed")
99
+ return
100
+ }
101
+ if !allowed {
102
+ abortWithOpenAiMessage(c, http.StatusTooManyRequests, fmt.Sprintf("您已达到请求数限制:%d分钟内最多请求%d次", setting.ModelRequestRateLimitDurationMinutes, successMaxCount))
103
+ return
104
+ }
105
+
106
+ // 3. 记录总请求(当totalMaxCount为0时会自动跳过)
107
+ recordRedisRequest(ctx, rdb, totalKey, totalMaxCount)
108
+
109
+ // 4. 处理请求
110
+ c.Next()
111
+
112
+ // 5. 如果请求成功,记录成功请求
113
+ if c.Writer.Status() < 400 {
114
+ recordRedisRequest(ctx, rdb, successKey, successMaxCount)
115
+ }
116
+ }
117
+ }
118
+
119
+ // 内存限流处理器
120
+ func memoryRateLimitHandler(duration int64, totalMaxCount, successMaxCount int) gin.HandlerFunc {
121
+ inMemoryRateLimiter.Init(common.RateLimitKeyExpirationDuration)
122
+
123
+ return func(c *gin.Context) {
124
+ userId := strconv.Itoa(c.GetInt("id"))
125
+ totalKey := ModelRequestRateLimitCountMark + userId
126
+ successKey := ModelRequestRateLimitSuccessCountMark + userId
127
+
128
+ // 1. 检查总请求数限制(当totalMaxCount为0时跳过)
129
+ if totalMaxCount > 0 && !inMemoryRateLimiter.Request(totalKey, totalMaxCount, duration) {
130
+ c.Status(http.StatusTooManyRequests)
131
+ c.Abort()
132
+ return
133
+ }
134
+
135
+ // 2. 检查成功请求数限制
136
+ // 使用一个临时key来检查限制,这样可以避免实际记录
137
+ checkKey := successKey + "_check"
138
+ if !inMemoryRateLimiter.Request(checkKey, successMaxCount, duration) {
139
+ c.Status(http.StatusTooManyRequests)
140
+ c.Abort()
141
+ return
142
+ }
143
+
144
+ // 3. 处理请求
145
+ c.Next()
146
+
147
+ // 4. 如果请求成功,记录到实际的成功请求计数中
148
+ if c.Writer.Status() < 400 {
149
+ inMemoryRateLimiter.Request(successKey, successMaxCount, duration)
150
+ }
151
+ }
152
+ }
153
+
154
+ // ModelRequestRateLimit 模型请求限流中间件
155
+ func ModelRequestRateLimit() func(c *gin.Context) {
156
+ // 如果未启用限流,直接放行
157
+ if !setting.ModelRequestRateLimitEnabled {
158
+ return defNext
159
+ }
160
+
161
+ // 计算限流参数
162
+ duration := int64(setting.ModelRequestRateLimitDurationMinutes * 60)
163
+ totalMaxCount := setting.ModelRequestRateLimitCount
164
+ successMaxCount := setting.ModelRequestRateLimitSuccessCount
165
+
166
+ // 根据存储类型选择限流处理器
167
+ if common.RedisEnabled {
168
+ return redisRateLimitHandler(duration, totalMaxCount, successMaxCount)
169
+ } else {
170
+ return memoryRateLimitHandler(duration, totalMaxCount, successMaxCount)
171
+ }
172
+ }
model/log.go CHANGED
@@ -1,8 +1,8 @@
1
  package model
2
 
3
  import (
4
- "context"
5
  "fmt"
 
6
  "one-api/common"
7
  "os"
8
  "strings"
@@ -87,14 +87,14 @@ func RecordLog(userId int, logType int, content string) {
87
  }
88
  }
89
 
90
- func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int,
91
  modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int,
92
  isStream bool, group string, other map[string]interface{}) {
93
- common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
94
  if !common.LogConsumeEnabled {
95
  return
96
  }
97
- username, _ := GetUsernameById(userId, false)
98
  otherStr := common.MapToJsonStr(other)
99
  log := &Log{
100
  UserId: userId,
@@ -116,7 +116,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
116
  }
117
  err := LOG_DB.Create(log).Error
118
  if err != nil {
119
- common.LogError(ctx, "failed to record log: "+err.Error())
120
  }
121
  if common.DataExportEnabled {
122
  gopool.Go(func() {
 
1
  package model
2
 
3
  import (
 
4
  "fmt"
5
+ "github.com/gin-gonic/gin"
6
  "one-api/common"
7
  "os"
8
  "strings"
 
87
  }
88
  }
89
 
90
+ func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens int, completionTokens int,
91
  modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int,
92
  isStream bool, group string, other map[string]interface{}) {
93
+ common.LogInfo(c, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
94
  if !common.LogConsumeEnabled {
95
  return
96
  }
97
+ username := c.GetString("username")
98
  otherStr := common.MapToJsonStr(other)
99
  log := &Log{
100
  UserId: userId,
 
116
  }
117
  err := LOG_DB.Create(log).Error
118
  if err != nil {
119
+ common.LogError(c, "failed to record log: "+err.Error())
120
  }
121
  if common.DataExportEnabled {
122
  gopool.Go(func() {
model/option.go CHANGED
@@ -3,6 +3,7 @@ package model
3
  import (
4
  "one-api/common"
5
  "one-api/setting"
 
6
  "strconv"
7
  "strings"
8
  "time"
@@ -23,6 +24,8 @@ func AllOption() ([]*Option, error) {
23
  func InitOptionMap() {
24
  common.OptionMapRWMutex.Lock()
25
  common.OptionMap = make(map[string]string)
 
 
26
  common.OptionMap["FileUploadPermission"] = strconv.Itoa(common.FileUploadPermission)
27
  common.OptionMap["FileDownloadPermission"] = strconv.Itoa(common.FileDownloadPermission)
28
  common.OptionMap["ImageUploadPermission"] = strconv.Itoa(common.ImageUploadPermission)
@@ -85,6 +88,9 @@ func InitOptionMap() {
85
  common.OptionMap["QuotaForInvitee"] = strconv.Itoa(common.QuotaForInvitee)
86
  common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
87
  common.OptionMap["ShouldPreConsumedQuota"] = strconv.Itoa(common.PreConsumedQuota)
 
 
 
88
  common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
89
  common.OptionMap["ModelPrice"] = common.ModelPrice2JSONString()
90
  common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString()
@@ -105,13 +111,19 @@ func InitOptionMap() {
105
  common.OptionMap["MjActionCheckSuccessEnabled"] = strconv.FormatBool(setting.MjActionCheckSuccessEnabled)
106
  common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(setting.CheckSensitiveEnabled)
107
  common.OptionMap["DemoSiteEnabled"] = strconv.FormatBool(setting.DemoSiteEnabled)
 
108
  common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(setting.CheckSensitiveOnPromptEnabled)
109
- //common.OptionMap["CheckSensitiveOnCompletionEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnCompletionEnabled)
110
  common.OptionMap["StopOnSensitiveEnabled"] = strconv.FormatBool(setting.StopOnSensitiveEnabled)
111
  common.OptionMap["SensitiveWords"] = setting.SensitiveWordsToString()
112
  common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(setting.StreamCacheQueueLength)
113
  common.OptionMap["AutomaticDisableKeywords"] = setting.AutomaticDisableKeywordsToString()
114
 
 
 
 
 
 
 
115
  common.OptionMapRWMutex.Unlock()
116
  loadOptionsFromDatabase()
117
  }
@@ -154,6 +166,13 @@ func updateOptionMap(key string, value string) (err error) {
154
  common.OptionMapRWMutex.Lock()
155
  defer common.OptionMapRWMutex.Unlock()
156
  common.OptionMap[key] = value
 
 
 
 
 
 
 
157
  if strings.HasSuffix(key, "Permission") {
158
  intValue, _ := strconv.Atoi(value)
159
  switch key {
@@ -226,8 +245,8 @@ func updateOptionMap(key string, value string) (err error) {
226
  setting.DemoSiteEnabled = boolValue
227
  case "CheckSensitiveOnPromptEnabled":
228
  setting.CheckSensitiveOnPromptEnabled = boolValue
229
- //case "CheckSensitiveOnCompletionEnabled":
230
- // constant.CheckSensitiveOnCompletionEnabled = boolValue
231
  case "StopOnSensitiveEnabled":
232
  setting.StopOnSensitiveEnabled = boolValue
233
  case "SMTPSSLEnabled":
@@ -308,6 +327,12 @@ func updateOptionMap(key string, value string) (err error) {
308
  common.QuotaRemindThreshold, _ = strconv.Atoi(value)
309
  case "ShouldPreConsumedQuota":
310
  common.PreConsumedQuota, _ = strconv.Atoi(value)
 
 
 
 
 
 
311
  case "RetryTimes":
312
  common.RetryTimes, _ = strconv.Atoi(value)
313
  case "DataExportInterval":
@@ -343,3 +368,28 @@ func updateOptionMap(key string, value string) (err error) {
343
  }
344
  return err
345
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import (
4
  "one-api/common"
5
  "one-api/setting"
6
+ "one-api/setting/config"
7
  "strconv"
8
  "strings"
9
  "time"
 
24
  func InitOptionMap() {
25
  common.OptionMapRWMutex.Lock()
26
  common.OptionMap = make(map[string]string)
27
+
28
+ // 添加原有的系统配置
29
  common.OptionMap["FileUploadPermission"] = strconv.Itoa(common.FileUploadPermission)
30
  common.OptionMap["FileDownloadPermission"] = strconv.Itoa(common.FileDownloadPermission)
31
  common.OptionMap["ImageUploadPermission"] = strconv.Itoa(common.ImageUploadPermission)
 
88
  common.OptionMap["QuotaForInvitee"] = strconv.Itoa(common.QuotaForInvitee)
89
  common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
90
  common.OptionMap["ShouldPreConsumedQuota"] = strconv.Itoa(common.PreConsumedQuota)
91
+ common.OptionMap["ModelRequestRateLimitCount"] = strconv.Itoa(setting.ModelRequestRateLimitCount)
92
+ common.OptionMap["ModelRequestRateLimitDurationMinutes"] = strconv.Itoa(setting.ModelRequestRateLimitDurationMinutes)
93
+ common.OptionMap["ModelRequestRateLimitSuccessCount"] = strconv.Itoa(setting.ModelRequestRateLimitSuccessCount)
94
  common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
95
  common.OptionMap["ModelPrice"] = common.ModelPrice2JSONString()
96
  common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString()
 
111
  common.OptionMap["MjActionCheckSuccessEnabled"] = strconv.FormatBool(setting.MjActionCheckSuccessEnabled)
112
  common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(setting.CheckSensitiveEnabled)
113
  common.OptionMap["DemoSiteEnabled"] = strconv.FormatBool(setting.DemoSiteEnabled)
114
+ common.OptionMap["ModelRequestRateLimitEnabled"] = strconv.FormatBool(setting.ModelRequestRateLimitEnabled)
115
  common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(setting.CheckSensitiveOnPromptEnabled)
 
116
  common.OptionMap["StopOnSensitiveEnabled"] = strconv.FormatBool(setting.StopOnSensitiveEnabled)
117
  common.OptionMap["SensitiveWords"] = setting.SensitiveWordsToString()
118
  common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(setting.StreamCacheQueueLength)
119
  common.OptionMap["AutomaticDisableKeywords"] = setting.AutomaticDisableKeywordsToString()
120
 
121
+ // 自动添加所有注册的模型配置
122
+ modelConfigs := config.GlobalConfig.ExportAllConfigs()
123
+ for k, v := range modelConfigs {
124
+ common.OptionMap[k] = v
125
+ }
126
+
127
  common.OptionMapRWMutex.Unlock()
128
  loadOptionsFromDatabase()
129
  }
 
166
  common.OptionMapRWMutex.Lock()
167
  defer common.OptionMapRWMutex.Unlock()
168
  common.OptionMap[key] = value
169
+
170
+ // 检查是否是模型配置 - 使用更规范的方式处理
171
+ if handleConfigUpdate(key, value) {
172
+ return nil // 已由配置系统处理
173
+ }
174
+
175
+ // 处理传统配置项...
176
  if strings.HasSuffix(key, "Permission") {
177
  intValue, _ := strconv.Atoi(value)
178
  switch key {
 
245
  setting.DemoSiteEnabled = boolValue
246
  case "CheckSensitiveOnPromptEnabled":
247
  setting.CheckSensitiveOnPromptEnabled = boolValue
248
+ case "ModelRequestRateLimitEnabled":
249
+ setting.ModelRequestRateLimitEnabled = boolValue
250
  case "StopOnSensitiveEnabled":
251
  setting.StopOnSensitiveEnabled = boolValue
252
  case "SMTPSSLEnabled":
 
327
  common.QuotaRemindThreshold, _ = strconv.Atoi(value)
328
  case "ShouldPreConsumedQuota":
329
  common.PreConsumedQuota, _ = strconv.Atoi(value)
330
+ case "ModelRequestRateLimitCount":
331
+ setting.ModelRequestRateLimitCount, _ = strconv.Atoi(value)
332
+ case "ModelRequestRateLimitDurationMinutes":
333
+ setting.ModelRequestRateLimitDurationMinutes, _ = strconv.Atoi(value)
334
+ case "ModelRequestRateLimitSuccessCount":
335
+ setting.ModelRequestRateLimitSuccessCount, _ = strconv.Atoi(value)
336
  case "RetryTimes":
337
  common.RetryTimes, _ = strconv.Atoi(value)
338
  case "DataExportInterval":
 
368
  }
369
  return err
370
  }
371
+
372
+ // handleConfigUpdate 处理分层配置更新,返回是否已处理
373
+ func handleConfigUpdate(key, value string) bool {
374
+ parts := strings.SplitN(key, ".", 2)
375
+ if len(parts) != 2 {
376
+ return false // 不是分层配置
377
+ }
378
+
379
+ configName := parts[0]
380
+ configKey := parts[1]
381
+
382
+ // 获取配置对象
383
+ cfg := config.GlobalConfig.Get(configName)
384
+ if cfg == nil {
385
+ return false // 未注册的配置
386
+ }
387
+
388
+ // 更新配置
389
+ configMap := map[string]string{
390
+ configKey: value,
391
+ }
392
+ config.UpdateConfigFromMap(cfg, configMap)
393
+
394
+ return true // 已处理
395
+ }
model/pricing.go CHANGED
@@ -69,7 +69,8 @@ func updatePricing() {
69
  pricing.ModelPrice = modelPrice
70
  pricing.QuotaType = 1
71
  } else {
72
- pricing.ModelRatio = common.GetModelRatio(model)
 
73
  pricing.CompletionRatio = common.GetCompletionRatio(model)
74
  pricing.QuotaType = 0
75
  }
 
69
  pricing.ModelPrice = modelPrice
70
  pricing.QuotaType = 1
71
  } else {
72
+ modelRatio, _ := common.GetModelRatio(model)
73
+ pricing.ModelRatio = modelRatio
74
  pricing.CompletionRatio = common.GetCompletionRatio(model)
75
  pricing.QuotaType = 0
76
  }
model/user.go CHANGED
@@ -320,7 +320,7 @@ func (user *User) Insert(inviterId int) error {
320
  }
321
  if inviterId != 0 {
322
  if common.QuotaForInvitee > 0 {
323
- _ = IncreaseUserQuota(user.Id, common.QuotaForInvitee)
324
  RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", common.LogQuota(common.QuotaForInvitee)))
325
  }
326
  if common.QuotaForInviter > 0 {
@@ -502,35 +502,35 @@ func IsAdmin(userId int) bool {
502
  return user.Role >= common.RoleAdminUser
503
  }
504
 
505
- // IsUserEnabled checks user status from Redis first, falls back to DB if needed
506
- func IsUserEnabled(id int, fromDB bool) (status bool, err error) {
507
- defer func() {
508
- // Update Redis cache asynchronously on successful DB read
509
- if shouldUpdateRedis(fromDB, err) {
510
- gopool.Go(func() {
511
- if err := updateUserStatusCache(id, status); err != nil {
512
- common.SysError("failed to update user status cache: " + err.Error())
513
- }
514
- })
515
- }
516
- }()
517
- if !fromDB && common.RedisEnabled {
518
- // Try Redis first
519
- status, err := getUserStatusCache(id)
520
- if err == nil {
521
- return status == common.UserStatusEnabled, nil
522
- }
523
- // Don't return error - fall through to DB
524
- }
525
- fromDB = true
526
- var user User
527
- err = DB.Where("id = ?", id).Select("status").Find(&user).Error
528
- if err != nil {
529
- return false, err
530
- }
531
-
532
- return user.Status == common.UserStatusEnabled, nil
533
- }
534
 
535
  func ValidateAccessToken(token string) (user *User) {
536
  if token == "" {
@@ -639,7 +639,7 @@ func GetUserSetting(id int, fromDB bool) (settingMap map[string]interface{}, err
639
  return common.StrToMap(setting), nil
640
  }
641
 
642
- func IncreaseUserQuota(id int, quota int) (err error) {
643
  if quota < 0 {
644
  return errors.New("quota 不能为负数!")
645
  }
@@ -649,7 +649,7 @@ func IncreaseUserQuota(id int, quota int) (err error) {
649
  common.SysError("failed to increase user quota: " + err.Error())
650
  }
651
  })
652
- if common.BatchUpdateEnabled {
653
  addNewRecord(BatchUpdateTypeUserQuota, id, quota)
654
  return nil
655
  }
@@ -694,7 +694,7 @@ func DeltaUpdateUserQuota(id int, delta int) (err error) {
694
  return nil
695
  }
696
  if delta > 0 {
697
- return IncreaseUserQuota(id, delta)
698
  } else {
699
  return DecreaseUserQuota(id, -delta)
700
  }
 
320
  }
321
  if inviterId != 0 {
322
  if common.QuotaForInvitee > 0 {
323
+ _ = IncreaseUserQuota(user.Id, common.QuotaForInvitee, true)
324
  RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", common.LogQuota(common.QuotaForInvitee)))
325
  }
326
  if common.QuotaForInviter > 0 {
 
502
  return user.Role >= common.RoleAdminUser
503
  }
504
 
505
+ //// IsUserEnabled checks user status from Redis first, falls back to DB if needed
506
+ //func IsUserEnabled(id int, fromDB bool) (status bool, err error) {
507
+ // defer func() {
508
+ // // Update Redis cache asynchronously on successful DB read
509
+ // if shouldUpdateRedis(fromDB, err) {
510
+ // gopool.Go(func() {
511
+ // if err := updateUserStatusCache(id, status); err != nil {
512
+ // common.SysError("failed to update user status cache: " + err.Error())
513
+ // }
514
+ // })
515
+ // }
516
+ // }()
517
+ // if !fromDB && common.RedisEnabled {
518
+ // // Try Redis first
519
+ // status, err := getUserStatusCache(id)
520
+ // if err == nil {
521
+ // return status == common.UserStatusEnabled, nil
522
+ // }
523
+ // // Don't return error - fall through to DB
524
+ // }
525
+ // fromDB = true
526
+ // var user User
527
+ // err = DB.Where("id = ?", id).Select("status").Find(&user).Error
528
+ // if err != nil {
529
+ // return false, err
530
+ // }
531
+ //
532
+ // return user.Status == common.UserStatusEnabled, nil
533
+ //}
534
 
535
  func ValidateAccessToken(token string) (user *User) {
536
  if token == "" {
 
639
  return common.StrToMap(setting), nil
640
  }
641
 
642
+ func IncreaseUserQuota(id int, quota int, db bool) (err error) {
643
  if quota < 0 {
644
  return errors.New("quota 不能为负数!")
645
  }
 
649
  common.SysError("failed to increase user quota: " + err.Error())
650
  }
651
  })
652
+ if !db && common.BatchUpdateEnabled {
653
  addNewRecord(BatchUpdateTypeUserQuota, id, quota)
654
  return nil
655
  }
 
694
  return nil
695
  }
696
  if delta > 0 {
697
+ return IncreaseUserQuota(id, delta, false)
698
  } else {
699
  return DecreaseUserQuota(id, -delta)
700
  }
model/user_cache.go CHANGED
@@ -3,6 +3,7 @@ package model
3
  import (
4
  "encoding/json"
5
  "fmt"
 
6
  "one-api/common"
7
  "one-api/constant"
8
  "time"
@@ -21,6 +22,15 @@ type UserBase struct {
21
  Setting string `json:"setting"`
22
  }
23
 
 
 
 
 
 
 
 
 
 
24
  func (user *UserBase) GetSetting() map[string]interface{} {
25
  if user.Setting == "" {
26
  return nil
 
3
  import (
4
  "encoding/json"
5
  "fmt"
6
+ "github.com/gin-gonic/gin"
7
  "one-api/common"
8
  "one-api/constant"
9
  "time"
 
22
  Setting string `json:"setting"`
23
  }
24
 
25
+ func (user *UserBase) WriteContext(c *gin.Context) {
26
+ c.Set(constant.ContextKeyUserGroup, user.Group)
27
+ c.Set(constant.ContextKeyUserQuota, user.Quota)
28
+ c.Set(constant.ContextKeyUserStatus, user.Status)
29
+ c.Set(constant.ContextKeyUserEmail, user.Email)
30
+ c.Set("username", user.Username)
31
+ c.Set(constant.ContextKeyUserSetting, user.GetSetting())
32
+ }
33
+
34
  func (user *UserBase) GetSetting() map[string]interface{} {
35
  if user.Setting == "" {
36
  return nil
relay/channel/api_request.go CHANGED
@@ -130,7 +130,7 @@ func DoTaskApiRequest(a TaskAdaptor, c *gin.Context, info *common.TaskRelayInfo,
130
  if err != nil {
131
  return nil, fmt.Errorf("setup request header failed: %w", err)
132
  }
133
- resp, err := doRequest(c, req, info.ToRelayInfo())
134
  if err != nil {
135
  return nil, fmt.Errorf("do request failed: %w", err)
136
  }
 
130
  if err != nil {
131
  return nil, fmt.Errorf("setup request header failed: %w", err)
132
  }
133
+ resp, err := doRequest(c, req, info.RelayInfo)
134
  if err != nil {
135
  return nil, fmt.Errorf("do request failed: %w", err)
136
  }
relay/channel/aws/adaptor.go CHANGED
@@ -8,6 +8,7 @@ import (
8
  "one-api/dto"
9
  "one-api/relay/channel/claude"
10
  relaycommon "one-api/relay/common"
 
11
  )
12
 
13
  const (
@@ -38,6 +39,7 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
38
  }
39
 
40
  func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
 
41
  return nil
42
  }
43
 
@@ -49,8 +51,10 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, re
49
  var claudeReq *claude.ClaudeRequest
50
  var err error
51
  claudeReq, err = claude.RequestOpenAI2ClaudeMessage(*request)
52
-
53
- c.Set("request_model", request.Model)
 
 
54
  c.Set("converted_request", claudeReq)
55
  return claudeReq, err
56
  }
@@ -64,7 +68,6 @@ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.Rela
64
  return nil, errors.New("not implemented")
65
  }
66
 
67
-
68
  func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
69
  return nil, nil
70
  }
 
8
  "one-api/dto"
9
  "one-api/relay/channel/claude"
10
  relaycommon "one-api/relay/common"
11
+ "one-api/setting/model_setting"
12
  )
13
 
14
  const (
 
39
  }
40
 
41
  func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
42
+ model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req)
43
  return nil
44
  }
45
 
 
51
  var claudeReq *claude.ClaudeRequest
52
  var err error
53
  claudeReq, err = claude.RequestOpenAI2ClaudeMessage(*request)
54
+ if err != nil {
55
+ return nil, err
56
+ }
57
+ c.Set("request_model", claudeReq.Model)
58
  c.Set("converted_request", claudeReq)
59
  return claudeReq, err
60
  }
 
68
  return nil, errors.New("not implemented")
69
  }
70
 
 
71
  func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
72
  return nil, nil
73
  }
relay/channel/aws/constants.go CHANGED
@@ -9,7 +9,8 @@ var awsModelIDMap = map[string]string{
9
  "claude-3-haiku-20240307": "anthropic.claude-3-haiku-20240307-v1:0",
10
  "claude-3-5-sonnet-20240620": "anthropic.claude-3-5-sonnet-20240620-v1:0",
11
  "claude-3-5-sonnet-20241022": "anthropic.claude-3-5-sonnet-20241022-v2:0",
12
- "claude-3-5-haiku-20241022": "anthropic.claude-3-5-haiku-20241022-v1:0",
 
13
  }
14
 
15
  var ChannelName = "aws"
 
9
  "claude-3-haiku-20240307": "anthropic.claude-3-haiku-20240307-v1:0",
10
  "claude-3-5-sonnet-20240620": "anthropic.claude-3-5-sonnet-20240620-v1:0",
11
  "claude-3-5-sonnet-20241022": "anthropic.claude-3-5-sonnet-20241022-v2:0",
12
+ "claude-3-5-haiku-20241022": "anthropic.claude-3-5-haiku-20241022-v1:0",
13
+ "claude-3-7-sonnet-20250219": "anthropic.claude-3-7-sonnet-20250219-v1:0",
14
  }
15
 
16
  var ChannelName = "aws"
relay/channel/aws/dto.go CHANGED
@@ -16,6 +16,7 @@ type AwsClaudeRequest struct {
16
  StopSequences []string `json:"stop_sequences,omitempty"`
17
  Tools []claude.Tool `json:"tools,omitempty"`
18
  ToolChoice any `json:"tool_choice,omitempty"`
 
19
  }
20
 
21
  func copyRequest(req *claude.ClaudeRequest) *AwsClaudeRequest {
@@ -30,5 +31,6 @@ func copyRequest(req *claude.ClaudeRequest) *AwsClaudeRequest {
30
  StopSequences: req.StopSequences,
31
  Tools: req.Tools,
32
  ToolChoice: req.ToolChoice,
 
33
  }
34
  }
 
16
  StopSequences []string `json:"stop_sequences,omitempty"`
17
  Tools []claude.Tool `json:"tools,omitempty"`
18
  ToolChoice any `json:"tool_choice,omitempty"`
19
+ Thinking *claude.Thinking `json:"thinking,omitempty"`
20
  }
21
 
22
  func copyRequest(req *claude.ClaudeRequest) *AwsClaudeRequest {
 
31
  StopSequences: req.StopSequences,
32
  Tools: req.Tools,
33
  ToolChoice: req.ToolChoice,
34
+ Thinking: req.Thinking,
35
  }
36
  }
relay/channel/claude/adaptor.go CHANGED
@@ -9,6 +9,7 @@ import (
9
  "one-api/dto"
10
  "one-api/relay/channel"
11
  relaycommon "one-api/relay/common"
 
12
  "strings"
13
  )
14
 
@@ -55,6 +56,7 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *rel
55
  anthropicVersion = "2023-06-01"
56
  }
57
  req.Set("anthropic-version", anthropicVersion)
 
58
  return nil
59
  }
60
 
 
9
  "one-api/dto"
10
  "one-api/relay/channel"
11
  relaycommon "one-api/relay/common"
12
+ "one-api/setting/model_setting"
13
  "strings"
14
  )
15
 
 
56
  anthropicVersion = "2023-06-01"
57
  }
58
  req.Set("anthropic-version", anthropicVersion)
59
+ model_setting.GetClaudeSettings().WriteHeaders(info.OriginModelName, req)
60
  return nil
61
  }
62
 
relay/channel/claude/constants.go CHANGED
@@ -11,6 +11,8 @@ var ModelList = []string{
11
  "claude-3-5-haiku-20241022",
12
  "claude-3-5-sonnet-20240620",
13
  "claude-3-5-sonnet-20241022",
 
 
14
  }
15
 
16
  var ChannelName = "claude"
 
11
  "claude-3-5-haiku-20241022",
12
  "claude-3-5-sonnet-20240620",
13
  "claude-3-5-sonnet-20241022",
14
+ "claude-3-7-sonnet-20250219",
15
+ "claude-3-7-sonnet-20250219-thinking",
16
  }
17
 
18
  var ChannelName = "claude"
relay/channel/claude/dto.go CHANGED
@@ -11,6 +11,9 @@ type ClaudeMediaMessage struct {
11
  Usage *ClaudeUsage `json:"usage,omitempty"`
12
  StopReason *string `json:"stop_reason,omitempty"`
13
  PartialJson string `json:"partial_json,omitempty"`
 
 
 
14
  // tool_calls
15
  Id string `json:"id,omitempty"`
16
  Name string `json:"name,omitempty"`
@@ -54,9 +57,15 @@ type ClaudeRequest struct {
54
  TopP float64 `json:"top_p,omitempty"`
55
  TopK int `json:"top_k,omitempty"`
56
  //ClaudeMetadata `json:"metadata,omitempty"`
57
- Stream bool `json:"stream,omitempty"`
58
- Tools []Tool `json:"tools,omitempty"`
59
- ToolChoice any `json:"tool_choice,omitempty"`
 
 
 
 
 
 
60
  }
61
 
62
  type ClaudeError struct {
 
11
  Usage *ClaudeUsage `json:"usage,omitempty"`
12
  StopReason *string `json:"stop_reason,omitempty"`
13
  PartialJson string `json:"partial_json,omitempty"`
14
+ Thinking string `json:"thinking,omitempty"`
15
+ Signature string `json:"signature,omitempty"`
16
+ Delta string `json:"delta,omitempty"`
17
  // tool_calls
18
  Id string `json:"id,omitempty"`
19
  Name string `json:"name,omitempty"`
 
57
  TopP float64 `json:"top_p,omitempty"`
58
  TopK int `json:"top_k,omitempty"`
59
  //ClaudeMetadata `json:"metadata,omitempty"`
60
+ Stream bool `json:"stream,omitempty"`
61
+ Tools []Tool `json:"tools,omitempty"`
62
+ ToolChoice any `json:"tool_choice,omitempty"`
63
+ Thinking *Thinking `json:"thinking,omitempty"`
64
+ }
65
+
66
+ type Thinking struct {
67
+ Type string `json:"type"`
68
+ BudgetTokens int `json:"budget_tokens"`
69
  }
70
 
71
  type ClaudeError struct {
relay/channel/claude/relay-claude.go CHANGED
@@ -10,6 +10,7 @@ import (
10
  "one-api/dto"
11
  relaycommon "one-api/relay/common"
12
  "one-api/service"
 
13
  "strings"
14
 
15
  "github.com/gin-gonic/gin"
@@ -92,9 +93,31 @@ func RequestOpenAI2ClaudeMessage(textRequest dto.GeneralOpenAIRequest) (*ClaudeR
92
  Stream: textRequest.Stream,
93
  Tools: claudeTools,
94
  }
 
95
  if claudeRequest.MaxTokens == 0 {
96
- claudeRequest.MaxTokens = 4096
97
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  if textRequest.Stop != nil {
99
  // stop maybe string/array string, convert to array string
100
  switch textRequest.Stop.(type) {
@@ -273,7 +296,7 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*
273
  response.Object = "chat.completion.chunk"
274
  response.Model = claudeResponse.Model
275
  response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0)
276
- tools := make([]dto.ToolCall, 0)
277
  var choice dto.ChatCompletionsStreamResponseChoice
278
  if reqMode == RequestModeCompletion {
279
  choice.Delta.SetContentString(claudeResponse.Completion)
@@ -292,10 +315,10 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*
292
  if claudeResponse.ContentBlock != nil {
293
  //choice.Delta.SetContentString(claudeResponse.ContentBlock.Text)
294
  if claudeResponse.ContentBlock.Type == "tool_use" {
295
- tools = append(tools, dto.ToolCall{
296
  ID: claudeResponse.ContentBlock.Id,
297
  Type: "function",
298
- Function: dto.FunctionCall{
299
  Name: claudeResponse.ContentBlock.Name,
300
  Arguments: "",
301
  },
@@ -308,12 +331,20 @@ func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) (*
308
  if claudeResponse.Delta != nil {
309
  choice.Index = claudeResponse.Index
310
  choice.Delta.SetContentString(claudeResponse.Delta.Text)
311
- if claudeResponse.Delta.Type == "input_json_delta" {
312
- tools = append(tools, dto.ToolCall{
313
- Function: dto.FunctionCall{
 
314
  Arguments: claudeResponse.Delta.PartialJson,
315
  },
316
  })
 
 
 
 
 
 
 
317
  }
318
  }
319
  } else if claudeResponse.Type == "message_delta" {
@@ -351,7 +382,9 @@ func ResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) *dto.Ope
351
  if len(claudeResponse.Content) > 0 {
352
  responseText = claudeResponse.Content[0].Text
353
  }
354
- tools := make([]dto.ToolCall, 0)
 
 
355
  if reqMode == RequestModeCompletion {
356
  content, _ := json.Marshal(strings.TrimPrefix(claudeResponse.Completion, " "))
357
  choice := dto.OpenAITextResponseChoice{
@@ -367,16 +400,22 @@ func ResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) *dto.Ope
367
  } else {
368
  fullTextResponse.Id = claudeResponse.Id
369
  for _, message := range claudeResponse.Content {
370
- if message.Type == "tool_use" {
 
371
  args, _ := json.Marshal(message.Input)
372
- tools = append(tools, dto.ToolCall{
373
  ID: message.Id,
374
  Type: "function", // compatible with other OpenAI derivative applications
375
- Function: dto.FunctionCall{
376
  Name: message.Name,
377
  Arguments: string(args),
378
  },
379
  })
 
 
 
 
 
380
  }
381
  }
382
  }
@@ -391,6 +430,7 @@ func ResponseClaude2OpenAI(reqMode int, claudeResponse *ClaudeResponse) *dto.Ope
391
  if len(tools) > 0 {
392
  choice.Message.SetToolCalls(tools)
393
  }
 
394
  fullTextResponse.Model = claudeResponse.Model
395
  choices = append(choices, choice)
396
  fullTextResponse.Choices = choices
 
10
  "one-api/dto"
11
  relaycommon "one-api/relay/common"
12
  "one-api/service"
13
+ "one-api/setting/model_setting"
14
  "strings"
15
 
16
  "github.com/gin-gonic/gin"
 
93
  Stream: textRequest.Stream,
94
  Tools: claudeTools,
95
  }
96
+
97
  if claudeRequest.MaxTokens == 0 {
98
+ claudeRequest.MaxTokens = uint(model_setting.GetClaudeSettings().GetDefaultMaxTokens(textRequest.Model))
99
  }
100
+
101
+ if model_setting.GetClaudeSettings().ThinkingAdapterEnabled &&
102
+ strings.HasSuffix(textRequest.Model, "-thinking") {
103
+
104
+ // 因为BudgetTokens 必须大于1024
105
+ if claudeRequest.MaxTokens < 1280 {
106
+ claudeRequest.MaxTokens = 1280
107
+ }
108
+
109
+ // BudgetTokens 为 max_tokens 的 80%
110
+ claudeRequest.Thinking = &Thinking{
111
+ Type: "enabled",
112
+ BudgetTokens: int(float64(claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage),
113
+ }
114
+ // TODO: 临时处理
115
+ // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking
116
+ claudeRequest.TopP = 0
117
+ claudeRequest.Temperature = common.GetPointer[float64](1.0)
118
+ claudeRequest.Model = strings.TrimSuffix(textRequest.Model, "-thinking")
119
+ }
120
+
121
  if textRequest.Stop != nil {
122
  // stop maybe string/array string, convert to array string
123
  switch textRequest.Stop.(type) {
 
296
  response.Object = "chat.completion.chunk"
297
  response.Model = claudeResponse.Model
298
  response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0)
299
+ tools := make([]dto.ToolCallResponse, 0)
300
  var choice dto.ChatCompletionsStreamResponseChoice
301
  if reqMode == RequestModeCompletion {
302
  choice.Delta.SetContentString(claudeResponse.Completion)
 
315
  if claudeResponse.ContentBlock != nil {
316
  //choice.Delta.SetContentString(claudeResponse.ContentBlock.Text)
317
  if claudeResponse.ContentBlock.Type == "tool_use" {
318
+ tools = append(tools, dto.ToolCallResponse{
319
  ID: claudeResponse.ContentBlock.Id,
320
  Type: "function",
321
+ Function: dto.FunctionResponse{
322
  Name: claudeResponse.ContentBlock.Name,
323
  Arguments: "",
324
  },
 
331
  if claudeResponse.Delta != nil {
332
  choice.Index = claudeResponse.Index
333
  choice.Delta.SetContentString(claudeResponse.Delta.Text)
334
+ switch claudeResponse.Delta.Type {
335
+ case "input_json_delta":
336
+ tools = append(tools, dto.ToolCallResponse{
337
+ Function: dto.FunctionResponse{
338
  Arguments: claudeResponse.Delta.PartialJson,
339
  },
340
  })
341
+ case "signature_delta":
342
+ // 加密的不处理
343
+ signatureContent := "\n"
344
+ choice.Delta.ReasoningContent = &signatureContent
345
+ case "thinking_delta":
346
+ thinkingContent := claudeResponse.Delta.Thinking
347
+ choice.Delta.ReasoningContent = &thinkingContent
348
  }
349
  }
350
  } else if claudeResponse.Type == "message_delta" {
 
382
  if len(claudeResponse.Content) > 0 {
383
  responseText = claudeResponse.Content[0].Text
384
  }
385
+ tools := make([]dto.ToolCallResponse, 0)
386
+ thinkingContent := ""
387
+
388
  if reqMode == RequestModeCompletion {
389
  content, _ := json.Marshal(strings.TrimPrefix(claudeResponse.Completion, " "))
390
  choice := dto.OpenAITextResponseChoice{
 
400
  } else {
401
  fullTextResponse.Id = claudeResponse.Id
402
  for _, message := range claudeResponse.Content {
403
+ switch message.Type {
404
+ case "tool_use":
405
  args, _ := json.Marshal(message.Input)
406
+ tools = append(tools, dto.ToolCallResponse{
407
  ID: message.Id,
408
  Type: "function", // compatible with other OpenAI derivative applications
409
+ Function: dto.FunctionResponse{
410
  Name: message.Name,
411
  Arguments: string(args),
412
  },
413
  })
414
+ case "thinking":
415
+ // 加密的不管, 只输出明文的推理过程
416
+ thinkingContent = message.Thinking
417
+ case "text":
418
+ responseText = message.Text
419
  }
420
  }
421
  }
 
430
  if len(tools) > 0 {
431
  choice.Message.SetToolCalls(tools)
432
  }
433
+ choice.Message.ReasoningContent = thinkingContent
434
  fullTextResponse.Model = claudeResponse.Model
435
  choices = append(choices, choice)
436
  fullTextResponse.Choices = choices
relay/channel/dify/adaptor.go CHANGED
@@ -9,9 +9,18 @@ import (
9
  "one-api/dto"
10
  "one-api/relay/channel"
11
  relaycommon "one-api/relay/common"
 
 
 
 
 
 
 
 
12
  )
13
 
14
  type Adaptor struct {
 
15
  }
16
 
17
  func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
@@ -25,10 +34,28 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
25
  }
26
 
27
  func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
  func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
31
- return fmt.Sprintf("%s/v1/chat-messages", info.BaseUrl), nil
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
@@ -53,7 +80,6 @@ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.Rela
53
  return nil, errors.New("not implemented")
54
  }
55
 
56
-
57
  func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
58
  return channel.DoApiRequest(a, c, info, requestBody)
59
  }
 
9
  "one-api/dto"
10
  "one-api/relay/channel"
11
  relaycommon "one-api/relay/common"
12
+ "strings"
13
+ )
14
+
15
+ const (
16
+ BotTypeChatFlow = 1 // chatflow default
17
+ BotTypeAgent = 2
18
+ BotTypeWorkFlow = 3
19
+ BotTypeCompletion = 4
20
  )
21
 
22
  type Adaptor struct {
23
+ BotType int
24
  }
25
 
26
  func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
 
34
  }
35
 
36
  func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
37
+ if strings.HasPrefix(info.UpstreamModelName, "agent") {
38
+ a.BotType = BotTypeAgent
39
+ } else if strings.HasPrefix(info.UpstreamModelName, "workflow") {
40
+ a.BotType = BotTypeWorkFlow
41
+ } else if strings.HasPrefix(info.UpstreamModelName, "chat") {
42
+ a.BotType = BotTypeCompletion
43
+ } else {
44
+ a.BotType = BotTypeChatFlow
45
+ }
46
  }
47
 
48
  func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
49
+ switch a.BotType {
50
+ case BotTypeWorkFlow:
51
+ return fmt.Sprintf("%s/v1/workflows/run", info.BaseUrl), nil
52
+ case BotTypeCompletion:
53
+ return fmt.Sprintf("%s/v1/completion-messages", info.BaseUrl), nil
54
+ case BotTypeAgent:
55
+ fallthrough
56
+ default:
57
+ return fmt.Sprintf("%s/v1/chat-messages", info.BaseUrl), nil
58
+ }
59
  }
60
 
61
  func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
 
80
  return nil, errors.New("not implemented")
81
  }
82
 
 
83
  func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
84
  return channel.DoApiRequest(a, c, info, requestBody)
85
  }
relay/channel/gemini/adaptor.go CHANGED
@@ -7,11 +7,11 @@ import (
7
  "io"
8
  "net/http"
9
  "one-api/common"
10
- "one-api/constant"
11
  "one-api/dto"
12
  "one-api/relay/channel"
13
  relaycommon "one-api/relay/common"
14
  "one-api/service"
 
15
 
16
  "strings"
17
 
@@ -64,15 +64,7 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
64
  }
65
 
66
  func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
67
- // 从映射中获取模型名称对应的版本,如果找不到就使用 info.ApiVersion 或默认的版本 "v1beta"
68
- version, beta := constant.GeminiModelMap[info.UpstreamModelName]
69
- if !beta {
70
- if info.ApiVersion != "" {
71
- version = info.ApiVersion
72
- } else {
73
- version = "v1beta"
74
- }
75
- }
76
 
77
  if strings.HasPrefix(info.UpstreamModelName, "imagen") {
78
  return fmt.Sprintf("%s/%s/models/%s:predict", info.BaseUrl, version, info.UpstreamModelName), nil
 
7
  "io"
8
  "net/http"
9
  "one-api/common"
 
10
  "one-api/dto"
11
  "one-api/relay/channel"
12
  relaycommon "one-api/relay/common"
13
  "one-api/service"
14
+ "one-api/setting/model_setting"
15
 
16
  "strings"
17
 
 
64
  }
65
 
66
  func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
67
+ version := model_setting.GetGeminiVersionSetting(info.UpstreamModelName)
 
 
 
 
 
 
 
 
68
 
69
  if strings.HasPrefix(info.UpstreamModelName, "imagen") {
70
  return fmt.Sprintf("%s/%s/models/%s:predict", info.BaseUrl, version, info.UpstreamModelName), nil
relay/channel/gemini/constant.go CHANGED
@@ -20,4 +20,12 @@ var ModelList = []string{
20
  "imagen-3.0-generate-002",
21
  }
22
 
 
 
 
 
 
 
 
 
23
  var ChannelName = "google gemini"
 
20
  "imagen-3.0-generate-002",
21
  }
22
 
23
+ var SafetySettingList = []string{
24
+ "HARM_CATEGORY_HARASSMENT",
25
+ "HARM_CATEGORY_HATE_SPEECH",
26
+ "HARM_CATEGORY_SEXUALLY_EXPLICIT",
27
+ "HARM_CATEGORY_DANGEROUS_CONTENT",
28
+ "HARM_CATEGORY_CIVIC_INTEGRITY",
29
+ }
30
+
31
  var ChannelName = "google gemini"
relay/channel/gemini/relay-gemini.go CHANGED
@@ -11,6 +11,7 @@ import (
11
  "one-api/dto"
12
  relaycommon "one-api/relay/common"
13
  "one-api/service"
 
14
  "strings"
15
  "unicode/utf8"
16
 
@@ -22,28 +23,7 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest) (*GeminiChatReque
22
 
23
  geminiRequest := GeminiChatRequest{
24
  Contents: make([]GeminiChatContent, 0, len(textRequest.Messages)),
25
- SafetySettings: []GeminiChatSafetySettings{
26
- {
27
- Category: "HARM_CATEGORY_HARASSMENT",
28
- Threshold: common.GeminiSafetySetting,
29
- },
30
- {
31
- Category: "HARM_CATEGORY_HATE_SPEECH",
32
- Threshold: common.GeminiSafetySetting,
33
- },
34
- {
35
- Category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
36
- Threshold: common.GeminiSafetySetting,
37
- },
38
- {
39
- Category: "HARM_CATEGORY_DANGEROUS_CONTENT",
40
- Threshold: common.GeminiSafetySetting,
41
- },
42
- {
43
- Category: "HARM_CATEGORY_CIVIC_INTEGRITY",
44
- Threshold: common.GeminiSafetySetting,
45
- },
46
- },
47
  GenerationConfig: GeminiChatGenerationConfig{
48
  Temperature: textRequest.Temperature,
49
  TopP: textRequest.TopP,
@@ -52,9 +32,18 @@ func CovertGemini2OpenAI(textRequest dto.GeneralOpenAIRequest) (*GeminiChatReque
52
  },
53
  }
54
 
 
 
 
 
 
 
 
 
 
55
  // openaiContent.FuncToToolCalls()
56
  if textRequest.Tools != nil {
57
- functions := make([]dto.FunctionCall, 0, len(textRequest.Tools))
58
  googleSearch := false
59
  codeExecution := false
60
  for _, tool := range textRequest.Tools {
@@ -349,7 +338,7 @@ func unescapeMapOrSlice(data interface{}) interface{} {
349
  return data
350
  }
351
 
352
- func getToolCall(item *GeminiPart) *dto.ToolCall {
353
  var argsBytes []byte
354
  var err error
355
  if result, ok := item.FunctionCall.Arguments.(map[string]interface{}); ok {
@@ -361,10 +350,10 @@ func getToolCall(item *GeminiPart) *dto.ToolCall {
361
  if err != nil {
362
  return nil
363
  }
364
- return &dto.ToolCall{
365
  ID: fmt.Sprintf("call_%s", common.GetUUID()),
366
  Type: "function",
367
- Function: dto.FunctionCall{
368
  Arguments: string(argsBytes),
369
  Name: item.FunctionCall.FunctionName,
370
  },
@@ -379,7 +368,7 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
379
  Choices: make([]dto.OpenAITextResponseChoice, 0, len(response.Candidates)),
380
  }
381
  content, _ := json.Marshal("")
382
- is_tool_call := false
383
  for _, candidate := range response.Candidates {
384
  choice := dto.OpenAITextResponseChoice{
385
  Index: int(candidate.Index),
@@ -391,12 +380,12 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
391
  }
392
  if len(candidate.Content.Parts) > 0 {
393
  var texts []string
394
- var tool_calls []dto.ToolCall
395
  for _, part := range candidate.Content.Parts {
396
  if part.FunctionCall != nil {
397
  choice.FinishReason = constant.FinishReasonToolCalls
398
- if call := getToolCall(&part); call != nil {
399
- tool_calls = append(tool_calls, *call)
400
  }
401
  } else {
402
  if part.ExecutableCode != nil {
@@ -411,9 +400,9 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
411
  }
412
  }
413
  }
414
- if len(tool_calls) > 0 {
415
- choice.Message.SetToolCalls(tool_calls)
416
- is_tool_call = true
417
  }
418
 
419
  choice.Message.SetStringContent(strings.Join(texts, "\n"))
@@ -429,7 +418,7 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
429
  choice.FinishReason = constant.FinishReasonContentFilter
430
  }
431
  }
432
- if is_tool_call {
433
  choice.FinishReason = constant.FinishReasonToolCalls
434
  }
435
 
@@ -468,7 +457,7 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C
468
  for _, part := range candidate.Content.Parts {
469
  if part.FunctionCall != nil {
470
  isTools = true
471
- if call := getToolCall(&part); call != nil {
472
  call.SetIndex(len(choice.Delta.ToolCalls))
473
  choice.Delta.ToolCalls = append(choice.Delta.ToolCalls, *call)
474
  }
 
11
  "one-api/dto"
12
  relaycommon "one-api/relay/common"
13
  "one-api/service"
14
+ "one-api/setting/model_setting"
15
  "strings"
16
  "unicode/utf8"
17
 
 
23
 
24
  geminiRequest := GeminiChatRequest{
25
  Contents: make([]GeminiChatContent, 0, len(textRequest.Messages)),
26
+ //SafetySettings: []GeminiChatSafetySettings{},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  GenerationConfig: GeminiChatGenerationConfig{
28
  Temperature: textRequest.Temperature,
29
  TopP: textRequest.TopP,
 
32
  },
33
  }
34
 
35
+ safetySettings := make([]GeminiChatSafetySettings, 0, len(SafetySettingList))
36
+ for _, category := range SafetySettingList {
37
+ safetySettings = append(safetySettings, GeminiChatSafetySettings{
38
+ Category: category,
39
+ Threshold: model_setting.GetGeminiSafetySetting(category),
40
+ })
41
+ }
42
+ geminiRequest.SafetySettings = safetySettings
43
+
44
  // openaiContent.FuncToToolCalls()
45
  if textRequest.Tools != nil {
46
+ functions := make([]dto.FunctionRequest, 0, len(textRequest.Tools))
47
  googleSearch := false
48
  codeExecution := false
49
  for _, tool := range textRequest.Tools {
 
338
  return data
339
  }
340
 
341
+ func getResponseToolCall(item *GeminiPart) *dto.ToolCallResponse {
342
  var argsBytes []byte
343
  var err error
344
  if result, ok := item.FunctionCall.Arguments.(map[string]interface{}); ok {
 
350
  if err != nil {
351
  return nil
352
  }
353
+ return &dto.ToolCallResponse{
354
  ID: fmt.Sprintf("call_%s", common.GetUUID()),
355
  Type: "function",
356
+ Function: dto.FunctionResponse{
357
  Arguments: string(argsBytes),
358
  Name: item.FunctionCall.FunctionName,
359
  },
 
368
  Choices: make([]dto.OpenAITextResponseChoice, 0, len(response.Candidates)),
369
  }
370
  content, _ := json.Marshal("")
371
+ isToolCall := false
372
  for _, candidate := range response.Candidates {
373
  choice := dto.OpenAITextResponseChoice{
374
  Index: int(candidate.Index),
 
380
  }
381
  if len(candidate.Content.Parts) > 0 {
382
  var texts []string
383
+ var toolCalls []dto.ToolCallResponse
384
  for _, part := range candidate.Content.Parts {
385
  if part.FunctionCall != nil {
386
  choice.FinishReason = constant.FinishReasonToolCalls
387
+ if call := getResponseToolCall(&part); call != nil {
388
+ toolCalls = append(toolCalls, *call)
389
  }
390
  } else {
391
  if part.ExecutableCode != nil {
 
400
  }
401
  }
402
  }
403
+ if len(toolCalls) > 0 {
404
+ choice.Message.SetToolCalls(toolCalls)
405
+ isToolCall = true
406
  }
407
 
408
  choice.Message.SetStringContent(strings.Join(texts, "\n"))
 
418
  choice.FinishReason = constant.FinishReasonContentFilter
419
  }
420
  }
421
+ if isToolCall {
422
  choice.FinishReason = constant.FinishReasonToolCalls
423
  }
424
 
 
457
  for _, part := range candidate.Content.Parts {
458
  if part.FunctionCall != nil {
459
  isTools = true
460
+ if call := getResponseToolCall(&part); call != nil {
461
  call.SetIndex(len(choice.Delta.ToolCalls))
462
  choice.Delta.ToolCalls = append(choice.Delta.ToolCalls, *call)
463
  }
relay/channel/jina/adaptor.go CHANGED
@@ -61,7 +61,7 @@ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.Rela
61
 
62
  func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *dto.OpenAIErrorWithStatusCode) {
63
  if info.RelayMode == constant.RelayModeRerank {
64
- err, usage = jinaRerankHandler(c, resp)
65
  } else if info.RelayMode == constant.RelayModeEmbeddings {
66
  err, usage = jinaEmbeddingHandler(c, resp)
67
  }
 
61
 
62
  func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *dto.OpenAIErrorWithStatusCode) {
63
  if info.RelayMode == constant.RelayModeRerank {
64
+ err, usage = JinaRerankHandler(c, resp)
65
  } else if info.RelayMode == constant.RelayModeEmbeddings {
66
  err, usage = jinaEmbeddingHandler(c, resp)
67
  }
relay/channel/jina/relay-jina.go CHANGED
@@ -9,7 +9,7 @@ import (
9
  "one-api/service"
10
  )
11
 
12
- func jinaRerankHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
13
  responseBody, err := io.ReadAll(resp.Body)
14
  if err != nil {
15
  return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
 
9
  "one-api/service"
10
  )
11
 
12
+ func JinaRerankHandler(c *gin.Context, resp *http.Response) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
13
  responseBody, err := io.ReadAll(resp.Body)
14
  if err != nil {
15
  return service.OpenAIErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError), nil
relay/channel/ollama/dto.go CHANGED
@@ -3,21 +3,22 @@ package ollama
3
  import "one-api/dto"
4
 
5
  type OllamaRequest struct {
6
- Model string `json:"model,omitempty"`
7
- Messages []dto.Message `json:"messages,omitempty"`
8
- Stream bool `json:"stream,omitempty"`
9
- Temperature *float64 `json:"temperature,omitempty"`
10
- Seed float64 `json:"seed,omitempty"`
11
- Topp float64 `json:"top_p,omitempty"`
12
- TopK int `json:"top_k,omitempty"`
13
- Stop any `json:"stop,omitempty"`
14
- Tools []dto.ToolCall `json:"tools,omitempty"`
15
- ResponseFormat any `json:"response_format,omitempty"`
16
- FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
17
- PresencePenalty float64 `json:"presence_penalty,omitempty"`
18
- Suffix any `json:"suffix,omitempty"`
19
- StreamOptions *dto.StreamOptions `json:"stream_options,omitempty"`
20
- Prompt any `json:"prompt,omitempty"`
 
21
  }
22
 
23
  type Options struct {
 
3
  import "one-api/dto"
4
 
5
  type OllamaRequest struct {
6
+ Model string `json:"model,omitempty"`
7
+ Messages []dto.Message `json:"messages,omitempty"`
8
+ Stream bool `json:"stream,omitempty"`
9
+ Temperature *float64 `json:"temperature,omitempty"`
10
+ Seed float64 `json:"seed,omitempty"`
11
+ Topp float64 `json:"top_p,omitempty"`
12
+ TopK int `json:"top_k,omitempty"`
13
+ Stop any `json:"stop,omitempty"`
14
+ MaxTokens uint `json:"max_tokens,omitempty"`
15
+ Tools []dto.ToolCallRequest `json:"tools,omitempty"`
16
+ ResponseFormat any `json:"response_format,omitempty"`
17
+ FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
18
+ PresencePenalty float64 `json:"presence_penalty,omitempty"`
19
+ Suffix any `json:"suffix,omitempty"`
20
+ StreamOptions *dto.StreamOptions `json:"stream_options,omitempty"`
21
+ Prompt any `json:"prompt,omitempty"`
22
  }
23
 
24
  type Options struct {
relay/channel/ollama/relay-ollama.go CHANGED
@@ -58,6 +58,7 @@ func requestOpenAI2Ollama(request dto.GeneralOpenAIRequest) (*OllamaRequest, err
58
  TopK: request.TopK,
59
  Stop: Stop,
60
  Tools: request.Tools,
 
61
  ResponseFormat: request.ResponseFormat,
62
  FrequencyPenalty: request.FrequencyPenalty,
63
  PresencePenalty: request.PresencePenalty,
 
58
  TopK: request.TopK,
59
  Stop: Stop,
60
  Tools: request.Tools,
61
+ MaxTokens: request.MaxTokens,
62
  ResponseFormat: request.ResponseFormat,
63
  FrequencyPenalty: request.FrequencyPenalty,
64
  PresencePenalty: request.PresencePenalty,
relay/channel/openai/adaptor.go CHANGED
@@ -14,6 +14,7 @@ import (
14
  "one-api/dto"
15
  "one-api/relay/channel"
16
  "one-api/relay/channel/ai360"
 
17
  "one-api/relay/channel/lingyiwanwu"
18
  "one-api/relay/channel/minimax"
19
  "one-api/relay/channel/moonshot"
@@ -146,7 +147,7 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, re
146
  }
147
 
148
  func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
149
- return nil, errors.New("not implemented")
150
  }
151
 
152
  func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
@@ -228,6 +229,8 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
228
  err, usage = OpenaiSTTHandler(c, resp, info, a.ResponseFormat)
229
  case constant.RelayModeImagesGenerations:
230
  err, usage = OpenaiTTSHandler(c, resp, info)
 
 
231
  default:
232
  if info.IsStream {
233
  err, usage = OaiStreamHandler(c, resp, info)
 
14
  "one-api/dto"
15
  "one-api/relay/channel"
16
  "one-api/relay/channel/ai360"
17
+ "one-api/relay/channel/jina"
18
  "one-api/relay/channel/lingyiwanwu"
19
  "one-api/relay/channel/minimax"
20
  "one-api/relay/channel/moonshot"
 
147
  }
148
 
149
  func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
150
+ return request, nil
151
  }
152
 
153
  func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
 
229
  err, usage = OpenaiSTTHandler(c, resp, info, a.ResponseFormat)
230
  case constant.RelayModeImagesGenerations:
231
  err, usage = OpenaiTTSHandler(c, resp, info)
232
+ case constant.RelayModeRerank:
233
+ err, usage = jina.JinaRerankHandler(c, resp)
234
  default:
235
  if info.IsStream {
236
  err, usage = OaiStreamHandler(c, resp, info)
relay/channel/openai/constant.go CHANGED
@@ -11,6 +11,7 @@ var ModelList = []string{
11
  "chatgpt-4o-latest",
12
  "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20",
13
  "gpt-4o-mini", "gpt-4o-mini-2024-07-18",
 
14
  "o1-preview", "o1-preview-2024-09-12",
15
  "o1-mini", "o1-mini-2024-09-12",
16
  "o3-mini", "o3-mini-2025-01-31",
 
11
  "chatgpt-4o-latest",
12
  "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20",
13
  "gpt-4o-mini", "gpt-4o-mini-2024-07-18",
14
+ "gpt-4.5-preview", "gpt-4.5-preview-2025-02-27",
15
  "o1-preview", "o1-preview-2024-09-12",
16
  "o1-mini", "o1-mini-2024-09-12",
17
  "o3-mini", "o3-mini-2025-01-31",
relay/channel/openai/relay-openai.go CHANGED
@@ -5,10 +5,6 @@ import (
5
  "bytes"
6
  "encoding/json"
7
  "fmt"
8
- "github.com/bytedance/gopkg/util/gopool"
9
- "github.com/gin-gonic/gin"
10
- "github.com/gorilla/websocket"
11
- "github.com/pkg/errors"
12
  "io"
13
  "math"
14
  "mime/multipart"
@@ -23,21 +19,66 @@ import (
23
  "strings"
24
  "sync"
25
  "time"
 
 
 
 
 
26
  )
27
 
28
- func sendStreamData(c *gin.Context, data string, forceFormat bool) error {
29
  if data == "" {
30
  return nil
31
  }
32
 
33
- if forceFormat {
34
- var lastStreamResponse dto.ChatCompletionsStreamResponse
35
- if err := json.Unmarshal(common.StringToByteSlice(data), &lastStreamResponse); err != nil {
36
- return err
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
 
 
 
 
38
  return service.ObjectData(c, lastStreamResponse)
39
  }
40
- return service.StringData(c, data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
  func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
@@ -56,11 +97,14 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
56
  var usage = &dto.Usage{}
57
  var streamItems []string // store stream items
58
  var forceFormat bool
 
59
 
60
- if info.ChannelType == common.ChannelTypeCustom {
61
- if forceFmt, ok := info.ChannelSetting["force_format"].(bool); ok {
62
- forceFormat = forceFmt
63
- }
 
 
64
  }
65
 
66
  toolCount := 0
@@ -84,7 +128,7 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
84
  )
85
  gopool.Go(func() {
86
  for scanner.Scan() {
87
- info.SetFirstResponseTime()
88
  ticker.Reset(time.Duration(constant.StreamingTimeout) * time.Second)
89
  data := scanner.Text()
90
  if common.DebugEnabled {
@@ -101,10 +145,11 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
101
  data = strings.TrimSpace(data)
102
  if !strings.HasPrefix(data, "[DONE]") {
103
  if lastStreamData != "" {
104
- err := sendStreamData(c, lastStreamData, forceFormat)
105
  if err != nil {
106
  common.LogError(c, "streaming error: "+err.Error())
107
  }
 
108
  }
109
  lastStreamData = data
110
  streamItems = append(streamItems, data)
@@ -144,7 +189,7 @@ func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
144
  }
145
  }
146
  if shouldSendLastResp {
147
- sendStreamData(c, lastStreamData, forceFormat)
148
  }
149
 
150
  // 计算token
 
5
  "bytes"
6
  "encoding/json"
7
  "fmt"
 
 
 
 
8
  "io"
9
  "math"
10
  "mime/multipart"
 
19
  "strings"
20
  "sync"
21
  "time"
22
+
23
+ "github.com/bytedance/gopkg/util/gopool"
24
+ "github.com/gin-gonic/gin"
25
+ "github.com/gorilla/websocket"
26
+ "github.com/pkg/errors"
27
  )
28
 
29
+ func sendStreamData(c *gin.Context, info *relaycommon.RelayInfo, data string, forceFormat bool, thinkToContent bool) error {
30
  if data == "" {
31
  return nil
32
  }
33
 
34
+ if !forceFormat && !thinkToContent {
35
+ return service.StringData(c, data)
36
+ }
37
+
38
+ var lastStreamResponse dto.ChatCompletionsStreamResponse
39
+ if err := json.Unmarshal(common.StringToByteSlice(data), &lastStreamResponse); err != nil {
40
+ return err
41
+ }
42
+
43
+ if !thinkToContent {
44
+ return service.ObjectData(c, lastStreamResponse)
45
+ }
46
+
47
+ // Handle think to content conversion
48
+ if info.IsFirstResponse {
49
+ response := lastStreamResponse.Copy()
50
+ for i := range response.Choices {
51
+ response.Choices[i].Delta.SetContentString("<think>\n")
52
+ response.Choices[i].Delta.SetReasoningContent("")
53
  }
54
+ service.ObjectData(c, response)
55
+ }
56
+
57
+ if lastStreamResponse.Choices == nil || len(lastStreamResponse.Choices) == 0 {
58
  return service.ObjectData(c, lastStreamResponse)
59
  }
60
+
61
+ // Process each choice
62
+ for i, choice := range lastStreamResponse.Choices {
63
+ // Handle transition from thinking to content
64
+ if len(choice.Delta.GetContentString()) > 0 && !info.SendLastReasoningResponse {
65
+ response := lastStreamResponse.Copy()
66
+ for j := range response.Choices {
67
+ response.Choices[j].Delta.SetContentString("\n</think>")
68
+ response.Choices[j].Delta.SetReasoningContent("")
69
+ }
70
+ info.SendLastReasoningResponse = true
71
+ service.ObjectData(c, response)
72
+ }
73
+
74
+ // Convert reasoning content to regular content
75
+ if len(choice.Delta.GetReasoningContent()) > 0 {
76
+ lastStreamResponse.Choices[i].Delta.SetContentString(choice.Delta.GetReasoningContent())
77
+ lastStreamResponse.Choices[i].Delta.SetReasoningContent("")
78
+ }
79
+ }
80
+
81
+ return service.ObjectData(c, lastStreamResponse)
82
  }
83
 
84
  func OaiStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (*dto.OpenAIErrorWithStatusCode, *dto.Usage) {
 
97
  var usage = &dto.Usage{}
98
  var streamItems []string // store stream items
99
  var forceFormat bool
100
+ var thinkToContent bool
101
 
102
+ if forceFmt, ok := info.ChannelSetting[constant.ForceFormat].(bool); ok {
103
+ forceFormat = forceFmt
104
+ }
105
+
106
+ if think2Content, ok := info.ChannelSetting[constant.ChannelSettingThinkingToContent].(bool); ok {
107
+ thinkToContent = think2Content
108
  }
109
 
110
  toolCount := 0
 
128
  )
129
  gopool.Go(func() {
130
  for scanner.Scan() {
131
+ //info.SetFirstResponseTime()
132
  ticker.Reset(time.Duration(constant.StreamingTimeout) * time.Second)
133
  data := scanner.Text()
134
  if common.DebugEnabled {
 
145
  data = strings.TrimSpace(data)
146
  if !strings.HasPrefix(data, "[DONE]") {
147
  if lastStreamData != "" {
148
+ err := sendStreamData(c, info, lastStreamData, forceFormat, thinkToContent)
149
  if err != nil {
150
  common.LogError(c, "streaming error: "+err.Error())
151
  }
152
+ info.SetFirstResponseTime()
153
  }
154
  lastStreamData = data
155
  streamItems = append(streamItems, data)
 
189
  }
190
  }
191
  if shouldSendLastResp {
192
+ sendStreamData(c, info, lastStreamData, forceFormat, thinkToContent)
193
  }
194
 
195
  // 计算token
relay/channel/openrouter/adaptor.go ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package openrouter
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "github.com/gin-gonic/gin"
7
+ "io"
8
+ "net/http"
9
+ "one-api/dto"
10
+ "one-api/relay/channel"
11
+ "one-api/relay/channel/openai"
12
+ relaycommon "one-api/relay/common"
13
+ )
14
+
15
+ type Adaptor struct {
16
+ }
17
+
18
+ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
19
+ //TODO implement me
20
+ return nil, errors.New("not implemented")
21
+ }
22
+
23
+ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
24
+ //TODO implement me
25
+ return nil, errors.New("not implemented")
26
+ }
27
+
28
+ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
29
+ }
30
+
31
+ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
32
+ return fmt.Sprintf("%s/v1/chat/completions", info.BaseUrl), nil
33
+ }
34
+
35
+ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Header, info *relaycommon.RelayInfo) error {
36
+ channel.SetupApiRequestHeader(info, c, req)
37
+ req.Set("Authorization", fmt.Sprintf("Bearer %s", info.ApiKey))
38
+ req.Set("HTTP-Referer", "https://github.com/Calcium-Ion/new-api")
39
+ req.Set("X-Title", "New API")
40
+ return nil
41
+ }
42
+
43
+ func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, request *dto.GeneralOpenAIRequest) (any, error) {
44
+ return request, nil
45
+ }
46
+
47
+ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
48
+ return channel.DoApiRequest(a, c, info, requestBody)
49
+ }
50
+
51
+ func (a *Adaptor) ConvertRerankRequest(c *gin.Context, relayMode int, request dto.RerankRequest) (any, error) {
52
+ return nil, errors.New("not implemented")
53
+ }
54
+
55
+ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.EmbeddingRequest) (any, error) {
56
+ return nil, errors.New("not implemented")
57
+ }
58
+
59
+ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (usage any, err *dto.OpenAIErrorWithStatusCode) {
60
+ if info.IsStream {
61
+ err, usage = openai.OaiStreamHandler(c, resp, info)
62
+ } else {
63
+ err, usage = openai.OpenaiHandler(c, resp, info.PromptTokens, info.UpstreamModelName)
64
+ }
65
+ return
66
+ }
67
+
68
+ func (a *Adaptor) GetModelList() []string {
69
+ return ModelList
70
+ }
71
+
72
+ func (a *Adaptor) GetChannelName() string {
73
+ return ChannelName
74
+ }
relay/channel/openrouter/constant.go ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ package openrouter
2
+
3
+ var ModelList = []string{}
4
+
5
+ var ChannelName = "openrouter"
relay/channel/vertex/adaptor.go CHANGED
@@ -28,6 +28,7 @@ var claudeModelMap = map[string]string{
28
  "claude-3-opus-20240229": "claude-3-opus@20240229",
29
  "claude-3-haiku-20240307": "claude-3-haiku@20240307",
30
  "claude-3-5-sonnet-20240620": "claude-3-5-sonnet@20240620",
 
31
  }
32
 
33
  const anthropicVersion = "vertex-2023-10-16"
@@ -132,7 +133,7 @@ func (a *Adaptor) ConvertRequest(c *gin.Context, info *relaycommon.RelayInfo, re
132
  if err = copier.Copy(vertexClaudeReq, claudeReq); err != nil {
133
  return nil, errors.New("failed to copy claude request")
134
  }
135
- c.Set("request_model", request.Model)
136
  return vertexClaudeReq, nil
137
  } else if a.RequestMode == RequestModeGemini {
138
  geminiRequest, err := gemini.CovertGemini2OpenAI(*request)
@@ -156,7 +157,6 @@ func (a *Adaptor) ConvertEmbeddingRequest(c *gin.Context, info *relaycommon.Rela
156
  return nil, errors.New("not implemented")
157
  }
158
 
159
-
160
  func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
161
  return channel.DoApiRequest(a, c, info, requestBody)
162
  }
 
28
  "claude-3-opus-20240229": "claude-3-opus@20240229",
29
  "claude-3-haiku-20240307": "claude-3-haiku@20240307",
30
  "claude-3-5-sonnet-20240620": "claude-3-5-sonnet@20240620",
31
+ "claude-3-7-sonnet-20250219": "claude-3-7-sonnet@20250219",
32
  }
33
 
34
  const anthropicVersion = "vertex-2023-10-16"
 
133
  if err = copier.Copy(vertexClaudeReq, claudeReq); err != nil {
134
  return nil, errors.New("failed to copy claude request")
135
  }
136
+ c.Set("request_model", claudeReq.Model)
137
  return vertexClaudeReq, nil
138
  } else if a.RequestMode == RequestModeGemini {
139
  geminiRequest, err := gemini.CovertGemini2OpenAI(*request)
 
157
  return nil, errors.New("not implemented")
158
  }
159
 
 
160
  func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (any, error) {
161
  return channel.DoApiRequest(a, c, info, requestBody)
162
  }
relay/common/relay_info.go CHANGED
@@ -13,23 +13,24 @@ import (
13
  )
14
 
15
  type RelayInfo struct {
16
- ChannelType int
17
- ChannelId int
18
- TokenId int
19
- TokenKey string
20
- UserId int
21
- Group string
22
- TokenUnlimited bool
23
- StartTime time.Time
24
- FirstResponseTime time.Time
25
- setFirstResponse bool
26
- ApiType int
27
- IsStream bool
28
- IsPlayground bool
29
- UsePrice bool
30
- RelayMode int
31
- UpstreamModelName string
32
- OriginModelName string
 
33
  //RecodeModelName string
34
  RequestURLPath string
35
  ApiVersion string
@@ -49,6 +50,9 @@ type RelayInfo struct {
49
  AudioUsage bool
50
  ReasoningEffort string
51
  ChannelSetting map[string]interface{}
 
 
 
52
  }
53
 
54
  // 定义支持流式选项的通道类型
@@ -88,6 +92,10 @@ func GenRelayInfo(c *gin.Context) *RelayInfo {
88
  apiType, _ := relayconstant.ChannelType2APIType(channelType)
89
 
90
  info := &RelayInfo{
 
 
 
 
91
  RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
92
  BaseUrl: c.GetString("base_url"),
93
  RequestURLPath: c.Request.URL.String(),
@@ -139,26 +147,14 @@ func (info *RelayInfo) SetIsStream(isStream bool) {
139
  }
140
 
141
  func (info *RelayInfo) SetFirstResponseTime() {
142
- if !info.setFirstResponse {
143
  info.FirstResponseTime = time.Now()
144
- info.setFirstResponse = true
145
  }
146
  }
147
 
148
  type TaskRelayInfo struct {
149
- ChannelType int
150
- ChannelId int
151
- TokenId int
152
- UserId int
153
- Group string
154
- StartTime time.Time
155
- ApiType int
156
- RelayMode int
157
- UpstreamModelName string
158
- RequestURLPath string
159
- ApiKey string
160
- BaseUrl string
161
-
162
  Action string
163
  OriginTaskID string
164
 
@@ -166,48 +162,8 @@ type TaskRelayInfo struct {
166
  }
167
 
168
  func GenTaskRelayInfo(c *gin.Context) *TaskRelayInfo {
169
- channelType := c.GetInt("channel_type")
170
- channelId := c.GetInt("channel_id")
171
-
172
- tokenId := c.GetInt("token_id")
173
- userId := c.GetInt("id")
174
- group := c.GetString("group")
175
- startTime := time.Now()
176
-
177
- apiType, _ := relayconstant.ChannelType2APIType(channelType)
178
-
179
  info := &TaskRelayInfo{
180
- RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
181
- BaseUrl: c.GetString("base_url"),
182
- RequestURLPath: c.Request.URL.String(),
183
- ChannelType: channelType,
184
- ChannelId: channelId,
185
- TokenId: tokenId,
186
- UserId: userId,
187
- Group: group,
188
- StartTime: startTime,
189
- ApiType: apiType,
190
- ApiKey: strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
191
- }
192
- if info.BaseUrl == "" {
193
- info.BaseUrl = common.ChannelBaseURLs[channelType]
194
  }
195
  return info
196
  }
197
-
198
- func (info *TaskRelayInfo) ToRelayInfo() *RelayInfo {
199
- return &RelayInfo{
200
- ChannelType: info.ChannelType,
201
- ChannelId: info.ChannelId,
202
- TokenId: info.TokenId,
203
- UserId: info.UserId,
204
- Group: info.Group,
205
- StartTime: info.StartTime,
206
- ApiType: info.ApiType,
207
- RelayMode: info.RelayMode,
208
- UpstreamModelName: info.UpstreamModelName,
209
- RequestURLPath: info.RequestURLPath,
210
- ApiKey: info.ApiKey,
211
- BaseUrl: info.BaseUrl,
212
- }
213
- }
 
13
  )
14
 
15
  type RelayInfo struct {
16
+ ChannelType int
17
+ ChannelId int
18
+ TokenId int
19
+ TokenKey string
20
+ UserId int
21
+ Group string
22
+ TokenUnlimited bool
23
+ StartTime time.Time
24
+ FirstResponseTime time.Time
25
+ IsFirstResponse bool
26
+ SendLastReasoningResponse bool
27
+ ApiType int
28
+ IsStream bool
29
+ IsPlayground bool
30
+ UsePrice bool
31
+ RelayMode int
32
+ UpstreamModelName string
33
+ OriginModelName string
34
  //RecodeModelName string
35
  RequestURLPath string
36
  ApiVersion string
 
50
  AudioUsage bool
51
  ReasoningEffort string
52
  ChannelSetting map[string]interface{}
53
+ UserSetting map[string]interface{}
54
+ UserEmail string
55
+ UserQuota int
56
  }
57
 
58
  // 定义支持流式选项的通道类型
 
92
  apiType, _ := relayconstant.ChannelType2APIType(channelType)
93
 
94
  info := &RelayInfo{
95
+ UserQuota: c.GetInt(constant.ContextKeyUserQuota),
96
+ UserSetting: c.GetStringMap(constant.ContextKeyUserSetting),
97
+ UserEmail: c.GetString(constant.ContextKeyUserEmail),
98
+ IsFirstResponse: true,
99
  RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
100
  BaseUrl: c.GetString("base_url"),
101
  RequestURLPath: c.Request.URL.String(),
 
147
  }
148
 
149
  func (info *RelayInfo) SetFirstResponseTime() {
150
+ if info.IsFirstResponse {
151
  info.FirstResponseTime = time.Now()
152
+ info.IsFirstResponse = false
153
  }
154
  }
155
 
156
  type TaskRelayInfo struct {
157
+ *RelayInfo
 
 
 
 
 
 
 
 
 
 
 
 
158
  Action string
159
  OriginTaskID string
160
 
 
162
  }
163
 
164
  func GenTaskRelayInfo(c *gin.Context) *TaskRelayInfo {
 
 
 
 
 
 
 
 
 
 
165
  info := &TaskRelayInfo{
166
+ RelayInfo: GenRelayInfo(c),
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  }
168
  return info
169
  }