AstrBot 3.5.6__py3-none-any.whl → 4.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrbot/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,7 +3,6 @@ import base64
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import random
|
|
6
|
-
from typing import Dict, List, Optional
|
|
7
6
|
from collections.abc import AsyncGenerator
|
|
8
7
|
|
|
9
8
|
from google import genai
|
|
@@ -12,11 +11,10 @@ from google.genai.errors import APIError
|
|
|
12
11
|
|
|
13
12
|
import astrbot.core.message.components as Comp
|
|
14
13
|
from astrbot import logger
|
|
15
|
-
from astrbot.api.provider import
|
|
16
|
-
from astrbot.core.db import BaseDatabase
|
|
14
|
+
from astrbot.api.provider import Provider
|
|
17
15
|
from astrbot.core.message.message_event_result import MessageChain
|
|
18
16
|
from astrbot.core.provider.entities import LLMResponse
|
|
19
|
-
from astrbot.core.provider.func_tool_manager import
|
|
17
|
+
from astrbot.core.provider.func_tool_manager import ToolSet
|
|
20
18
|
from astrbot.core.utils.io import download_image_by_url
|
|
21
19
|
|
|
22
20
|
from ..register import register_provider_adapter
|
|
@@ -33,7 +31,8 @@ logging.getLogger("google_genai.types").addFilter(SuppressNonTextPartsWarning())
|
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
@register_provider_adapter(
|
|
36
|
-
"googlegenai_chat_completion",
|
|
34
|
+
"googlegenai_chat_completion",
|
|
35
|
+
"Google Gemini Chat Completion 提供商适配器",
|
|
37
36
|
)
|
|
38
37
|
class ProviderGoogleGenAI(Provider):
|
|
39
38
|
CATEGORY_MAPPING = {
|
|
@@ -52,24 +51,18 @@ class ProviderGoogleGenAI(Provider):
|
|
|
52
51
|
|
|
53
52
|
def __init__(
|
|
54
53
|
self,
|
|
55
|
-
provider_config
|
|
56
|
-
provider_settings
|
|
57
|
-
db_helper: BaseDatabase,
|
|
58
|
-
persistant_history=True,
|
|
59
|
-
default_persona: Personality = None,
|
|
54
|
+
provider_config,
|
|
55
|
+
provider_settings,
|
|
60
56
|
) -> None:
|
|
61
57
|
super().__init__(
|
|
62
58
|
provider_config,
|
|
63
59
|
provider_settings,
|
|
64
|
-
persistant_history,
|
|
65
|
-
db_helper,
|
|
66
|
-
default_persona,
|
|
67
60
|
)
|
|
68
|
-
self.api_keys:
|
|
69
|
-
self.chosen_api_key: str = self.api_keys[0] if len(self.api_keys) > 0 else
|
|
61
|
+
self.api_keys: list = super().get_keys()
|
|
62
|
+
self.chosen_api_key: str = self.api_keys[0] if len(self.api_keys) > 0 else ""
|
|
70
63
|
self.timeout: int = int(provider_config.get("timeout", 180))
|
|
71
64
|
|
|
72
|
-
self.api_base:
|
|
65
|
+
self.api_base: str | None = provider_config.get("api_base", None)
|
|
73
66
|
if self.api_base and self.api_base.endswith("/"):
|
|
74
67
|
self.api_base = self.api_base[:-1]
|
|
75
68
|
|
|
@@ -92,41 +85,43 @@ class ProviderGoogleGenAI(Provider):
|
|
|
92
85
|
user_safety_config = self.provider_config.get("gm_safety_settings", {})
|
|
93
86
|
self.safety_settings = [
|
|
94
87
|
types.SafetySetting(
|
|
95
|
-
category=harm_category,
|
|
88
|
+
category=harm_category,
|
|
89
|
+
threshold=self.THRESHOLD_MAPPING[threshold_str],
|
|
96
90
|
)
|
|
97
91
|
for config_key, harm_category in self.CATEGORY_MAPPING.items()
|
|
98
92
|
if (threshold_str := user_safety_config.get(config_key))
|
|
99
93
|
and threshold_str in self.THRESHOLD_MAPPING
|
|
100
94
|
]
|
|
101
95
|
|
|
102
|
-
async def _handle_api_error(self, e: APIError, keys:
|
|
96
|
+
async def _handle_api_error(self, e: APIError, keys: list[str]) -> bool:
|
|
103
97
|
"""处理API错误,返回是否需要重试"""
|
|
98
|
+
if e.message is None:
|
|
99
|
+
e.message = ""
|
|
100
|
+
|
|
104
101
|
if e.code == 429 or "API key not valid" in e.message:
|
|
105
102
|
keys.remove(self.chosen_api_key)
|
|
106
103
|
if len(keys) > 0:
|
|
107
104
|
self.set_key(random.choice(keys))
|
|
108
105
|
logger.info(
|
|
109
|
-
f"检测到 Key 异常({e.message}),正在尝试更换 API Key 重试... 当前 Key: {self.chosen_api_key[:12]}..."
|
|
106
|
+
f"检测到 Key 异常({e.message}),正在尝试更换 API Key 重试... 当前 Key: {self.chosen_api_key[:12]}...",
|
|
110
107
|
)
|
|
111
108
|
await asyncio.sleep(1)
|
|
112
109
|
return True
|
|
113
|
-
else:
|
|
114
|
-
logger.error(
|
|
115
|
-
f"检测到 Key 异常({e.message}),且已没有可用的 Key。 当前 Key: {self.chosen_api_key[:12]}..."
|
|
116
|
-
)
|
|
117
|
-
raise Exception("达到了 Gemini 速率限制, 请稍后再试...")
|
|
118
|
-
else:
|
|
119
110
|
logger.error(
|
|
120
|
-
f"
|
|
111
|
+
f"检测到 Key 异常({e.message}),且已没有可用的 Key。 当前 Key: {self.chosen_api_key[:12]}...",
|
|
121
112
|
)
|
|
122
|
-
raise
|
|
113
|
+
raise Exception("达到了 Gemini 速率限制, 请稍后再试...")
|
|
114
|
+
logger.error(
|
|
115
|
+
f"发生了错误(gemini_source)。Provider 配置如下: {self.provider_config}",
|
|
116
|
+
)
|
|
117
|
+
raise e
|
|
123
118
|
|
|
124
119
|
async def _prepare_query_config(
|
|
125
120
|
self,
|
|
126
121
|
payloads: dict,
|
|
127
|
-
tools:
|
|
128
|
-
system_instruction:
|
|
129
|
-
modalities:
|
|
122
|
+
tools: ToolSet | None = None,
|
|
123
|
+
system_instruction: str | None = None,
|
|
124
|
+
modalities: list[str] | None = None,
|
|
130
125
|
temperature: float = 0.7,
|
|
131
126
|
) -> types.GenerateContentConfig:
|
|
132
127
|
"""准备查询配置"""
|
|
@@ -141,24 +136,66 @@ class ProviderGoogleGenAI(Provider):
|
|
|
141
136
|
logger.warning("流式输出不支持图片模态,已自动降级为文本模态")
|
|
142
137
|
modalities = ["Text"]
|
|
143
138
|
|
|
144
|
-
tool_list =
|
|
139
|
+
tool_list = []
|
|
140
|
+
model_name = self.get_model()
|
|
145
141
|
native_coderunner = self.provider_config.get("gm_native_coderunner", False)
|
|
146
142
|
native_search = self.provider_config.get("gm_native_search", False)
|
|
143
|
+
url_context = self.provider_config.get("gm_url_context", False)
|
|
144
|
+
|
|
145
|
+
if "gemini-2.5" in model_name:
|
|
146
|
+
if native_coderunner:
|
|
147
|
+
tool_list.append(types.Tool(code_execution=types.ToolCodeExecution()))
|
|
148
|
+
if native_search:
|
|
149
|
+
logger.warning("代码执行工具与搜索工具互斥,已忽略搜索工具")
|
|
150
|
+
if url_context:
|
|
151
|
+
logger.warning(
|
|
152
|
+
"代码执行工具与URL上下文工具互斥,已忽略URL上下文工具",
|
|
153
|
+
)
|
|
154
|
+
else:
|
|
155
|
+
if native_search:
|
|
156
|
+
tool_list.append(types.Tool(google_search=types.GoogleSearch()))
|
|
147
157
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
if url_context:
|
|
159
|
+
if hasattr(types, "UrlContext"):
|
|
160
|
+
tool_list.append(types.Tool(url_context=types.UrlContext()))
|
|
161
|
+
else:
|
|
162
|
+
logger.warning(
|
|
163
|
+
"当前 SDK 版本不支持 URL 上下文工具,已忽略该设置,请升级 google-genai 包",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
elif "gemini-2.0-lite" in model_name:
|
|
167
|
+
if native_coderunner or native_search or url_context:
|
|
168
|
+
logger.warning(
|
|
169
|
+
"gemini-2.0-lite 不支持代码执行、搜索工具和URL上下文,将忽略这些设置",
|
|
170
|
+
)
|
|
171
|
+
tool_list = None
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
if native_coderunner:
|
|
175
|
+
tool_list.append(types.Tool(code_execution=types.ToolCodeExecution()))
|
|
176
|
+
if native_search:
|
|
177
|
+
logger.warning("代码执行工具与搜索工具互斥,已忽略搜索工具")
|
|
178
|
+
elif native_search:
|
|
179
|
+
tool_list.append(types.Tool(google_search=types.GoogleSearch()))
|
|
180
|
+
|
|
181
|
+
if url_context and not native_coderunner:
|
|
182
|
+
if hasattr(types, "UrlContext"):
|
|
183
|
+
tool_list.append(types.Tool(url_context=types.UrlContext()))
|
|
184
|
+
else:
|
|
185
|
+
logger.warning(
|
|
186
|
+
"当前 SDK 版本不支持 URL 上下文工具,已忽略该设置,请升级 google-genai 包",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if not tool_list:
|
|
190
|
+
tool_list = None
|
|
191
|
+
|
|
192
|
+
if tools and tool_list:
|
|
193
|
+
logger.warning("已启用原生工具,函数工具将被忽略")
|
|
158
194
|
elif tools and (func_desc := tools.get_func_desc_google_genai_style()):
|
|
159
195
|
tool_list = [
|
|
160
|
-
types.Tool(function_declarations=func_desc["function_declarations"])
|
|
196
|
+
types.Tool(function_declarations=func_desc["function_declarations"]),
|
|
161
197
|
]
|
|
198
|
+
|
|
162
199
|
return types.GenerateContentConfig(
|
|
163
200
|
system_instruction=system_instruction,
|
|
164
201
|
temperature=temperature,
|
|
@@ -178,24 +215,28 @@ class ProviderGoogleGenAI(Provider):
|
|
|
178
215
|
response_modalities=modalities,
|
|
179
216
|
tools=tool_list,
|
|
180
217
|
safety_settings=self.safety_settings if self.safety_settings else None,
|
|
181
|
-
thinking_config=
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"
|
|
186
|
-
|
|
218
|
+
thinking_config=(
|
|
219
|
+
types.ThinkingConfig(
|
|
220
|
+
thinking_budget=min(
|
|
221
|
+
int(
|
|
222
|
+
self.provider_config.get("gm_thinking_config", {}).get(
|
|
223
|
+
"budget",
|
|
224
|
+
0,
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
24576,
|
|
187
228
|
),
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
229
|
+
)
|
|
230
|
+
if "gemini-2.5-flash" in self.get_model()
|
|
231
|
+
and hasattr(types.ThinkingConfig, "thinking_budget")
|
|
232
|
+
else None
|
|
233
|
+
),
|
|
193
234
|
automatic_function_calling=types.AutomaticFunctionCallingConfig(
|
|
194
|
-
disable=True
|
|
235
|
+
disable=True,
|
|
195
236
|
),
|
|
196
237
|
)
|
|
197
238
|
|
|
198
|
-
def _prepare_conversation(self, payloads:
|
|
239
|
+
def _prepare_conversation(self, payloads: dict) -> list[types.Content]:
|
|
199
240
|
"""准备 Gemini SDK 的 Content 列表"""
|
|
200
241
|
|
|
201
242
|
def create_text_part(text: str) -> types.Part:
|
|
@@ -220,12 +261,12 @@ class ProviderGoogleGenAI(Provider):
|
|
|
220
261
|
else:
|
|
221
262
|
contents.append(content_cls(parts=part))
|
|
222
263
|
|
|
223
|
-
gemini_contents:
|
|
264
|
+
gemini_contents: list[types.Content] = []
|
|
224
265
|
native_tool_enabled = any(
|
|
225
266
|
[
|
|
226
267
|
self.provider_config.get("gm_native_coderunner", False),
|
|
227
268
|
self.provider_config.get("gm_native_search", False),
|
|
228
|
-
]
|
|
269
|
+
],
|
|
229
270
|
)
|
|
230
271
|
for message in payloads["messages"]:
|
|
231
272
|
role, content = message["role"], message.get("content")
|
|
@@ -233,9 +274,11 @@ class ProviderGoogleGenAI(Provider):
|
|
|
233
274
|
if role == "user":
|
|
234
275
|
if isinstance(content, list):
|
|
235
276
|
parts = [
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
277
|
+
(
|
|
278
|
+
types.Part.from_text(text=item["text"] or " ")
|
|
279
|
+
if item["type"] == "text"
|
|
280
|
+
else process_image_url(item["image_url"])
|
|
281
|
+
)
|
|
239
282
|
for item in content
|
|
240
283
|
]
|
|
241
284
|
else:
|
|
@@ -247,19 +290,30 @@ class ProviderGoogleGenAI(Provider):
|
|
|
247
290
|
parts = [types.Part.from_text(text=content)]
|
|
248
291
|
append_or_extend(gemini_contents, parts, types.ModelContent)
|
|
249
292
|
elif not native_tool_enabled and "tool_calls" in message:
|
|
250
|
-
parts = [
|
|
251
|
-
|
|
293
|
+
parts = []
|
|
294
|
+
for tool in message["tool_calls"]:
|
|
295
|
+
part = types.Part.from_function_call(
|
|
252
296
|
name=tool["function"]["name"],
|
|
253
297
|
args=json.loads(tool["function"]["arguments"]),
|
|
254
298
|
)
|
|
255
|
-
|
|
256
|
-
|
|
299
|
+
# we should set thought_signature back to part if exists
|
|
300
|
+
# for more info about thought_signature, see:
|
|
301
|
+
# https://ai.google.dev/gemini-api/docs/thought-signatures
|
|
302
|
+
if "extra_content" in tool and tool["extra_content"]:
|
|
303
|
+
ts_bs64 = (
|
|
304
|
+
tool["extra_content"]
|
|
305
|
+
.get("google", {})
|
|
306
|
+
.get("thought_signature")
|
|
307
|
+
)
|
|
308
|
+
if ts_bs64:
|
|
309
|
+
part.thought_signature = base64.b64decode(ts_bs64)
|
|
310
|
+
parts.append(part)
|
|
257
311
|
append_or_extend(gemini_contents, parts, types.ModelContent)
|
|
258
312
|
else:
|
|
259
313
|
logger.warning("assistant 角色的消息内容为空,已添加空格占位")
|
|
260
314
|
if native_tool_enabled and "tool_calls" in message:
|
|
261
315
|
logger.warning(
|
|
262
|
-
"检测到启用Gemini原生工具,且上下文中存在函数调用,建议使用 /reset 重置上下文"
|
|
316
|
+
"检测到启用Gemini原生工具,且上下文中存在函数调用,建议使用 /reset 重置上下文",
|
|
263
317
|
)
|
|
264
318
|
parts = [types.Part.from_text(text=" ")]
|
|
265
319
|
append_or_extend(gemini_contents, parts, types.ModelContent)
|
|
@@ -272,7 +326,7 @@ class ProviderGoogleGenAI(Provider):
|
|
|
272
326
|
"name": message["tool_call_id"],
|
|
273
327
|
"content": message["content"],
|
|
274
328
|
},
|
|
275
|
-
)
|
|
329
|
+
),
|
|
276
330
|
]
|
|
277
331
|
append_or_extend(gemini_contents, parts, types.UserContent)
|
|
278
332
|
|
|
@@ -281,58 +335,94 @@ class ProviderGoogleGenAI(Provider):
|
|
|
281
335
|
|
|
282
336
|
return gemini_contents
|
|
283
337
|
|
|
284
|
-
|
|
338
|
+
def _extract_reasoning_content(self, candidate: types.Candidate) -> str:
|
|
339
|
+
"""Extract reasoning content from candidate parts"""
|
|
340
|
+
if not candidate.content or not candidate.content.parts:
|
|
341
|
+
return ""
|
|
342
|
+
|
|
343
|
+
thought_buf: list[str] = [
|
|
344
|
+
(p.text or "") for p in candidate.content.parts if p.thought
|
|
345
|
+
]
|
|
346
|
+
return "".join(thought_buf).strip()
|
|
347
|
+
|
|
285
348
|
def _process_content_parts(
|
|
286
|
-
|
|
349
|
+
self,
|
|
350
|
+
candidate: types.Candidate,
|
|
351
|
+
llm_response: LLMResponse,
|
|
287
352
|
) -> MessageChain:
|
|
288
353
|
"""处理内容部分并构建消息链"""
|
|
289
|
-
|
|
290
|
-
|
|
354
|
+
if not candidate.content:
|
|
355
|
+
logger.warning(f"收到的 candidate.content 为空: {candidate}")
|
|
356
|
+
raise Exception("API 返回的 candidate.content 为空。")
|
|
357
|
+
|
|
358
|
+
finish_reason = candidate.finish_reason
|
|
359
|
+
result_parts: list[types.Part] | None = candidate.content.parts
|
|
291
360
|
|
|
292
361
|
if finish_reason == types.FinishReason.SAFETY:
|
|
293
|
-
raise Exception("
|
|
362
|
+
raise Exception("模型生成内容未通过 Gemini 平台的安全检查")
|
|
294
363
|
|
|
295
364
|
if finish_reason in {
|
|
296
365
|
types.FinishReason.PROHIBITED_CONTENT,
|
|
297
366
|
types.FinishReason.SPII,
|
|
298
367
|
types.FinishReason.BLOCKLIST,
|
|
299
368
|
}:
|
|
300
|
-
raise Exception("模型生成内容违反Gemini平台政策")
|
|
369
|
+
raise Exception("模型生成内容违反 Gemini 平台政策")
|
|
301
370
|
|
|
302
371
|
# 防止旧版本SDK不存在IMAGE_SAFETY
|
|
303
372
|
if hasattr(types.FinishReason, "IMAGE_SAFETY"):
|
|
304
373
|
if finish_reason == types.FinishReason.IMAGE_SAFETY:
|
|
305
|
-
raise Exception("模型生成内容违反Gemini平台政策")
|
|
374
|
+
raise Exception("模型生成内容违反 Gemini 平台政策")
|
|
306
375
|
|
|
307
376
|
if not result_parts:
|
|
308
|
-
logger.
|
|
309
|
-
raise Exception("API
|
|
377
|
+
logger.warning(f"收到的 candidate.content.parts 为空: {candidate}")
|
|
378
|
+
raise Exception("API 返回的 candidate.content.parts 为空。")
|
|
379
|
+
|
|
380
|
+
# 提取 reasoning content
|
|
381
|
+
reasoning = self._extract_reasoning_content(candidate)
|
|
382
|
+
if reasoning:
|
|
383
|
+
llm_response.reasoning_content = reasoning
|
|
310
384
|
|
|
311
385
|
chain = []
|
|
312
386
|
part: types.Part
|
|
313
387
|
|
|
314
388
|
# 暂时这样Fallback
|
|
315
389
|
if all(
|
|
316
|
-
part.inline_data
|
|
390
|
+
part.inline_data
|
|
391
|
+
and part.inline_data.mime_type
|
|
392
|
+
and part.inline_data.mime_type.startswith("image/")
|
|
317
393
|
for part in result_parts
|
|
318
394
|
):
|
|
319
395
|
chain.append(Comp.Plain("这是图片"))
|
|
320
396
|
for part in result_parts:
|
|
321
397
|
if part.text:
|
|
322
398
|
chain.append(Comp.Plain(part.text))
|
|
323
|
-
elif
|
|
399
|
+
elif (
|
|
400
|
+
part.function_call
|
|
401
|
+
and part.function_call.name is not None
|
|
402
|
+
and part.function_call.args is not None
|
|
403
|
+
):
|
|
324
404
|
llm_response.role = "tool"
|
|
325
405
|
llm_response.tools_call_name.append(part.function_call.name)
|
|
326
406
|
llm_response.tools_call_args.append(part.function_call.args)
|
|
327
|
-
#
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
407
|
+
# function_call.id might be None, use name as fallback
|
|
408
|
+
tool_call_id = part.function_call.id or part.function_call.name
|
|
409
|
+
llm_response.tools_call_ids.append(tool_call_id)
|
|
410
|
+
# extra_content
|
|
411
|
+
if part.thought_signature:
|
|
412
|
+
ts_bs64 = base64.b64encode(part.thought_signature).decode("utf-8")
|
|
413
|
+
llm_response.tools_call_extra_content[tool_call_id] = {
|
|
414
|
+
"google": {"thought_signature": ts_bs64}
|
|
415
|
+
}
|
|
416
|
+
elif (
|
|
417
|
+
part.inline_data
|
|
418
|
+
and part.inline_data.mime_type
|
|
419
|
+
and part.inline_data.mime_type.startswith("image/")
|
|
420
|
+
and part.inline_data.data
|
|
421
|
+
):
|
|
332
422
|
chain.append(Comp.Image.fromBytes(part.inline_data.data))
|
|
333
423
|
return MessageChain(chain=chain)
|
|
334
424
|
|
|
335
|
-
async def _query(self, payloads: dict, tools:
|
|
425
|
+
async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
|
|
336
426
|
"""非流式请求 Gemini API"""
|
|
337
427
|
system_instruction = next(
|
|
338
428
|
(msg["content"] for msg in payloads["messages"] if msg["role"] == "system"),
|
|
@@ -346,33 +436,44 @@ class ProviderGoogleGenAI(Provider):
|
|
|
346
436
|
conversation = self._prepare_conversation(payloads)
|
|
347
437
|
temperature = payloads.get("temperature", 0.7)
|
|
348
438
|
|
|
349
|
-
result:
|
|
439
|
+
result: types.GenerateContentResponse | None = None
|
|
350
440
|
while True:
|
|
351
441
|
try:
|
|
352
442
|
config = await self._prepare_query_config(
|
|
353
|
-
payloads,
|
|
443
|
+
payloads,
|
|
444
|
+
tools,
|
|
445
|
+
system_instruction,
|
|
446
|
+
modalities,
|
|
447
|
+
temperature,
|
|
354
448
|
)
|
|
355
449
|
result = await self.client.models.generate_content(
|
|
356
450
|
model=self.get_model(),
|
|
357
451
|
contents=conversation,
|
|
358
452
|
config=config,
|
|
359
453
|
)
|
|
454
|
+
logger.debug(f"genai result: {result}")
|
|
455
|
+
|
|
456
|
+
if not result.candidates:
|
|
457
|
+
logger.error(f"请求失败, 返回的 candidates 为空: {result}")
|
|
458
|
+
raise Exception("请求失败, 返回的 candidates 为空。")
|
|
360
459
|
|
|
361
460
|
if result.candidates[0].finish_reason == types.FinishReason.RECITATION:
|
|
362
461
|
if temperature > 2:
|
|
363
462
|
raise Exception("温度参数已超过最大值2,仍然发生recitation")
|
|
364
463
|
temperature += 0.2
|
|
365
464
|
logger.warning(
|
|
366
|
-
f"发生了recitation,正在提高温度至{temperature:.1f}重试..."
|
|
465
|
+
f"发生了recitation,正在提高温度至{temperature:.1f}重试...",
|
|
367
466
|
)
|
|
368
467
|
continue
|
|
369
468
|
|
|
370
469
|
break
|
|
371
470
|
|
|
372
471
|
except APIError as e:
|
|
472
|
+
if e.message is None:
|
|
473
|
+
e.message = ""
|
|
373
474
|
if "Developer instruction is not enabled" in e.message:
|
|
374
475
|
logger.warning(
|
|
375
|
-
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)"
|
|
476
|
+
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)",
|
|
376
477
|
)
|
|
377
478
|
system_instruction = None
|
|
378
479
|
elif "Function calling is not enabled" in e.message:
|
|
@@ -385,7 +486,7 @@ class ProviderGoogleGenAI(Provider):
|
|
|
385
486
|
or "only supports text output" in e.message
|
|
386
487
|
):
|
|
387
488
|
logger.warning(
|
|
388
|
-
f"{self.get_model()} 不支持多模态输出,降级为文本模态"
|
|
489
|
+
f"{self.get_model()} 不支持多模态输出,降级为文本模态",
|
|
389
490
|
)
|
|
390
491
|
modalities = ["Text"]
|
|
391
492
|
else:
|
|
@@ -393,11 +494,17 @@ class ProviderGoogleGenAI(Provider):
|
|
|
393
494
|
continue
|
|
394
495
|
|
|
395
496
|
llm_response = LLMResponse("assistant")
|
|
396
|
-
llm_response.
|
|
497
|
+
llm_response.raw_completion = result
|
|
498
|
+
llm_response.result_chain = self._process_content_parts(
|
|
499
|
+
result.candidates[0],
|
|
500
|
+
llm_response,
|
|
501
|
+
)
|
|
397
502
|
return llm_response
|
|
398
503
|
|
|
399
504
|
async def _query_stream(
|
|
400
|
-
self,
|
|
505
|
+
self,
|
|
506
|
+
payloads: dict,
|
|
507
|
+
tools: ToolSet | None,
|
|
401
508
|
) -> AsyncGenerator[LLMResponse, None]:
|
|
402
509
|
"""流式请求 Gemini API"""
|
|
403
510
|
system_instruction = next(
|
|
@@ -411,7 +518,9 @@ class ProviderGoogleGenAI(Provider):
|
|
|
411
518
|
while True:
|
|
412
519
|
try:
|
|
413
520
|
config = await self._prepare_query_config(
|
|
414
|
-
payloads,
|
|
521
|
+
payloads,
|
|
522
|
+
tools,
|
|
523
|
+
system_instruction,
|
|
415
524
|
)
|
|
416
525
|
result = await self.client.models.generate_content_stream(
|
|
417
526
|
model=self.get_model(),
|
|
@@ -420,9 +529,11 @@ class ProviderGoogleGenAI(Provider):
|
|
|
420
529
|
)
|
|
421
530
|
break
|
|
422
531
|
except APIError as e:
|
|
532
|
+
if e.message is None:
|
|
533
|
+
e.message = ""
|
|
423
534
|
if "Developer instruction is not enabled" in e.message:
|
|
424
535
|
logger.warning(
|
|
425
|
-
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)"
|
|
536
|
+
f"{self.get_model()} 不支持 system prompt,已自动去除(影响人格设置)",
|
|
426
537
|
)
|
|
427
538
|
system_instruction = None
|
|
428
539
|
elif "Function calling is not enabled" in e.message:
|
|
@@ -432,47 +543,98 @@ class ProviderGoogleGenAI(Provider):
|
|
|
432
543
|
raise
|
|
433
544
|
continue
|
|
434
545
|
|
|
546
|
+
# Accumulate the complete response text for the final response
|
|
547
|
+
accumulated_text = ""
|
|
548
|
+
accumulated_reasoning = ""
|
|
549
|
+
final_response = None
|
|
550
|
+
|
|
435
551
|
async for chunk in result:
|
|
436
552
|
llm_response = LLMResponse("assistant", is_chunk=True)
|
|
437
553
|
|
|
554
|
+
if not chunk.candidates:
|
|
555
|
+
logger.warning(f"收到的 chunk 中 candidates 为空: {chunk}")
|
|
556
|
+
continue
|
|
557
|
+
if not chunk.candidates[0].content:
|
|
558
|
+
logger.warning(f"收到的 chunk 中 content 为空: {chunk}")
|
|
559
|
+
continue
|
|
560
|
+
|
|
438
561
|
if chunk.candidates[0].content.parts and any(
|
|
439
562
|
part.function_call for part in chunk.candidates[0].content.parts
|
|
440
563
|
):
|
|
441
564
|
llm_response = LLMResponse("assistant", is_chunk=False)
|
|
565
|
+
llm_response.raw_completion = chunk
|
|
442
566
|
llm_response.result_chain = self._process_content_parts(
|
|
443
|
-
chunk,
|
|
567
|
+
chunk.candidates[0],
|
|
568
|
+
llm_response,
|
|
444
569
|
)
|
|
445
570
|
yield llm_response
|
|
446
|
-
|
|
571
|
+
return
|
|
447
572
|
|
|
573
|
+
_f = False
|
|
574
|
+
|
|
575
|
+
# 提取 reasoning content
|
|
576
|
+
reasoning = self._extract_reasoning_content(chunk.candidates[0])
|
|
577
|
+
if reasoning:
|
|
578
|
+
_f = True
|
|
579
|
+
accumulated_reasoning += reasoning
|
|
580
|
+
llm_response.reasoning_content = reasoning
|
|
448
581
|
if chunk.text:
|
|
582
|
+
_f = True
|
|
583
|
+
accumulated_text += chunk.text
|
|
449
584
|
llm_response.result_chain = MessageChain(chain=[Comp.Plain(chunk.text)])
|
|
585
|
+
if _f:
|
|
450
586
|
yield llm_response
|
|
451
587
|
|
|
452
588
|
if chunk.candidates[0].finish_reason:
|
|
453
|
-
|
|
454
|
-
if
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
chunk,
|
|
589
|
+
# Process the final chunk for potential tool calls or other content
|
|
590
|
+
if chunk.candidates[0].content.parts:
|
|
591
|
+
final_response = LLMResponse("assistant", is_chunk=False)
|
|
592
|
+
final_response.raw_completion = chunk
|
|
593
|
+
final_response.result_chain = self._process_content_parts(
|
|
594
|
+
chunk.candidates[0],
|
|
595
|
+
final_response,
|
|
459
596
|
)
|
|
460
|
-
yield llm_response
|
|
461
597
|
break
|
|
462
598
|
|
|
599
|
+
# Yield final complete response with accumulated text
|
|
600
|
+
if not final_response:
|
|
601
|
+
final_response = LLMResponse("assistant", is_chunk=False)
|
|
602
|
+
|
|
603
|
+
# Set the complete accumulated reasoning in the final response
|
|
604
|
+
if accumulated_reasoning:
|
|
605
|
+
final_response.reasoning_content = accumulated_reasoning
|
|
606
|
+
|
|
607
|
+
# Set the complete accumulated text in the final response
|
|
608
|
+
if accumulated_text:
|
|
609
|
+
final_response.result_chain = MessageChain(
|
|
610
|
+
chain=[Comp.Plain(accumulated_text)],
|
|
611
|
+
)
|
|
612
|
+
elif not final_response.result_chain:
|
|
613
|
+
# If no text was accumulated and no final response was set, provide empty space
|
|
614
|
+
final_response.result_chain = MessageChain(chain=[Comp.Plain(" ")])
|
|
615
|
+
|
|
616
|
+
yield final_response
|
|
617
|
+
|
|
463
618
|
async def text_chat(
|
|
464
619
|
self,
|
|
465
|
-
prompt
|
|
466
|
-
session_id
|
|
467
|
-
image_urls
|
|
468
|
-
func_tool
|
|
469
|
-
contexts=
|
|
620
|
+
prompt=None,
|
|
621
|
+
session_id=None,
|
|
622
|
+
image_urls=None,
|
|
623
|
+
func_tool=None,
|
|
624
|
+
contexts=None,
|
|
470
625
|
system_prompt=None,
|
|
471
626
|
tool_calls_result=None,
|
|
627
|
+
model=None,
|
|
472
628
|
**kwargs,
|
|
473
629
|
) -> LLMResponse:
|
|
474
|
-
|
|
475
|
-
|
|
630
|
+
if contexts is None:
|
|
631
|
+
contexts = []
|
|
632
|
+
new_record = None
|
|
633
|
+
if prompt is not None:
|
|
634
|
+
new_record = await self.assemble_context(prompt, image_urls)
|
|
635
|
+
context_query = self._ensure_message_to_dicts(contexts)
|
|
636
|
+
if new_record:
|
|
637
|
+
context_query.append(new_record)
|
|
476
638
|
if system_prompt:
|
|
477
639
|
context_query.insert(0, {"role": "system", "content": system_prompt})
|
|
478
640
|
|
|
@@ -482,10 +644,14 @@ class ProviderGoogleGenAI(Provider):
|
|
|
482
644
|
|
|
483
645
|
# tool calls result
|
|
484
646
|
if tool_calls_result:
|
|
485
|
-
|
|
647
|
+
if not isinstance(tool_calls_result, list):
|
|
648
|
+
context_query.extend(tool_calls_result.to_openai_messages())
|
|
649
|
+
else:
|
|
650
|
+
for tcr in tool_calls_result:
|
|
651
|
+
context_query.extend(tcr.to_openai_messages())
|
|
486
652
|
|
|
487
653
|
model_config = self.provider_config.get("model_config", {})
|
|
488
|
-
model_config["model"] = self.get_model()
|
|
654
|
+
model_config["model"] = model or self.get_model()
|
|
489
655
|
|
|
490
656
|
payloads = {"messages": context_query, **model_config}
|
|
491
657
|
|
|
@@ -500,19 +666,28 @@ class ProviderGoogleGenAI(Provider):
|
|
|
500
666
|
continue
|
|
501
667
|
break
|
|
502
668
|
|
|
669
|
+
raise Exception("请求失败。")
|
|
670
|
+
|
|
503
671
|
async def text_chat_stream(
|
|
504
672
|
self,
|
|
505
|
-
prompt
|
|
506
|
-
session_id
|
|
507
|
-
image_urls
|
|
508
|
-
func_tool
|
|
509
|
-
contexts=
|
|
673
|
+
prompt=None,
|
|
674
|
+
session_id=None,
|
|
675
|
+
image_urls=None,
|
|
676
|
+
func_tool=None,
|
|
677
|
+
contexts=None,
|
|
510
678
|
system_prompt=None,
|
|
511
679
|
tool_calls_result=None,
|
|
680
|
+
model=None,
|
|
512
681
|
**kwargs,
|
|
513
682
|
) -> AsyncGenerator[LLMResponse, None]:
|
|
514
|
-
|
|
515
|
-
|
|
683
|
+
if contexts is None:
|
|
684
|
+
contexts = []
|
|
685
|
+
new_record = None
|
|
686
|
+
if prompt is not None:
|
|
687
|
+
new_record = await self.assemble_context(prompt, image_urls)
|
|
688
|
+
context_query = self._ensure_message_to_dicts(contexts)
|
|
689
|
+
if new_record:
|
|
690
|
+
context_query.append(new_record)
|
|
516
691
|
if system_prompt:
|
|
517
692
|
context_query.insert(0, {"role": "system", "content": system_prompt})
|
|
518
693
|
|
|
@@ -522,10 +697,14 @@ class ProviderGoogleGenAI(Provider):
|
|
|
522
697
|
|
|
523
698
|
# tool calls result
|
|
524
699
|
if tool_calls_result:
|
|
525
|
-
|
|
700
|
+
if not isinstance(tool_calls_result, list):
|
|
701
|
+
context_query.extend(tool_calls_result.to_openai_messages())
|
|
702
|
+
else:
|
|
703
|
+
for tcr in tool_calls_result:
|
|
704
|
+
context_query.extend(tcr.to_openai_messages())
|
|
526
705
|
|
|
527
706
|
model_config = self.provider_config.get("model_config", {})
|
|
528
|
-
model_config["model"] = self.get_model()
|
|
707
|
+
model_config["model"] = model or self.get_model()
|
|
529
708
|
|
|
530
709
|
payloads = {"messages": context_query, **model_config}
|
|
531
710
|
|
|
@@ -548,7 +727,9 @@ class ProviderGoogleGenAI(Provider):
|
|
|
548
727
|
return [
|
|
549
728
|
m.name.replace("models/", "")
|
|
550
729
|
for m in models
|
|
551
|
-
if
|
|
730
|
+
if m.supported_actions
|
|
731
|
+
and "generateContent" in m.supported_actions
|
|
732
|
+
and m.name
|
|
552
733
|
]
|
|
553
734
|
except APIError as e:
|
|
554
735
|
raise Exception(f"获取模型列表失败: {e.message}")
|
|
@@ -556,17 +737,15 @@ class ProviderGoogleGenAI(Provider):
|
|
|
556
737
|
def get_current_key(self) -> str:
|
|
557
738
|
return self.chosen_api_key
|
|
558
739
|
|
|
559
|
-
def get_keys(self) ->
|
|
740
|
+
def get_keys(self) -> list[str]:
|
|
560
741
|
return self.api_keys
|
|
561
742
|
|
|
562
743
|
def set_key(self, key):
|
|
563
744
|
self.chosen_api_key = key
|
|
564
745
|
self._init_client()
|
|
565
746
|
|
|
566
|
-
async def assemble_context(self, text: str, image_urls:
|
|
567
|
-
"""
|
|
568
|
-
组装上下文。
|
|
569
|
-
"""
|
|
747
|
+
async def assemble_context(self, text: str, image_urls: list[str] | None = None):
|
|
748
|
+
"""组装上下文。"""
|
|
570
749
|
if image_urls:
|
|
571
750
|
user_content = {
|
|
572
751
|
"role": "user",
|
|
@@ -585,22 +764,21 @@ class ProviderGoogleGenAI(Provider):
|
|
|
585
764
|
logger.warning(f"图片 {image_url} 得到的结果为空,将忽略。")
|
|
586
765
|
continue
|
|
587
766
|
user_content["content"].append(
|
|
588
|
-
{
|
|
767
|
+
{
|
|
768
|
+
"type": "image_url",
|
|
769
|
+
"image_url": {"url": image_data},
|
|
770
|
+
},
|
|
589
771
|
)
|
|
590
772
|
return user_content
|
|
591
|
-
|
|
592
|
-
return {"role": "user", "content": text}
|
|
773
|
+
return {"role": "user", "content": text}
|
|
593
774
|
|
|
594
775
|
async def encode_image_bs64(self, image_url: str) -> str:
|
|
595
|
-
"""
|
|
596
|
-
将图片转换为 base64
|
|
597
|
-
"""
|
|
776
|
+
"""将图片转换为 base64"""
|
|
598
777
|
if image_url.startswith("base64://"):
|
|
599
778
|
return image_url.replace("base64://", "data:image/jpeg;base64,")
|
|
600
779
|
with open(image_url, "rb") as f:
|
|
601
780
|
image_bs64 = base64.b64encode(f.read()).decode("utf-8")
|
|
602
781
|
return "data:image/jpeg;base64," + image_bs64
|
|
603
|
-
return ""
|
|
604
782
|
|
|
605
783
|
async def terminate(self):
|
|
606
784
|
logger.info("Google GenAI 适配器已终止。")
|