@@ -97,7 +97,6 @@ class ProviderGoogleGenAI(Provider):
if proxy :
async_client_kwargs [ " proxy " ] = proxy
async_client_kwargs [ " trust_env " ] = False
logger . info ( " [Gemini] 使用代理 " )
else :
async_client_kwargs [ " trust_env " ] = True
@@ -137,15 +136,18 @@ class ProviderGoogleGenAI(Provider):
keys . remove ( self . chosen_api_key )
if len ( keys ) > 0 :
self . set_key ( random . choice ( keys ) )
logger . info (
f " 检测到 Key 异常( { e . message } ),正在尝试更换 API K ey 重试... 当前 Key: { self . chosen_api_key [ : 12 ] } ... " ,
logger . warning (
" Retrying with a different API k ey due to detected key issue: %s . Current key: %s ... " ,
e . message ,
self . chosen_api_key [ : 12 ] ,
)
await asyncio . sleep ( 1 )
return True
logger . error (
f " 检测到 Key 异常( { e . message } ),且已没有可用的 Key。 当前 Key: { self . chosen_api_key [ : 12 ] } ... " ,
" No valid API keys remaining. Current key: %s ... " ,
self . chosen_api_key [ : 12 ] ,
)
raise Exception ( " 达到了 Gemini 速率限制, 请稍后再试.. ." )
raise Exception ( " Gemini API rate limit reached or API key issue detected . " )
# 连接错误处理
if is_connection_error ( e ) :
@@ -172,7 +174,9 @@ class ProviderGoogleGenAI(Provider):
self . provider_settings . get ( " streaming_response " , False )
and " IMAGE " in modalities
) :
logger . warning ( " 流式输出不支持图片模态,已自动降级为文本模态 " )
logger . warning (
" Streaming responses do not support IMAGE modality, falling back to TEXT modality. "
)
modalities = [ " TEXT " ]
tool_list : list [ types . Tool ] | None = [ ]
@@ -181,60 +185,28 @@ class ProviderGoogleGenAI(Provider):
native_search = self . provider_config . get ( " gm_native_search " , False )
url_context = self . provider_config . get ( " gm_url_context " , False )
if " gemini-2.5 " in model_name :
if native_coderunner :
tool_list . append ( types . Tool ( code_execution = types . ToolCodeExecution ( ) ) )
if native_search :
logger . warning ( " 代码执行工具与搜索工具互斥,已忽略搜索工具 " )
if url_context :
logger . warning (
" 代码执行工具与URL上下文工具互斥, 已忽略URL上下文工具 " ,
)
else :
if native_search :
tool_list . append ( types . Tool ( google_search = types . GoogleSearch ( ) ) )
if url_context :
if hasattr ( types , " UrlContext " ) :
tool_list . append ( types . Tool ( url_context = types . UrlContext ( ) ) )
else :
logger . warning (
" 当前 SDK 版本不支持 URL 上下文工具,已忽略该设置,请升级 google-genai 包 " ,
)
elif " gemini-2.0-lite " in model_name :
if " gemini-2.0-lite " in model_name :
if native_coderunner or native_search or url_context :
logger . warning (
" gemini-2.0-lite 不支持代码执行、搜索工具和URL上下文, 将忽略这些设置 " ,
" gemini-2.0-lite does not support native code execution, search, or URL context tools. These settings will be ignored. " ,
)
tool_list = None
else :
if native_coderunner :
tool_list . append ( types . Tool ( code_execution = types . ToolCodeExecution ( ) ) )
if native_search :
logger . warning ( " 代码执行工具与搜索工具互斥,已忽略搜索工具 " )
elif native_search :
if native_search :
tool_list . append ( types . Tool ( google_search = types . GoogleSearch ( ) ) )
if url_context :
tool_list . append ( types . Tool ( url_context = types . UrlContext ( ) ) )
if url_context and not native_coderunner :
if hasattr ( types , " UrlContext " ) :
tool_list . append ( types . Tool ( url_context = types . UrlContext ( ) ) )
else :
logger . warning (
" 当前 SDK 版本不支持 URL 上下文工具,已忽略该设置,请升级 google-genai 包 " ,
)
if tools :
func_desc = tools . get_func_desc_google_genai_style ( )
tool_list . append (
types . Tool ( function_declarations = func_desc [ " function_declarations " ] ) ,
)
if not tool_list :
tool_list = None
if tools and tool_list :
logger . warning ( " 已启用原生工具,函数工具将被忽略 " )
elif tools and ( func_desc := tools . get_func_desc_google_genai_style ( ) ) :
tool_list = [
types . Tool ( function_declarations = func_desc [ " function_declarations " ] ) ,
]
tool_config = None
has_func_decl = tool_list and any ( t . function_declarations for t in tool_list )
if has_func_decl :
@@ -322,7 +294,7 @@ class ProviderGoogleGenAI(Provider):
def create_text_part ( text : str ) - > types . Part :
content_a = text if text else " "
if not text :
logger . warning ( " 文本内容为空,已添加空格占位 " )
logger . warning ( " Text content is empty, added a space as placeholder. " )
return types . Part . from_text ( text = content_a )
def process_image_url ( image_url_dict : dict ) - > types . Part :
@@ -349,12 +321,6 @@ class ProviderGoogleGenAI(Provider):
contents . append ( content_cls ( parts = part ) )
gemini_contents : list [ types . Content ] = [ ]
native_tool_enabled = any (
[
self . provider_config . get ( " gm_native_coderunner " , False ) ,
self . provider_config . get ( " gm_native_search " , False ) ,
] ,
)
for message in payloads [ " messages " ] :
role , content = message [ " role " ] , message . get ( " content " )
@@ -377,11 +343,10 @@ class ProviderGoogleGenAI(Provider):
append_or_extend ( gemini_contents , parts , types . UserContent )
elif role == " assistant " :
parts = [ ]
if isinstance ( content , str ) :
parts = [ types . Part . from_text ( text = content ) ]
append_or_extend ( gemini_contents , parts , types . ModelContent )
parts . append ( types . Part . from_text ( text = content ) )
elif isinstance ( content , list ) :
parts = [ ]
thinking_signature = None
text = " "
for part in content :
@@ -400,16 +365,31 @@ class ProviderGoogleGenAI(Provider):
exc_info = True ,
)
thinking_signature = None
parts . append (
types . Part (
text = text ,
thought_signature = thinking_signature ,
)
)
append_or_extend ( gemini_contents , parts , types . ModelContent )
elif not native_tool_enabled and " tool_calls " in message :
parts = [ ]
if (
not text
and thinking_signature
and " tool_calls " in message
and any (
isinstance ( tool , dict )
and isinstance ( tool . get ( " extra_content " ) , dict )
and isinstance ( tool [ " extra_content " ] . get ( " google " ) , dict )
and tool [ " extra_content " ] [ " google " ] . get ( " thought_signature " )
for tool in message [ " tool_calls " ]
)
) :
# If the main content is empty but tool calls have thought signatures,
# skip adding an empty text part to deduplicate the thinking signature in the main content and tool calls.
pass
else :
parts . append (
types . Part (
text = text ,
thought_signature = thinking_signature ,
)
)
if " tool_calls " in message :
for tool in message [ " tool_calls " ] :
part = types . Part . from_function_call (
name = tool [ " function " ] [ " name " ] ,
@@ -427,17 +407,13 @@ class ProviderGoogleGenAI(Provider):
if ts_bs64 :
part . thought_signature = base64 . b64decode ( ts_bs64 )
parts . append ( part )
append_or_extend ( gemini_contents , parts , types . ModelContent )
else :
logger . warning ( " assistant 角色的消息内容为空,已添加空格占位 " )
if native_tool_enabled and " tool_calls " in message :
logger . warning (
" 检测到启用Gemini原生工具, 且上下文中存在函数调用, 建议使用 /reset 重置上下文 " ,
)
parts = [ types . Part . from_text ( text = " " ) ]
append_or_extend ( gemini_contents , parts , types . ModelContent )
elif role == " tool " and not native_tool_enabled :
if not parts :
parts = [ types . Part . from_text ( text = " " ) ]
append_or_extend ( gemini_contents , parts , types . ModelContent )
elif role == " tool " :
func_name = message . get ( " name " , message [ " tool_call_id " ] )
part = types . Part . from_function_response (
name = func_name ,
@@ -501,7 +477,7 @@ class ProviderGoogleGenAI(Provider):
) - > MessageChain :
""" 处理内容部分并构建消息链 """
if not candidate . content :
logger . warning ( f " 收到的 candidate.content 为空 : { candidate } " )
logger . warning ( f " Gemini candidate.content is empty : { candidate } " )
if validate_output :
raise EmptyModelOutputError (
" Gemini candidate content is empty. "
@@ -514,22 +490,22 @@ class ProviderGoogleGenAI(Provider):
result_parts : list [ types . Part ] | None = candidate . content . parts
if finish_reason == types . FinishReason . SAFETY :
raise Exception ( " 模型生成内容未通过 Gemini 平台的安全检查 " )
raise Exception ( " The model output failed Gemini platform safety checks. " )
if finish_reason in {
types . FinishReason . PROHIBITED_CONTENT ,
types . FinishReason . SPII ,
types . FinishReason . BLOCKLIST ,
} :
raise Exception ( " 模型生成内容违反 Gemini 平台政策 " )
raise Exception ( " The model output violates Gemini platform policy. " )
# 防止旧版本SDK不存在IMAGE_SAFETY
if hasattr ( types . FinishReason , " IMAGE_SAFETY " ) :
if finish_reason == types . FinishReason . IMAGE_SAFETY :
raise Exception ( " 模型生成内容违反 Gemini 平台政策 " )
raise Exception ( " The model output violates Gemini platform policy. " )
if not result_parts :
logger . warning ( f " 收到的 candidate.content.parts 为空 : { candidate } " )
logger . warning ( f " Gemini candidate.content.parts is empty : { candidate } " )
if validate_output :
raise EmptyModelOutputError (
" Gemini candidate content parts are empty. "
@@ -636,15 +612,19 @@ class ProviderGoogleGenAI(Provider):
logger . debug ( f " genai result: { result } " )
if not result . candidates :
logger . error ( f " 请求失败, 返回的 candidates 为空: { result } " )
raise Exception ( " 请求失败, 返回的 candidates 为空。 " )
logger . error (
f " Gemini request failed: candidates is empty: { result } "
)
raise Exception ( " Gemini request failed: candidates is empty. " )
if result . candidates [ 0 ] . finish_reason == types . FinishReason . RECITATION :
if temperature > 2 :
raise Exception ( " 温度参数已超过最大值2, 仍然发生recitation " )
raise Exception (
" Temperature exceeded the maximum value of 2, but Gemini recitation still occurred. "
)
temperature + = 0.2
logger . warning (
f " 发生了 recitation,正在提高温度至 { temperature : .1f } 重试 ..." ,
f " Gemini recitation detected; increasing temperature to { temperature : .1f } and retrying ..." ,
)
continue
@@ -655,11 +635,13 @@ class ProviderGoogleGenAI(Provider):
e . message = " "
if " Developer instruction is not enabled " in e . message :
logger . warning (
f " { model } 不支持 system prompt, 已自动去除(影响人格设置) " ,
f " { model } does not support system prompts; removing it automatically. This may affect persona settings. " ,
)
system_instruction = None
elif " Function calling is not enabled " in e . message :
logger . warning ( f " { model } 不支持函数调用,已自动去除 " )
logger . warning (
f " { model } does not support function calling; removing tools automatically. "
)
tools = None
elif (
" Multi-modal output is not supported " in e . message
@@ -668,7 +650,7 @@ class ProviderGoogleGenAI(Provider):
or " only supports text output " in e . message
) :
logger . warning (
f " { model } 不支持多模态输出,降级为文本模态 " ,
f " { model } does not support multimodal output; falling back to TEXT modality. " ,
)
modalities = [ " TEXT " ]
else :
@@ -719,11 +701,13 @@ class ProviderGoogleGenAI(Provider):
e . message = " "
if " Developer instruction is not enabled " in e . message :
logger . warning (
f " { model } 不支持 system prompt, 已自动去除(影响人格设置) " ,
f " { model } does not support system prompts; removing it automatically. This may affect persona settings. " ,
)
system_instruction = None
elif " Function calling is not enabled " in e . message :
logger . warning ( f " { model } 不支持函数调用,已自动去除 " )
logger . warning (
f " { model } does not support function calling; removing tools automatically. "
)
tools = None
else :
raise
@@ -738,10 +722,10 @@ class ProviderGoogleGenAI(Provider):
llm_response = LLMResponse ( " assistant " , is_chunk = True )
if not chunk . candidates :
logger . warning ( f " 收到的 chunk 中 candidates 为空 : { chunk } " )
logger . warning ( f " Gemini stream chunk has empty candidates: { chunk } " )
continue
if not chunk . candidates [ 0 ] . content :
logger . warning ( f " 收到的 chunk 中 content 为空 : { chunk } " )
logger . warning ( f " Gemini stream chunk has empty content: { chunk } " )
continue
if chunk . candidates [ 0 ] . content . parts and any (
@@ -872,7 +856,7 @@ class ProviderGoogleGenAI(Provider):
continue
break
raise Exception ( " 请求失败。 " )
raise Exception ( " Gemini request failed. " )
async def text_chat_stream (
self ,
@@ -947,7 +931,7 @@ class ProviderGoogleGenAI(Provider):
and m . name
]
except APIError as e :
raise Exception ( f " 获取模型列表失败 : { e . message } " )
raise Exception ( f " Failed to fetch Gemini model list : { e . message } " )
def get_current_key ( self ) - > str :
return self . chosen_api_key
@@ -974,7 +958,7 @@ class ProviderGoogleGenAI(Provider):
media_type = " image " ,
)
if not image_data :
logger . warning ( " 图片预处理结果为空,将忽略。 " )
logger . warning ( " Image preprocessing returned no data; ignoring it. " )
return None
return {
" type " : " image_url " ,
@@ -989,11 +973,13 @@ class ProviderGoogleGenAI(Provider):
strict = True ,
)
except Exception as exc :
logger . warning ( " 音频预处理失败,将忽略。错误: %s " , exc )
logger . warning (
" Audio preprocessing failed; ignoring it. Error: %s " , exc
)
return None
if not audio_data :
logger . warning ( " 音频预处理结果为空,将忽略。 " )
logger . warning ( " Audio preprocessing returned no data; ignoring it. " )
return None
return {
" type " : " audio_url " ,
@@ -1029,7 +1015,9 @@ class ProviderGoogleGenAI(Provider):
if audio_part :
content_blocks . append ( audio_part )
else :
raise ValueError ( f " 不支持的额外内容块类型: { type ( part ) } " )
raise ValueError (
f " Unsupported extra content part type: { type ( part ) } "
)
# 3. 图片内容
if image_urls :