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
|
@@ -1,60 +1,115 @@
|
|
|
1
|
-
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
from collections.abc import AsyncGenerator
|
|
2
4
|
from mimetypes import guess_type
|
|
3
5
|
|
|
6
|
+
import anthropic
|
|
4
7
|
from anthropic import AsyncAnthropic
|
|
5
8
|
from anthropic.types import Message
|
|
6
9
|
|
|
7
|
-
from astrbot.core.utils.io import download_image_by_url
|
|
8
|
-
from astrbot.core.db import BaseDatabase
|
|
9
|
-
from astrbot.api.provider import Provider, Personality
|
|
10
10
|
from astrbot import logger
|
|
11
|
-
from astrbot.
|
|
11
|
+
from astrbot.api.provider import Provider
|
|
12
|
+
from astrbot.core.provider.entities import LLMResponse
|
|
13
|
+
from astrbot.core.provider.func_tool_manager import ToolSet
|
|
14
|
+
from astrbot.core.utils.io import download_image_by_url
|
|
15
|
+
|
|
12
16
|
from ..register import register_provider_adapter
|
|
13
|
-
from astrbot.core.message.message_event_result import MessageChain
|
|
14
|
-
from astrbot.core.provider.entities import LLMResponse, ToolCallsResult
|
|
15
|
-
from .openai_source import ProviderOpenAIOfficial
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@register_provider_adapter(
|
|
19
|
-
"anthropic_chat_completion",
|
|
20
|
+
"anthropic_chat_completion",
|
|
21
|
+
"Anthropic Claude API 提供商适配器",
|
|
20
22
|
)
|
|
21
|
-
class ProviderAnthropic(
|
|
23
|
+
class ProviderAnthropic(Provider):
|
|
22
24
|
def __init__(
|
|
23
25
|
self,
|
|
24
|
-
provider_config
|
|
25
|
-
provider_settings
|
|
26
|
-
db_helper: BaseDatabase,
|
|
27
|
-
persistant_history=True,
|
|
28
|
-
default_persona: Personality = None,
|
|
26
|
+
provider_config,
|
|
27
|
+
provider_settings,
|
|
29
28
|
) -> None:
|
|
30
|
-
|
|
31
|
-
Provider.__init__(
|
|
32
|
-
self,
|
|
29
|
+
super().__init__(
|
|
33
30
|
provider_config,
|
|
34
31
|
provider_settings,
|
|
35
|
-
persistant_history,
|
|
36
|
-
db_helper,
|
|
37
|
-
default_persona,
|
|
38
32
|
)
|
|
39
33
|
|
|
40
|
-
self.chosen_api_key =
|
|
41
|
-
self.api_keys:
|
|
42
|
-
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else
|
|
34
|
+
self.chosen_api_key: str = ""
|
|
35
|
+
self.api_keys: list = super().get_keys()
|
|
36
|
+
self.chosen_api_key = self.api_keys[0] if len(self.api_keys) > 0 else ""
|
|
43
37
|
self.base_url = provider_config.get("api_base", "https://api.anthropic.com")
|
|
44
38
|
self.timeout = provider_config.get("timeout", 120)
|
|
45
39
|
if isinstance(self.timeout, str):
|
|
46
40
|
self.timeout = int(self.timeout)
|
|
47
41
|
|
|
48
42
|
self.client = AsyncAnthropic(
|
|
49
|
-
api_key=self.chosen_api_key,
|
|
43
|
+
api_key=self.chosen_api_key,
|
|
44
|
+
timeout=self.timeout,
|
|
45
|
+
base_url=self.base_url,
|
|
50
46
|
)
|
|
51
47
|
|
|
52
48
|
self.set_model(provider_config["model_config"]["model"])
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
def _prepare_payload(self, messages: list[dict]):
|
|
51
|
+
"""准备 Anthropic API 的请求 payload
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
messages: OpenAI 格式的消息列表,包含用户输入和系统提示等信息
|
|
55
|
+
Returns:
|
|
56
|
+
system_prompt: 系统提示内容
|
|
57
|
+
new_messages: 处理后的消息列表,去除系统提示
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
system_prompt = ""
|
|
61
|
+
new_messages = []
|
|
62
|
+
for message in messages:
|
|
63
|
+
if message["role"] == "system":
|
|
64
|
+
system_prompt = message["content"]
|
|
65
|
+
elif message["role"] == "assistant":
|
|
66
|
+
blocks = []
|
|
67
|
+
if isinstance(message["content"], str):
|
|
68
|
+
blocks.append({"type": "text", "text": message["content"]})
|
|
69
|
+
if "tool_calls" in message:
|
|
70
|
+
for tool_call in message["tool_calls"]:
|
|
71
|
+
blocks.append( # noqa: PERF401
|
|
72
|
+
{
|
|
73
|
+
"type": "tool_use",
|
|
74
|
+
"name": tool_call["function"]["name"],
|
|
75
|
+
"input": (
|
|
76
|
+
json.loads(tool_call["function"]["arguments"])
|
|
77
|
+
if isinstance(
|
|
78
|
+
tool_call["function"]["arguments"],
|
|
79
|
+
str,
|
|
80
|
+
)
|
|
81
|
+
else tool_call["function"]["arguments"]
|
|
82
|
+
),
|
|
83
|
+
"id": tool_call["id"],
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
new_messages.append(
|
|
87
|
+
{
|
|
88
|
+
"role": "assistant",
|
|
89
|
+
"content": blocks,
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
elif message["role"] == "tool":
|
|
93
|
+
new_messages.append(
|
|
94
|
+
{
|
|
95
|
+
"role": "user",
|
|
96
|
+
"content": [
|
|
97
|
+
{
|
|
98
|
+
"type": "tool_result",
|
|
99
|
+
"tool_use_id": message["tool_call_id"],
|
|
100
|
+
"content": message["content"],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
new_messages.append(message)
|
|
107
|
+
|
|
108
|
+
return system_prompt, new_messages
|
|
109
|
+
|
|
110
|
+
async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse:
|
|
55
111
|
if tools:
|
|
56
|
-
tool_list
|
|
57
|
-
if tool_list:
|
|
112
|
+
if tool_list := tools.get_func_desc_anthropic_style():
|
|
58
113
|
payloads["tools"] = tool_list
|
|
59
114
|
|
|
60
115
|
completion = await self.client.messages.create(**payloads, stream=False)
|
|
@@ -64,68 +119,169 @@ class ProviderAnthropic(ProviderOpenAIOfficial):
|
|
|
64
119
|
|
|
65
120
|
if len(completion.content) == 0:
|
|
66
121
|
raise Exception("API 返回的 completion 为空。")
|
|
67
|
-
# TODO: 如果进行函数调用,思维链被截断,用户可能需要思维链的内容
|
|
68
|
-
# 选最后一条消息,如果要进行函数调用,anthropic会先返回文本消息的思维链,然后再返回函数调用请求
|
|
69
|
-
content = completion.content[-1]
|
|
70
|
-
|
|
71
|
-
llm_response = LLMResponse("assistant")
|
|
72
|
-
|
|
73
|
-
if content.type == "text":
|
|
74
|
-
# text completion
|
|
75
|
-
completion_text = str(content.text).strip()
|
|
76
|
-
# llm_response.completion_text = completion_text
|
|
77
|
-
llm_response.result_chain = MessageChain().message(completion_text)
|
|
78
|
-
|
|
79
|
-
# Anthropic每次只返回一个函数调用
|
|
80
|
-
if completion.stop_reason == "tool_use":
|
|
81
|
-
# tools call (function calling)
|
|
82
|
-
args_ls = []
|
|
83
|
-
func_name_ls = []
|
|
84
|
-
tool_use_ids = []
|
|
85
|
-
func_name_ls.append(content.name)
|
|
86
|
-
args_ls.append(content.input)
|
|
87
|
-
tool_use_ids.append(content.id)
|
|
88
|
-
llm_response.role = "tool"
|
|
89
|
-
llm_response.tools_call_args = args_ls
|
|
90
|
-
llm_response.tools_call_name = func_name_ls
|
|
91
|
-
llm_response.tools_call_ids = tool_use_ids
|
|
92
122
|
|
|
93
|
-
|
|
94
|
-
logger.error(f"API 返回的 completion 无法解析:{completion}。")
|
|
95
|
-
raise Exception(f"API 返回的 completion 无法解析:{completion}。")
|
|
123
|
+
llm_response = LLMResponse(role="assistant")
|
|
96
124
|
|
|
97
|
-
|
|
125
|
+
for content_block in completion.content:
|
|
126
|
+
if content_block.type == "text":
|
|
127
|
+
completion_text = str(content_block.text).strip()
|
|
128
|
+
llm_response.completion_text = completion_text
|
|
129
|
+
|
|
130
|
+
if content_block.type == "tool_use":
|
|
131
|
+
llm_response.tools_call_args.append(content_block.input)
|
|
132
|
+
llm_response.tools_call_name.append(content_block.name)
|
|
133
|
+
llm_response.tools_call_ids.append(content_block.id)
|
|
134
|
+
# TODO(Soulter): 处理 end_turn 情况
|
|
135
|
+
if not llm_response.completion_text and not llm_response.tools_call_args:
|
|
136
|
+
raise Exception(f"Anthropic API 返回的 completion 无法解析:{completion}。")
|
|
98
137
|
|
|
99
138
|
return llm_response
|
|
100
139
|
|
|
140
|
+
async def _query_stream(
|
|
141
|
+
self,
|
|
142
|
+
payloads: dict,
|
|
143
|
+
tools: ToolSet | None,
|
|
144
|
+
) -> AsyncGenerator[LLMResponse, None]:
|
|
145
|
+
if tools:
|
|
146
|
+
if tool_list := tools.get_func_desc_anthropic_style():
|
|
147
|
+
payloads["tools"] = tool_list
|
|
148
|
+
|
|
149
|
+
# 用于累积工具调用信息
|
|
150
|
+
tool_use_buffer = {}
|
|
151
|
+
# 用于累积最终结果
|
|
152
|
+
final_text = ""
|
|
153
|
+
final_tool_calls = []
|
|
154
|
+
|
|
155
|
+
async with self.client.messages.stream(**payloads) as stream:
|
|
156
|
+
assert isinstance(stream, anthropic.AsyncMessageStream)
|
|
157
|
+
async for event in stream:
|
|
158
|
+
if event.type == "content_block_start":
|
|
159
|
+
if event.content_block.type == "text":
|
|
160
|
+
# 文本块开始
|
|
161
|
+
yield LLMResponse(
|
|
162
|
+
role="assistant",
|
|
163
|
+
completion_text="",
|
|
164
|
+
is_chunk=True,
|
|
165
|
+
)
|
|
166
|
+
elif event.content_block.type == "tool_use":
|
|
167
|
+
# 工具使用块开始,初始化缓冲区
|
|
168
|
+
tool_use_buffer[event.index] = {
|
|
169
|
+
"id": event.content_block.id,
|
|
170
|
+
"name": event.content_block.name,
|
|
171
|
+
"input": {},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
elif event.type == "content_block_delta":
|
|
175
|
+
if event.delta.type == "text_delta":
|
|
176
|
+
# 文本增量
|
|
177
|
+
final_text += event.delta.text
|
|
178
|
+
yield LLMResponse(
|
|
179
|
+
role="assistant",
|
|
180
|
+
completion_text=event.delta.text,
|
|
181
|
+
is_chunk=True,
|
|
182
|
+
)
|
|
183
|
+
elif event.delta.type == "input_json_delta":
|
|
184
|
+
# 工具调用参数增量
|
|
185
|
+
if event.index in tool_use_buffer:
|
|
186
|
+
# 累积 JSON 输入
|
|
187
|
+
if "input_json" not in tool_use_buffer[event.index]:
|
|
188
|
+
tool_use_buffer[event.index]["input_json"] = ""
|
|
189
|
+
tool_use_buffer[event.index]["input_json"] += (
|
|
190
|
+
event.delta.partial_json
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
elif event.type == "content_block_stop":
|
|
194
|
+
# 内容块结束
|
|
195
|
+
if event.index in tool_use_buffer:
|
|
196
|
+
# 解析完整的工具调用
|
|
197
|
+
tool_info = tool_use_buffer[event.index]
|
|
198
|
+
try:
|
|
199
|
+
if "input_json" in tool_info:
|
|
200
|
+
tool_info["input"] = json.loads(tool_info["input_json"])
|
|
201
|
+
|
|
202
|
+
# 添加到最终结果
|
|
203
|
+
final_tool_calls.append(
|
|
204
|
+
{
|
|
205
|
+
"id": tool_info["id"],
|
|
206
|
+
"name": tool_info["name"],
|
|
207
|
+
"input": tool_info["input"],
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
yield LLMResponse(
|
|
212
|
+
role="tool",
|
|
213
|
+
completion_text="",
|
|
214
|
+
tools_call_args=[tool_info["input"]],
|
|
215
|
+
tools_call_name=[tool_info["name"]],
|
|
216
|
+
tools_call_ids=[tool_info["id"]],
|
|
217
|
+
is_chunk=True,
|
|
218
|
+
)
|
|
219
|
+
except json.JSONDecodeError:
|
|
220
|
+
# JSON 解析失败,跳过这个工具调用
|
|
221
|
+
logger.warning(f"工具调用参数 JSON 解析失败: {tool_info}")
|
|
222
|
+
|
|
223
|
+
# 清理缓冲区
|
|
224
|
+
del tool_use_buffer[event.index]
|
|
225
|
+
|
|
226
|
+
# 返回最终的完整结果
|
|
227
|
+
final_response = LLMResponse(
|
|
228
|
+
role="assistant",
|
|
229
|
+
completion_text=final_text,
|
|
230
|
+
is_chunk=False,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if final_tool_calls:
|
|
234
|
+
final_response.tools_call_args = [
|
|
235
|
+
call["input"] for call in final_tool_calls
|
|
236
|
+
]
|
|
237
|
+
final_response.tools_call_name = [call["name"] for call in final_tool_calls]
|
|
238
|
+
final_response.tools_call_ids = [call["id"] for call in final_tool_calls]
|
|
239
|
+
|
|
240
|
+
yield final_response
|
|
241
|
+
|
|
101
242
|
async def text_chat(
|
|
102
243
|
self,
|
|
103
|
-
prompt
|
|
104
|
-
session_id
|
|
105
|
-
image_urls
|
|
106
|
-
func_tool
|
|
107
|
-
contexts=
|
|
244
|
+
prompt=None,
|
|
245
|
+
session_id=None,
|
|
246
|
+
image_urls=None,
|
|
247
|
+
func_tool=None,
|
|
248
|
+
contexts=None,
|
|
108
249
|
system_prompt=None,
|
|
109
|
-
tool_calls_result
|
|
250
|
+
tool_calls_result=None,
|
|
251
|
+
model=None,
|
|
110
252
|
**kwargs,
|
|
111
253
|
) -> LLMResponse:
|
|
112
|
-
if
|
|
113
|
-
|
|
254
|
+
if contexts is None:
|
|
255
|
+
contexts = []
|
|
256
|
+
new_record = None
|
|
257
|
+
if prompt is not None:
|
|
258
|
+
new_record = await self.assemble_context(prompt, image_urls)
|
|
259
|
+
context_query = self._ensure_message_to_dicts(contexts)
|
|
260
|
+
if new_record:
|
|
261
|
+
context_query.append(new_record)
|
|
114
262
|
|
|
115
|
-
|
|
116
|
-
|
|
263
|
+
if system_prompt:
|
|
264
|
+
context_query.insert(0, {"role": "system", "content": system_prompt})
|
|
117
265
|
|
|
118
266
|
for part in context_query:
|
|
119
267
|
if "_no_save" in part:
|
|
120
268
|
del part["_no_save"]
|
|
121
269
|
|
|
270
|
+
# tool calls result
|
|
122
271
|
if tool_calls_result:
|
|
123
|
-
|
|
124
|
-
|
|
272
|
+
if not isinstance(tool_calls_result, list):
|
|
273
|
+
context_query.extend(tool_calls_result.to_openai_messages())
|
|
274
|
+
else:
|
|
275
|
+
for tcr in tool_calls_result:
|
|
276
|
+
context_query.extend(tcr.to_openai_messages())
|
|
277
|
+
|
|
278
|
+
system_prompt, new_messages = self._prepare_payload(context_query)
|
|
125
279
|
|
|
126
280
|
model_config = self.provider_config.get("model_config", {})
|
|
281
|
+
model_config["model"] = model or self.get_model()
|
|
282
|
+
|
|
283
|
+
payloads = {"messages": new_messages, **model_config}
|
|
127
284
|
|
|
128
|
-
payloads = {"messages": context_query, **model_config}
|
|
129
285
|
# Anthropic has a different way of handling system prompts
|
|
130
286
|
if system_prompt:
|
|
131
287
|
payloads["system"] = system_prompt
|
|
@@ -133,32 +289,9 @@ class ProviderAnthropic(ProviderOpenAIOfficial):
|
|
|
133
289
|
llm_response = None
|
|
134
290
|
try:
|
|
135
291
|
llm_response = await self._query(payloads, func_tool)
|
|
136
|
-
|
|
137
292
|
except Exception as e:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
while retry_cnt > 0:
|
|
141
|
-
logger.warning(
|
|
142
|
-
f"上下文长度超过限制。尝试弹出最早的记录然后重试。当前记录条数: {len(context_query)}"
|
|
143
|
-
)
|
|
144
|
-
try:
|
|
145
|
-
await self.pop_record(context_query)
|
|
146
|
-
response = await self.client.messages.create(
|
|
147
|
-
messages=context_query, **model_config
|
|
148
|
-
)
|
|
149
|
-
llm_response = LLMResponse("assistant")
|
|
150
|
-
llm_response.result_chain = MessageChain().message(response.content[0].text)
|
|
151
|
-
llm_response.raw_completion = response
|
|
152
|
-
return llm_response
|
|
153
|
-
except Exception as e:
|
|
154
|
-
if "maximum context length" in str(e):
|
|
155
|
-
retry_cnt -= 1
|
|
156
|
-
else:
|
|
157
|
-
raise e
|
|
158
|
-
return LLMResponse("err", "err: 请尝试 /reset 清除会话记录。")
|
|
159
|
-
else:
|
|
160
|
-
logger.error(f"发生了错误。Provider 配置如下: {model_config}")
|
|
161
|
-
raise e
|
|
293
|
+
logger.error(f"发生了错误。Provider 配置如下: {model_config}")
|
|
294
|
+
raise e
|
|
162
295
|
|
|
163
296
|
return llm_response
|
|
164
297
|
|
|
@@ -171,25 +304,47 @@ class ProviderAnthropic(ProviderOpenAIOfficial):
|
|
|
171
304
|
contexts=...,
|
|
172
305
|
system_prompt=None,
|
|
173
306
|
tool_calls_result=None,
|
|
307
|
+
model=None,
|
|
174
308
|
**kwargs,
|
|
175
309
|
):
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
yield llm_response
|
|
310
|
+
if contexts is None:
|
|
311
|
+
contexts = []
|
|
312
|
+
new_record = None
|
|
313
|
+
if prompt is not None:
|
|
314
|
+
new_record = await self.assemble_context(prompt, image_urls)
|
|
315
|
+
context_query = self._ensure_message_to_dicts(contexts)
|
|
316
|
+
if new_record:
|
|
317
|
+
context_query.append(new_record)
|
|
318
|
+
if system_prompt:
|
|
319
|
+
context_query.insert(0, {"role": "system", "content": system_prompt})
|
|
320
|
+
|
|
321
|
+
for part in context_query:
|
|
322
|
+
if "_no_save" in part:
|
|
323
|
+
del part["_no_save"]
|
|
191
324
|
|
|
192
|
-
|
|
325
|
+
# tool calls result
|
|
326
|
+
if tool_calls_result:
|
|
327
|
+
if not isinstance(tool_calls_result, list):
|
|
328
|
+
context_query.extend(tool_calls_result.to_openai_messages())
|
|
329
|
+
else:
|
|
330
|
+
for tcr in tool_calls_result:
|
|
331
|
+
context_query.extend(tcr.to_openai_messages())
|
|
332
|
+
|
|
333
|
+
system_prompt, new_messages = self._prepare_payload(context_query)
|
|
334
|
+
|
|
335
|
+
model_config = self.provider_config.get("model_config", {})
|
|
336
|
+
model_config["model"] = model or self.get_model()
|
|
337
|
+
|
|
338
|
+
payloads = {"messages": new_messages, **model_config}
|
|
339
|
+
|
|
340
|
+
# Anthropic has a different way of handling system prompts
|
|
341
|
+
if system_prompt:
|
|
342
|
+
payloads["system"] = system_prompt
|
|
343
|
+
|
|
344
|
+
async for llm_response in self._query_stream(payloads, func_tool):
|
|
345
|
+
yield llm_response
|
|
346
|
+
|
|
347
|
+
async def assemble_context(self, text: str, image_urls: list[str] | None = None):
|
|
193
348
|
"""组装上下文,支持文本和图片"""
|
|
194
349
|
if not image_urls:
|
|
195
350
|
return {"role": "user", "content": text}
|
|
@@ -222,11 +377,36 @@ class ProviderAnthropic(ProviderOpenAIOfficial):
|
|
|
222
377
|
"source": {
|
|
223
378
|
"type": "base64",
|
|
224
379
|
"media_type": mime_type,
|
|
225
|
-
"data":
|
|
226
|
-
|
|
227
|
-
|
|
380
|
+
"data": (
|
|
381
|
+
image_data.split("base64,")[1]
|
|
382
|
+
if "base64," in image_data
|
|
383
|
+
else image_data
|
|
384
|
+
),
|
|
228
385
|
},
|
|
229
|
-
}
|
|
386
|
+
},
|
|
230
387
|
)
|
|
231
388
|
|
|
232
389
|
return {"role": "user", "content": content}
|
|
390
|
+
|
|
391
|
+
async def encode_image_bs64(self, image_url: str) -> str:
|
|
392
|
+
"""将图片转换为 base64"""
|
|
393
|
+
if image_url.startswith("base64://"):
|
|
394
|
+
return image_url.replace("base64://", "data:image/jpeg;base64,")
|
|
395
|
+
with open(image_url, "rb") as f:
|
|
396
|
+
image_bs64 = base64.b64encode(f.read()).decode("utf-8")
|
|
397
|
+
return "data:image/jpeg;base64," + image_bs64
|
|
398
|
+
return ""
|
|
399
|
+
|
|
400
|
+
def get_current_key(self) -> str:
|
|
401
|
+
return self.chosen_api_key
|
|
402
|
+
|
|
403
|
+
async def get_models(self) -> list[str]:
|
|
404
|
+
models_str = []
|
|
405
|
+
models = await self.client.models.list()
|
|
406
|
+
models = sorted(models.data, key=lambda x: x.id)
|
|
407
|
+
for model in models:
|
|
408
|
+
models_str.append(model.id)
|
|
409
|
+
return models_str
|
|
410
|
+
|
|
411
|
+
def set_key(self, key: str):
|
|
412
|
+
self.chosen_api_key = key
|