2
2
OpenAI Transfer Module - Handles conversion between OpenAI and Gemini API formats
3
3
被openai-router调用,负责OpenAI格式与Gemini格式的双向转换
4
4
"""
5
- import json
6
5
import time
7
6
import uuid
8
7
from typing import Dict , Any
@@ -67,51 +66,8 @@ async def openai_request_to_gemini_payload(openai_request: ChatCompletionRequest
67
66
if role == "assistant" :
68
67
role = "model"
69
68
70
- # 处理工具调用响应消息(role为tool)
71
- if role == "tool" and message .tool_call_id and message .content :
72
- # 工具调用结果消息转换为Gemini格式
73
- # 注意:这里我们需要从message.name中获取函数名,如果没有则跳过
74
- function_name = getattr (message , 'name' , None )
75
- if not function_name :
76
- log .error (f"Tool response message missing function name for tool_call_id: { message .tool_call_id } " )
77
- log .error ("This will cause a 400 error from Gemini API: 'please ensure that the number of function response parts is equal to the number of function call parts'" )
78
- log .error ("Solution: Include 'name' field in tool messages matching the original function name" )
79
- continue
80
-
81
- parts = [{
82
- "functionResponse" : {
83
- "name" : function_name ,
84
- "response" : {"result" : message .content }
85
- }
86
- }]
87
- contents .append ({"role" : "function" , "parts" : parts })
88
- log .debug (f"Added tool response to contents: function={ function_name } , tool_call_id={ message .tool_call_id } " )
89
- continue
90
-
91
- # 处理工具调用(从assistant消息中的tool_calls)
92
- if hasattr (message , 'tool_calls' ) and message .tool_calls :
93
- # 如果有工具调用,优先处理工具调用,忽略文本内容
94
- tool_parts = []
95
- for tool_call in message .tool_calls :
96
- if tool_call .type == "function" :
97
- try :
98
- args = json .loads (tool_call .function .arguments )
99
- except json .JSONDecodeError :
100
- args = {}
101
-
102
- tool_parts .append ({
103
- "functionCall" : {
104
- "name" : tool_call .function .name ,
105
- "args" : args
106
- }
107
- })
108
-
109
- if tool_parts :
110
- contents .append ({"role" : role , "parts" : tool_parts })
111
- log .debug (f"Added tool calls to contents: { [tc .function .name for tc in message .tool_calls ]} " )
112
-
113
- # 处理普通内容(只有在没有工具调用的情况下)
114
- elif isinstance (message .content , list ):
69
+ # 处理普通内容
70
+ if isinstance (message .content , list ):
115
71
parts = []
116
72
for part in message .content :
117
73
if part .get ("type" ) == "text" :
@@ -191,77 +147,10 @@ async def openai_request_to_gemini_payload(openai_request: ChatCompletionRequest
191
147
"thinkingBudget" : thinking_budget ,
192
148
"includeThoughts" : should_include_thoughts (openai_request .model )
193
149
}
194
-
195
- # 处理工具定义转换(OpenAI tools -> Gemini tools)
196
- if openai_request .tools :
197
- function_declarations = []
198
- for tool in openai_request .tools :
199
- if tool .type == "function" :
200
- # 清理parameters中的additionalProperties字段,Gemini API不支持
201
- parameters = tool .function .parameters .copy () if tool .function .parameters else {}
202
- if "additionalProperties" in parameters :
203
- del parameters ["additionalProperties" ]
204
-
205
- # 递归清理嵌套对象中的additionalProperties
206
- def clean_additional_properties (obj ):
207
- if isinstance (obj , dict ):
208
- # 删除当前层级的additionalProperties
209
- if "additionalProperties" in obj :
210
- del obj ["additionalProperties" ]
211
- # 递归处理嵌套的对象
212
- for key , value in obj .items ():
213
- if isinstance (value , dict ):
214
- clean_additional_properties (value )
215
- elif isinstance (value , list ):
216
- for item in value :
217
- if isinstance (item , dict ):
218
- clean_additional_properties (item )
219
-
220
- clean_additional_properties (parameters )
221
-
222
- function_def = {
223
- "name" : tool .function .name ,
224
- "description" : tool .function .description ,
225
- "parameters" : parameters
226
- }
227
- function_declarations .append (function_def )
228
-
229
- # 所有函数声明放在一个工具对象中
230
- if function_declarations :
231
- request_data ["tools" ] = [{"functionDeclarations" : function_declarations }]
232
-
233
- # 处理工具选择转换(OpenAI tool_choice -> Gemini toolConfig)
234
- if openai_request .tool_choice :
235
- if isinstance (openai_request .tool_choice , str ):
236
- if openai_request .tool_choice == "auto" :
237
- # Gemini默认就是auto模式,无需特殊设置
238
- pass
239
- elif openai_request .tool_choice == "required" :
240
- request_data ["toolConfig" ] = {"functionCallingConfig" : {"mode" : "ANY" }}
241
- elif openai_request .tool_choice == "none" :
242
- request_data ["toolConfig" ] = {"functionCallingConfig" : {"mode" : "NONE" }}
243
- elif isinstance (openai_request .tool_choice , dict ) and openai_request .tool_choice .get ("type" ) == "function" :
244
- # 强制调用特定函数
245
- function_name = openai_request .tool_choice .get ("function" , {}).get ("name" )
246
- if function_name :
247
- request_data ["toolConfig" ] = {
248
- "functionCallingConfig" : {
249
- "mode" : "ANY" ,
250
- "allowedFunctionNames" : [function_name ]
251
- }
252
- }
253
150
254
- # 为搜索模型添加Google Search工具(只有在没有functionDeclarations时才添加)
151
+ # 为搜索模型添加Google Search工具
255
152
if is_search_model (openai_request .model ):
256
- if "tools" not in request_data :
257
- request_data ["tools" ] = []
258
- request_data ["tools" ].append ({"googleSearch" : {}})
259
- elif not any ("functionDeclarations" in tool for tool in request_data ["tools" ]):
260
- # 只有在没有函数工具时才添加googleSearch
261
- request_data ["tools" ].append ({"googleSearch" : {}})
262
- else :
263
- # 如果已有函数工具,不添加googleSearch(Gemini API不支持混合工具类型)
264
- log .debug ("Skipping googleSearch tool because functionDeclarations are present" )
153
+ request_data ["tools" ] = [{"googleSearch" : {}}]
265
154
266
155
# 移除None值
267
156
request_data = {k : v for k , v in request_data .items () if v is not None }
@@ -273,10 +162,9 @@ def clean_additional_properties(obj):
273
162
}
274
163
275
164
def _extract_content_and_reasoning (parts : list ) -> tuple :
276
- """从Gemini响应部件中提取内容、推理内容和函数调用 """
165
+ """从Gemini响应部件中提取内容和推理内容 """
277
166
content = ""
278
167
reasoning_content = ""
279
- tool_calls = []
280
168
281
169
for part in parts :
282
170
# 处理文本内容
@@ -286,35 +174,16 @@ def _extract_content_and_reasoning(parts: list) -> tuple:
286
174
reasoning_content += part .get ("text" , "" )
287
175
else :
288
176
content += part .get ("text" , "" )
289
-
290
- # 处理函数调用
291
- elif part .get ("functionCall" ):
292
- function_call = part ["functionCall" ]
293
- tool_call = {
294
- "id" : f"call_{ uuid .uuid4 ().hex [:8 ]} " ,
295
- "type" : "function" ,
296
- "function" : {
297
- "name" : function_call .get ("name" , "" ),
298
- "arguments" : json .dumps (function_call .get ("args" , {}))
299
- }
300
- }
301
- tool_calls .append (tool_call )
302
177
303
- return content , reasoning_content , tool_calls
178
+ return content , reasoning_content
304
179
305
- def _build_message_with_reasoning (role : str , content : str , reasoning_content : str , tool_calls : list = None ) -> dict :
306
- """构建包含可选推理内容和工具调用的消息对象 """
180
+ def _build_message_with_reasoning (role : str , content : str , reasoning_content : str ) -> dict :
181
+ """构建包含可选推理内容的消息对象 """
307
182
message = {
308
183
"role" : role ,
184
+ "content" : content
309
185
}
310
186
311
- # 如果有工具调用,设置content为None,否则设置文本内容
312
- if tool_calls :
313
- message ["tool_calls" ] = tool_calls
314
- message ["content" ] = None if not content .strip () else content
315
- else :
316
- message ["content" ] = content
317
-
318
187
# 如果有thinking tokens,添加reasoning_content
319
188
if reasoning_content :
320
189
message ["reasoning_content" ] = reasoning_content
@@ -341,12 +210,12 @@ def gemini_response_to_openai(gemini_response: Dict[str, Any], model: str) -> Di
341
210
if role == "model" :
342
211
role = "assistant"
343
212
344
- # 提取并分离thinking tokens、常规内容和工具调用
213
+ # 提取并分离thinking tokens和常规内容
345
214
parts = candidate .get ("content" , {}).get ("parts" , [])
346
- content , reasoning_content , tool_calls = _extract_content_and_reasoning (parts )
215
+ content , reasoning_content = _extract_content_and_reasoning (parts )
347
216
348
217
# 构建消息对象
349
- message = _build_message_with_reasoning (role , content , reasoning_content , tool_calls )
218
+ message = _build_message_with_reasoning (role , content , reasoning_content )
350
219
351
220
choices .append ({
352
221
"index" : candidate .get ("index" , 0 ),
@@ -383,18 +252,16 @@ def gemini_stream_chunk_to_openai(gemini_chunk: Dict[str, Any], model: str, resp
383
252
if role == "model" :
384
253
role = "assistant"
385
254
386
- # 提取并分离thinking tokens、常规内容和工具调用
255
+ # 提取并分离thinking tokens和常规内容
387
256
parts = candidate .get ("content" , {}).get ("parts" , [])
388
- content , reasoning_content , tool_calls = _extract_content_and_reasoning (parts )
257
+ content , reasoning_content = _extract_content_and_reasoning (parts )
389
258
390
259
# 构建delta对象
391
260
delta = {}
392
261
if content :
393
262
delta ["content" ] = content
394
263
if reasoning_content :
395
264
delta ["reasoning_content" ] = reasoning_content
396
- if tool_calls :
397
- delta ["tool_calls" ] = tool_calls
398
265
399
266
choices .append ({
400
267
"index" : candidate .get ("index" , 0 ),
0 commit comments