mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
* Add legacy session waiter compatibility * Expand legacy astrbot package compatibility * Tighten external legacy plugin compatibility smoke tests * Consolidate controlled legacy facade compatibility * docs: clarify compat package boundaries * test: align runtime fixtures with maintained samples * fix: preserve unicode sample fixtures in runtime tests * Implement legacy hook and tool compat runtime * Refactor legacy runtime execution boundary * 增强旧版兼容性,添加多个旧路径入口和相关功能 * 增强旧版兼容性,添加适配器边界的启动和关闭钩子支持 * Refactor legacy API and LLM compatibility logic - Moved legacy LLM and tool compatibility logic from `_legacy_api.py` to a new module `_legacy_llm.py` for better organization and separation of concerns. - Updated `_legacy_api.py` to import necessary components from `_legacy_llm.py`, removing redundant code. - Enhanced database client functionality by adding support for batch read/write operations and change event subscriptions. - Improved documentation in the database client and capability router to reflect new features. - Refined environment management process in the loader to better handle plugin grouping and virtual environment management. * 补充插件分组环境测试覆盖 * feat: Enhance CLI and testing capabilities - Added a new script entry point `astrbot-sdk` in `pyproject.toml`. - Introduced `has_waiter` method in `SessionWaiterManager` to check for existing waiters. - Updated `cli.py` to improve error handling and added context to error messages. - Implemented local development support in `cli.py` with a new `dev` command for running plugins against a mock core. - Created a new testing module `astrbot_sdk.testing` with utilities for local development and plugin testing. - Added comprehensive tests for the new testing module and CLI commands. - Improved compatibility and error messaging for plugin loading failures. * feat: 添加插件初始化、验证和构建命令,增强 CLI 功能 * feat: add platform client documentation and examples - Introduced platform client documentation in `docs/v4/clients/platform.md` detailing methods for sending messages, images, and managing group members. - Added example plugins for LLM chat and database functionalities in `docs/v4/examples/README.md`, `docs/v4/examples/llm-chat/README.md`, and `docs/v4/examples/database/README.md`. - Enhanced quickstart guide with links to new documentation and example plugins. - Implemented runtime contract tests to ensure compatibility of public capabilities and hooks. * Refactor legacy runtime handling and improve plugin loading - Updated `handler_dispatcher.py` to streamline legacy runtime preparation and dispatching results. - Enhanced `loader.py` to simplify legacy plugin detection and manifest building. - Added tests for new HTTPClient and MetadataClient functionalities. - Introduced tests for legacy context metadata methods and legacy loader helpers. - Improved legacy runtime tests to cover new functionality and edge cases. * refactor: 更新兼容层和导入路径,优化文档描述 * Support grouped plugin workers in shared environments - add group metadata driven worker startup for shared env plans - track per-plugin handler and capability ownership inside grouped workers - update runtime and smoke tests for grouped worker session behavior * Add v4 compat layer and legacy shims - Introduce private v4 compatibility surface using _legacy_api.py, _legacy_runtime.py, _legacy_loader.py plus new _legacy_context.py and _legacy_star.py to centralize legacy adapters while keeping public APIs thin. - Extend InitializeOutput to carry protocol_version for negotiated protocol, enabling runtime to adapt to the chosen v4 version. - Add lightweight legacy support for Star/Context via new LegacyStar and LegacyContext shims and expose legacy API through the aggregate _legacy_api entry point. - Ensure legacy loader preserves class declaration order by iterating module.__dict__ instead of relying on alphabetical sorting. - Add tests: protocol_version handling in InitializeOutput, legacy main component order preservation, and embedded-newline framing in transport tests. * Add architecture doc and refine API compat - Add PROJECT_ARCHITECTURE.md documenting architecture, compat surface, and testing notes. - Update astrbot_sdk.api.__init__ to clarify it is a compatibility implementation layer, not a simple facade, and list migration targets. - Normalize platform in AstrMessageEvent.to_payload to emit a string id by using get_platform_id(). * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * delete old sdk * delete old sdk (#7) Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com> * feat(cli): normalize plugin init skeletons Add interactive plugin init prompts and normalize generated plugin names to the astrbot_plugin_ convention. Update CLI tests for the new skeleton layout and ignore generated plugin directories in git and coverage tooling. Also include related runtime logging adjustments from the current worktree. * Create lint.yml * Update lint.yml * clean it * feat: Enhance handler and capability dispatchers with improved error handling - Updated HandlerDispatcher to raise TypeError for uninjectable required parameters, logging errors appropriately. - Refactored CapabilityDispatcher to raise TypeError for missing required parameters during capability execution. - Renamed _load_plugin_config to load_plugin_config for clarity and consistency. - Introduced _sync_plugin_registry method in SupervisorRuntime to manage plugin capabilities more effectively. - Enhanced capability registration logic to handle naming conflicts with better logging and automatic renaming. - Added tests for handler and capability dispatchers to ensure proper error handling and functionality. - Implemented new HTTP and metadata capabilities with corresponding tests for registration and retrieval. - Improved MemoryClient methods with additional tests for save_with_ttl, get_many, delete_many, and stats. - Added tests for the testing module to ensure proper import and functionality of PluginHarness. * feat: 更新 SDK 描述,重构插件调用上下文,移除插件 ID 传递,增强能力路由和 HTTP 客户端的插件身份管理 * refactor: 更新文档和代码注释,优化兼容性描述,增强可读性 * feat: 添加插件热重载功能,支持文件变更时自动重新加载插件 * feat: 增强错误处理,添加上下文信息,优化插件组件加载和参数注入校验 * feat: 添加 hello_plugin 示例,包含插件结构、命令处理和测试用例 * refactor: 删除过时的架构文档、变更日志和兼容矩阵文件 * refactor: 更新兼容层弃用通知,优化文档结构和可读性 * refactor: 更新项目架构文档,增强能力客户端和执行边界的描述,移除兼容层设计章节 * feat(errors): Enhance AstrBotError with detailed documentation and examples feat(events): Expand MessageEvent with reply capabilities and detailed docstrings fix(loader): Ensure plugin path is correctly managed in sys.path feat(star): Improve Star class documentation and lifecycle method descriptions feat(testing): Add plugin metadata handling in MockContext and enhance PluginHarness feat(hello): Refactor HelloPlugin to utilize new reply methods and structured capabilities test(decorators): Add tests for input/output model support in provide_capability test(events): Implement tests for reply_image and reply_chain methods in MessageEvent test(http): Validate API registration with capability handler references and error handling test(tests): Enhance tests for plugin harness and directory handling in dev commands * feat(runtime): add configurable msgpack wire codec support * fix(runtime): align msgpack framing with transport defaults * fix(runtime): preserve json transport compatibility * fix(cli): scope worker wire codec option * feat: 添加 AGENTS.md 文档,描述 v4 架构约束和开发命令 refactor: 更新 HandlerDispatcher 和 WorkerSession,增强参数处理和结果汇总逻辑 * fix(test): 更新 init_plugin 测试以匹配新的目录命名规范 CLI 的 _normalize_init_plugin_name 函数现在自动添加 astrbot_plugin_ 前缀, 测试期望的目录名从 demo_plugin 更新为 astrbot_plugin_demo_plugin。 * Refactor worker initialization and remove unused codec parameters; add schedule and session waiter modules - Simplified `GroupWorkerRuntime` and `PluginWorkerRuntime` constructors by removing the codec parameter and related logic. - Introduced `schedule.py` to define `ScheduleContext` for managing scheduled tasks with a clear structure and payload handling. - Added `session_waiter.py` for session-based conversational flow management, including `SessionController` and `SessionWaiterManager` for handling multi-turn dialogues. - Enhanced testing utilities in `testing.py` by removing unused classes and streamlining the structure. - Created `types.py` to introduce `GreedyStr` for improved command parameter parsing. * feat: 添加 LLM 工具管理和会话级别状态管理能力 - 新增 llm/ 模块,包含 LLMToolSpec、ProviderRequest、AgentSpec 等实体 - 新增 LLMToolManager 用于管理 LLM 工具注册和激活状态 - 新增 SessionPluginManager 用于会话级别的插件启用状态管理 - 新增 SessionServiceManager 用于会话级别的 LLM/TTS 服务状态管理 - 新增 RegistryClient 用于查询 handler 元数据和设置白名单 - 扩展 CapabilityRouter 内置能力,支持 session.* 和 registry.* 命名空间 - 增强描述符和装饰器以支持新的 trigger 类型 * feat: 大幅增强 SDK 核心功能和文档 新增模块: - clients/files.py: 文件上传/下载客户端 - clients/managers.py: 会话/LLM/Provider 管理器 - clients/provider.py: LLM Provider 客户端 - conversation.py: 对话上下文管理 - plugin_kv.py: 插件 KV 存储辅助 - runtime/limiter.py: 限流器 - star_tools.py: Star 工具函数 - docs/: 完整的 SDK 使用文档 (01-05) 功能增强: - Context 大幅扩展,增加 reply/send_image/typing 等便捷方法 - 装饰器增强,支持 on_llm_request/on_provider_request 等 - 内置 schemas 扩展,覆盖更多 capability 定义 - capability_router_builtins 大幅扩展内置能力实现 - handler_dispatcher 增强参数注入和错误处理 - Star 基类增加生命周期钩子和工具方法 * Add comprehensive API documentation for types and utilities in AstrBot SDK - Introduced `types.md` detailing type aliases, generics, and Pydantic models used in the SDK. - Added `utils.md` covering utility classes and functions including CancelToken, MessageSession, command groups, and session management. - Included usage examples and detailed descriptions for each component to enhance developer understanding and ease of use. * feat: 添加高级方法和辅助函数文档,增强消息组件和事件处理功能 * feat: 增强过滤器类型和能力路由文档,添加 Provider 和会话管理功能 * change location * delete no need thing * delete again * feat: add Star plugin base class and StarTools utility class - Introduced `Star` class as a base for v4 native plugins, providing lifecycle methods and context management. - Added `StarTools` class for accessing runtime context and managing LLM tools. - Implemented `PluginHarness` for local development and testing of plugins, allowing for message dispatching and lifecycle management. - Created `GreedyStr` type for enhanced command parameter parsing, enabling the capture of remaining command text as a single argument. - Added testing utilities and mock capabilities for plugin development. * delete: remove hello_plugin example and its related files * Remove obsolete test files for testing module, top-level modules, transport, and wire codecs - Deleted `test_testing_module.py` as it is no longer needed. - Removed `test_top_level_modules.py` which had no content. - Eliminated `test_transport.py` due to redundancy. - Cleared out `test_wire_codecs.py` as part of the cleanup. * fix(runtime): avoid creating Star instance in on_error fallback * fix(runtime): avoid virtual dispatch in Star.on_error fallback * refactor(runtime): unify command matching logic (#25) * refactor(testing): share command matching with handler dispatcher * fix:添加公共函数文件 * fix: simplify register_task completion handling (#27) * fix: simplify register_task completion handling Remove duplicated cancellation logging in Context.register_task while keeping Future inputs compatible with asyncio.create_task semantics. Add regression coverage for coroutine, Future, cancellation, and failure paths. * fix: prioritize local src in tests_v4 Ensure tests_v4 always imports the working tree package by moving src to sys.path[0] even when another checkout or installed copy is already present. * chore: sync subtree from AstrBot * feat: replay non-sdk changes on clean sdk subtree baseline * feat(sdk): add merged provider config capability support Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(sdk): add merged provider config bridge and client Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(sdk): add merged provider config capability support Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * fix(sdk): tighten bridge cast typing Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(sdk): add merged provider config bridge and client Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * test(sdk): cover merged provider config parity Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat: 完善 memory 向量检索与索引统计 (#28) Co-authored-by: united_pooh <united_pooh@outlook.com> * feat(tests): 添加测试用例以验证 register_task 的行为并更新测试运行说明 * Merge commit 'e45bade147ff44b43860ecff12067309e59c151a' into feat/sdk-integration * Squashed 'astrbot-sdk/' changes from 7dda6077..85342f1485342f14feat(tests): 添加测试用例以验证 register_task 的行为并更新测试运行说明fdffc09bMerge pull request #26 from united-pooh/fix/fix-star-on-error-fallback3b09747cfeat: 完善 memory 向量检索与索引统计 (#28)665c9c69fix(runtime): avoid virtual dispatch in Star.on_error fallback200559a5fix(runtime): avoid creating Star instance in on_error fallback git-subtree-dir: astrbot-sdk git-subtree-split:85342f149b* feat(tests): 添加测试用例以验证平台和消息类型过滤器的冲突处理 * docs: remove redundant testing instructions from AGENTS.md * docs: remove redundant testing instructions from AGENTS.md * docs: remove redundant testing instructions from AGENTS.md * Merge commit '5ac9401852ddb46f337da6bcc0f9b66eed265da9' into feat/sdk-integration * Squashed 'astrbot-sdk/' changes from 85342f14..09beabeb09beabebfeat(tests): 添加测试用例以验证平台和消息类型过滤器的冲突处理 git-subtree-dir: astrbot-sdk git-subtree-split:09beabeb62* feat: enhance SDK plugin configuration handling and logging * feat: enhance SDK plugin configuration handling and logging * feat: enhance SDK plugin configuration handling and logging * feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器 * feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器 * feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器 * feat: 更新文档以反映SDK负载的JSON可序列化要求和延迟导入设计约束 * feat: add conversation.get_current capability and related schemas - Introduced CONVERSATION_GET_CURRENT_INPUT_SCHEMA and CONVERSATION_GET_CURRENT_OUTPUT_SCHEMA for handling current conversation requests. - Implemented _conversation_get_current method in BuiltinCapabilityRouterMixin to manage current conversation retrieval and creation. - Registered the new capability in CoreCapabilityBridge. - Enhanced HandlerDispatcher to inject provider request, LLM response, and event result payloads into the event handling process. - Updated tests to validate the new functionality and ensure proper payload handling. * feat: add conversation.get_current capability and related schemas - Introduced CONVERSATION_GET_CURRENT_INPUT_SCHEMA and CONVERSATION_GET_CURRENT_OUTPUT_SCHEMA for handling current conversation requests. - Implemented _conversation_get_current method in BuiltinCapabilityRouterMixin to manage current conversation retrieval and creation. - Registered the new capability in CoreCapabilityBridge. - Enhanced HandlerDispatcher to inject provider request, LLM response, and event result payloads into the event handling process. - Updated tests to validate the new functionality and ensure proper payload handling. * feat: add conversation.get_current capability and related schemas - Introduced CONVERSATION_GET_CURRENT_INPUT_SCHEMA and CONVERSATION_GET_CURRENT_OUTPUT_SCHEMA for handling current conversation requests. - Implemented _conversation_get_current method in BuiltinCapabilityRouterMixin to manage current conversation retrieval and creation. - Registered the new capability in CoreCapabilityBridge. - Enhanced HandlerDispatcher to inject provider request, LLM response, and event result payloads into the event handling process. - Updated tests to validate the new functionality and ensure proper payload handling. * Refactor tool call handling in SdkPluginBridge - Introduced a dictionary to map tool call IDs to tool names for better clarity and efficiency. - Enhanced the extraction of tool call information from raw results, ensuring compatibility with both dictionary and object formats. - Updated the logic to retrieve tool names based on tool call IDs, improving the robustness of the tool calls result processing. * Refactor tool call handling in SdkPluginBridge - Introduced a dictionary to map tool call IDs to tool names for better clarity and efficiency. - Enhanced the extraction of tool call information from raw results, ensuring compatibility with both dictionary and object formats. - Updated the logic to retrieve tool names based on tool call IDs, improving the robustness of the tool calls result processing. * Refactor tool call handling in SdkPluginBridge - Introduced a dictionary to map tool call IDs to tool names for better clarity and efficiency. - Enhanced the extraction of tool call information from raw results, ensuring compatibility with both dictionary and object formats. - Updated the logic to retrieve tool names based on tool call IDs, improving the robustness of the tool calls result processing. * feat: add session and system capabilities for plugin management and event handling - Implemented SessionCapabilityMixin with methods to manage session-level plugin states and handlers. - Added SystemCapabilityMixin to handle system-level functionalities including file management, event handling, and dynamic command registration. - Introduced methods for enabling/disabling plugins, filtering handlers, and managing LLM and TTS service states. - Registered various system capabilities for data directory access, HTML rendering, and event streaming. * feat: add session and system capabilities for plugin management and event handling - Implemented SessionCapabilityMixin with methods to manage session-level plugin states and handlers. - Added SystemCapabilityMixin to handle system-level functionalities including file management, event handling, and dynamic command registration. - Introduced methods for enabling/disabling plugins, filtering handlers, and managing LLM and TTS service states. - Registered various system capabilities for data directory access, HTML rendering, and event streaming. * Squashed 'astrbot-sdk/' changes from 09beabeb..3204c9db3204c9dbMerge sdk-remote dev into feat/sdk-integration3a2d715eRefactor tool call handling in SdkPluginBridgeed1b9665feat: add conversation.get_current capability and related schemase74123bbfeat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器bb361cf9feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器c6237f52Merge sdk-remote/dev into astrbot-sdk subtreee12029fffeat: enhance SDK plugin configuration handling and logging5e54bbb3feat: enhance SDK plugin configuration handling and loggingf48e2041Merge commit '5ac9401852ddb46f337da6bcc0f9b66eed265da9' into feat/sdk-integration619672e6Merge commit '5ac9401852ddb46f337da6bcc0f9b66eed265da9' into feat/sdk-integrationd5a3796ddocs: remove redundant testing instructions from AGENTS.md323e3f4ddocs: remove redundant testing instructions from AGENTS.mdf8438a7bMerge commit 'e45bade147ff44b43860ecff12067309e59c151a' into feat/sdk-integration96d1df85Merge commit 'e45bade147ff44b43860ecff12067309e59c151a' into feat/sdk-integrationf8a7e253feat(sdk): add merged provider config bridge and client752dc6cffeat(sdk): add merged provider config capability support git-subtree-dir: astrbot-sdk git-subtree-split:3204c9db9f* Implement feature X to enhance user experience and optimize performance * Implement feature X to enhance user experience and optimize performance * chore(sdk): stop tracking uv.lock * feat(sdk): add merged provider config capability support Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(sdk): add merged provider config bridge and client Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(tests): 添加测试用例以验证 register_task 的行为并更新测试运行说明 * feat(tests): 添加测试用例以验证平台和消息类型过滤器的冲突处理 * docs: remove redundant testing instructions from AGENTS.md * feat: enhance SDK plugin configuration handling and logging * feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器 * feat: add conversation.get_current capability and related schemas - Introduced CONVERSATION_GET_CURRENT_INPUT_SCHEMA and CONVERSATION_GET_CURRENT_OUTPUT_SCHEMA for handling current conversation requests. - Implemented _conversation_get_current method in BuiltinCapabilityRouterMixin to manage current conversation retrieval and creation. - Registered the new capability in CoreCapabilityBridge. - Enhanced HandlerDispatcher to inject provider request, LLM response, and event result payloads into the event handling process. - Updated tests to validate the new functionality and ensure proper payload handling. * Refactor tool call handling in SdkPluginBridge - Introduced a dictionary to map tool call IDs to tool names for better clarity and efficiency. - Enhanced the extraction of tool call information from raw results, ensuring compatibility with both dictionary and object formats. - Updated the logic to retrieve tool names based on tool call IDs, improving the robustness of the tool calls result processing. * feat: add session and system capabilities for plugin management and event handling - Implemented SessionCapabilityMixin with methods to manage session-level plugin states and handlers. - Added SystemCapabilityMixin to handle system-level functionalities including file management, event handling, and dynamic command registration. - Introduced methods for enabling/disabling plugins, filtering handlers, and managing LLM and TTS service states. - Registered various system capabilities for data directory access, HTML rendering, and event streaming. * fix(runtime): avoid creating Star instance in on_error fallback * fix(runtime): avoid virtual dispatch in Star.on_error fallback * feat: refactor injected parameter handling and introduce is_framework_injected_parameter utility * Squashed 'astrbot-sdk/' changes from 027c15b4..d078e510d078e510feat: refactor injected parameter handling and introduce is_framework_injected_parameter utility461f7276Merge branch 'dev' of https://github.com/united-pooh/astrbot-sdk into dev5ead59c4fix(runtime): avoid virtual dispatch in Star.on_error fallbackd2382858fix(runtime): avoid creating Star instance in on_error fallbacke961e361feat: add session and system capabilities for plugin management and event handling5a46321aRefactor tool call handling in SdkPluginBridge47698448feat: add conversation.get_current capability and related schemas9b35bec8feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器48a20240feat: enhance SDK plugin configuration handling and loggingcb593a53docs: remove redundant testing instructions from AGENTS.mdf4942076feat(tests): 添加测试用例以验证平台和消息类型过滤器的冲突处理b0f8b2d6feat(tests): 添加测试用例以验证 register_task 的行为并更新测试运行说明6e417c6dfeat(sdk): add merged provider config bridge and client659eabcefeat(sdk): add merged provider config capability support REVERT:027c15b4Implement feature X to enhance user experience and optimize performance REVERT:c272661fchore: pull sdk subtree from dev (resolve delete/modify conflict) REVERT:0a2a3592feat: add session and system capabilities for plugin management and event handling REVERT:3204c9dbMerge sdk-remote dev into feat/sdk-integration REVERT:36443f1dRefactor tool call handling in SdkPluginBridge REVERT:3a2d715eRefactor tool call handling in SdkPluginBridge REVERT:b93c2c2bfeat: add conversation.get_current capability and related schemas REVERT:ed1b9665feat: add conversation.get_current capability and related schemas REVERT:e74123bbfeat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器 REVERT:bb361cf9feat: 增强装饰器功能,添加会话命令支持及相关权限和限流装饰器 REVERT:c6237f52Merge sdk-remote/dev into astrbot-sdk subtree REVERT:e12029fffeat: enhance SDK plugin configuration handling and logging REVERT:5e54bbb3feat: enhance SDK plugin configuration handling and logging REVERT:f48e2041Merge commit '5ac9401852ddb46f337da6bcc0f9b66eed265da9' into feat/sdk-integration REVERT:619672e6Merge commit '5ac9401852ddb46f337da6bcc0f9b66eed265da9' into feat/sdk-integration REVERT:d5a3796ddocs: remove redundant testing instructions from AGENTS.md REVERT:323e3f4ddocs: remove redundant testing instructions from AGENTS.md REVERT:f8438a7bMerge commit 'e45bade147ff44b43860ecff12067309e59c151a' into feat/sdk-integration REVERT:96d1df85Merge commit 'e45bade147ff44b43860ecff12067309e59c151a' into feat/sdk-integration REVERT:f8a7e253feat(sdk): add merged provider config bridge and client REVERT:752dc6cffeat(sdk): add merged provider config capability support git-subtree-dir: astrbot-sdk git-subtree-split:d078e51051* refactor: reorganize imports and enhance type hints in sdk_bridge modules * refactor: update import paths to use Path for better compatibility * refactor(injection): centralize legacy injected parameter filtering * fix(testing): use public session waiter probe in PluginHarness * docs: add TODO for documentation content in _command_model.py * feat: add memory management capabilities to CoreCapabilityBridge and implement unit tests * feat: enhance memory search functionality and improve metadata retrieval in SDK * feat: add memory management attributes and typed provider method to CapabilityMixinHost * Implement feature X to enhance user experience and optimize performance * Refactor memory utility functions and enhance memory capability mixin - Added new utility functions for memory management in _memory_utils.py. - Refactored memory capability mixin methods to utilize the new utility functions for better readability and maintainability. - Updated PROJECT_ARCHITECTURE.md to reflect changes in documentation and structure. * Squashed 'astrbot-sdk/' changes from d078e510..208bc591208bc591Merge pull request #30 from united-pooh/refactor/unify-legacy-injected-paramsd86534a2docs: add TODO for documentation content in _command_model.py090724a7refactor(injection): centralize legacy injected parameter filtering git-subtree-dir: astrbot-sdk git-subtree-split:208bc591dd* Initial plan * docs: fix path, Python version, and client API table in PROJECT_ARCHITECTURE.md Co-authored-by: whatevertogo <149563971+whatevertogo@users.noreply.github.com> * Squashed 'astrbot-sdk/' changes from 208bc591..ad5e8d13ad5e8d13Merge pull request #37 from united-pooh/sdk/whatevertogo5751701fMerge pull request #38 from united-pooh/copilot/sub-pr-37e21acba5docs: fix path, Python version, and client API table in PROJECT_ARCHITECTURE.mdee67cab4Initial plan7d921570Refactor memory utility functions and enhance memory capability mixin git-subtree-dir: astrbot-sdk git-subtree-split:ad5e8d1397* docs: fix TODO comment formatting in _command_model.py * 集成SDK命令候选项,优化Telegram和Discord平台适配器的命令收集逻辑 * 删除AI女友插件的单元测试文件 * 更新文档,添加Telegram和Discord原生命令菜单的注册说明 * Squashed 'astrbot-sdk/' changes from ad5e8d13..5003da585003da58Merge pull request #40 from united-pooh/sdk/whatevertogob5084c44Merge branch 'sdk/whatevertogo' of https://github.com/united-pooh/astrbot-sdk into sdk/whatevertogo7559edf7docs: fix TODO comment formatting in _command_model.py git-subtree-dir: astrbot-sdk git-subtree-split:5003da58f5* fix(testing): route session waiter followups through dispatcher (#33) * fix(testing): route session waiter followups through dispatcher * fix(testing): preserve waiter context and completion state * fix(runtime): preserve session waiter plugin identity * fix(runtime): scope session waiters by plugin * fix(testing): isolate waiter replacement and followup drains * fix(runtime): normalize waiter routing inputs * fix(cli): route protocol stdout at command entry (#41) * 添加内存后端支持,优化插件内存管理逻辑 * feat(memory): enhance memory schemas and add namespace support - Updated MEMORY_SEARCH_INPUT_SCHEMA to include `namespace` and `include_descendants`. - Modified MEMORY_SEARCH_OUTPUT_SCHEMA to allow nullable `namespace`. - Added `namespace` to MEMORY_GET_INPUT_SCHEMA, MEMORY_DELETE_INPUT_SCHEMA, MEMORY_SAVE_WITH_TTL_INPUT_SCHEMA, MEMORY_GET_MANY_INPUT_SCHEMA, and MEMORY_DELETE_MANY_INPUT_SCHEMA. - Enhanced MEMORY_STATS_INPUT_SCHEMA to support `namespace` and `include_descendants`. - Updated MEMORY_GET_OUTPUT_SCHEMA and MEMORY_STATS_OUTPUT_SCHEMA to include `namespace` and `namespace_count`. - Introduced `_memory_backends` in CapabilityRouterHost and CapabilityRouterBridgeBase for better memory management. - Refactored MemoryCapabilityMixin to utilize memory backends for plugin-specific memory operations. - Improved memory search functionality to respect namespaces and include descendants based on input parameters. - Added tests to validate memory operations across different namespaces and ensure persistence across restarts. - Implemented error handling in the handler dispatcher to manage exceptions gracefully. * fix: guard session_waiter blocking usage * fix(runtime): preserve request-scoped system event overlays * test(runtime): lock peer initialization and transport failure semantics * test(loader): cover plugin reload and import isolation regressions * refactor(supervisor): clarify plugin registry sync phases * test(clients): cover provider lifecycle regressions * feat(cli): improve astr init defaults * feat(plugin): add plugin ID validation and data directory resolution - Implemented `validate_plugin_id` to ensure safe plugin identifiers. - Added `resolve_plugin_data_dir` to resolve plugin data directories securely. - Updated memory and system capabilities to utilize new plugin ID validation. - Refactored session waiter management to simplify plugin ID handling. - Enhanced tests for plugin ID validation and data directory resolution. * fix(cli): exit cleanly on init abort * feat(memory): enhance namespace handling and add tests for memory client * feat(agent): add tool status message handling and improve SDK command integration * Refactor SDK structure for backward compatibility - Moved message result and session classes to internal modules while preserving legacy import paths for compatibility. - Updated imports across the SDK to reflect the new internal structure. - Enhanced session waiter management to support multiple plugins and improve error handling. - Added tests to ensure LLM tool registration and session waiter functionality align with dispatcher expectations. - Cleaned up code and improved documentation for clarity and maintainability. * 增强异步下载功能,更新组件导入方式,并添加相关测试用例 * 添加任务重入锁以支持会话等待器的嵌套清理,并更新相关测试用例以验证后续消息的序列化处理 * 添加 .astrbot_sdk_testing 到 .gitignore 文件 * Add unit tests for provider management and tool capabilities - Introduced new test suite for provider platform management in `test_sdk_provider_platform_management.py`, covering scenarios for merged provider configurations, reserved plugin checks, and provider management functionalities. - Added tests for tool capabilities and provider queries in `test_sdk_provider_tool_platform_capabilities.py`, validating interactions with LLM tools and specialized proxies. - Removed obsolete `test_sdk_transport.py` as it contained outdated tests for transport layer functionality. * 添加多个模块和测试用例,增强SDK功能并支持单元测试 * fix(bridge): add missing capability registrations for db/memory/http/metadata Register methods for db, memory, http, and metadata capabilities exist in BasicCapabilityMixin but were never called in CoreCapabilityBridge.__init__. This caused SDK plugins using ctx.memory, ctx.db, ctx.http to fail with "LookupError: capability not found". * feat(kb): enhance knowledge base capabilities with document management and serialization * 添加知识库文档管理功能,包括文档上传、列表、获取、删除和刷新能力,更新相关的能力路由和协议模式 * feat(conversation): add ability to unset conversation persona and update related methods * 添加对话管理和元数据管理功能,包括清空对话人格和保存插件配置的能力 * feat(conversation): add test for unsetting conversation persona and verify state * feat(plugin): add save_plugin_config method and related tests for plugin configuration persistence * 优化插件配置保存方法的代码格式 * feat(sdk): enhance handler metadata with descriptions, priority, kind, and admin requirements * 添加描述、优先级和其他元数据到处理程序元数据和描述符中,优化相关功能 * feat(sdk): enhance SDK integration with local extras handling and message payloads * 添加对 MessageEvent 的额外字段支持,优化事件处理器的参数注入,增强 SDK 本地临时数据的管理能力 * 增强插件日志记录功能,添加控制台输出格式化和路径标签支持,新增单元测试以验证日志格式 * feat: Enhance command and tool management in dashboard - Refactor CommandRoute to utilize AstrBotCoreLifecycle for improved command handling. - Introduce command_key for commands to streamline toggling, renaming, and permission updates. - Implement support for SDK commands in the dashboard, marking them as read-only. - Update ToolTable and CommandTable components to use new command_key and tool_key properties. - Add runtime_kind and plugin_id to tools for better management. - Enhance API tests to cover SDK commands and tools, ensuring proper functionality and error handling. - Update localization files to include new messages related to SDK commands. * 优化插件日志记录和能力代理模块,增强异常处理,确保优先级参数为整数 * feat: 增强LLM能力,添加聊天提供者协议和异常处理,更新单元测试以验证提供者有效性 * feat: Implement SDK skill management capabilities - Added SkillCapabilityMixin to handle skill registration, unregistration, and listing. - Integrated skill management into the CoreCapabilityBridge. - Enhanced SkillManager to support SDK-registered skills, including loading, saving, and syncing skills. - Updated computer_client to utilize SkillManager for skill synchronization with sandboxes. - Refactored skill export functionality in SkillsRoute to accommodate new skill management structure. - Introduced tests for SDK skill registration, unregistration, and syncing to ensure functionality. - Improved skill handling in the dashboard and test suite to reflect changes in skill management. * 添加技能注册功能,包含技能注册、注销和列出能力的实现,增强插件能力管理 * 删除CLAUDE.md文档,移除过时的已知问题描述 * 删除 AGENTS.md 文档,移除过时的架构约束和开发命令说明 * 更新测试用例,修复插件发现失败时的错误信息,确保使用正确的运行时字段 * 修复错误处理和权限检查,新增单元测试以验证功能 * format * 增强命名空间管理,优化数据库操作,添加 HTTP 路由验证,新增并发隔离测试,完善命令模型解析单元测试 * 增强 HTTP 路由功能,添加 HTTP 方法注销逻辑的单元测试 * 格式化日志输出,优化批量插入时无内容提供的调试信息 * fix: add uv dependency for plugin environment groups * 删除代码审查文档 CODE_REVIEW_ISSUES.md * 添加消息历史管理功能,包括消息记录的增删查改,完善相关能力混合类和测试用例 * 实现 SDK 消息历史管理功能,包括记录的增删查改操作 * Add unit tests for MCP contract and capabilities in SDK - Implemented `_mcp_contract.py` to test local MCP server functionalities including listing, enabling, and disabling servers. - Created `test_sdk_mcp_capabilities.py` to cover various aspects of the CoreCapabilityBridge and SdkPluginBridge, including session management and global MCP server operations. - Introduced fake classes to simulate MCP server behavior and manage tool configurations for testing. - Ensured comprehensive coverage of MCP session lifecycle, including opening, listing tools, calling tools, and closing sessions. * feat(mcp): Implement local and global MCP server management capabilities - Added MCP management client to the context for local/global MCP service management. - Introduced decorators to acknowledge global MCP risk for plugins. - Defined schemas for MCP server operations including get, list, enable, disable, and session management. - Created MCP capability mixin to handle local and global MCP server operations. - Enhanced provider capabilities to include active local MCP tool names. - Updated capability router to support MCP functionalities and maintain session state. - Added tests for MCP functionalities, ensuring proper behavior and risk acknowledgment. --------- Co-authored-by: whatevertogo <149563971+whatevertogo@users.noreply.github.com> Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com> Co-authored-by: letr <letr007@foxmail.com> Co-authored-by: united_pooh <united_pooh@outlook.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: united_pooh <united_pooh@icloud.com> Co-authored-by: Lishiling <m18384519631@163.com> Co-authored-by: Li-shi-ling <114913764+Li-shi-ling@users.noreply.github.com> Co-authored-by: letr <123731298+letr007@users.noreply.github.com> Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: catDforD <3276453835@qq.com> Co-authored-by: united_pooh <united_pooh@MBP-F6H6T2CYVV-2219.local>
854 lines
35 KiB
Python
854 lines
35 KiB
Python
import asyncio
|
|
import copy
|
|
import os
|
|
import traceback
|
|
from collections.abc import Awaitable, Callable
|
|
from typing import Protocol, runtime_checkable
|
|
|
|
from astrbot.core import astrbot_config, logger, sp
|
|
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
|
from astrbot.core.db import BaseDatabase
|
|
from astrbot.core.utils.error_redaction import safe_error
|
|
|
|
from ..persona_mgr import PersonaManager
|
|
from .entities import ProviderType
|
|
from .provider import (
|
|
EmbeddingProvider,
|
|
Provider,
|
|
Providers,
|
|
RerankProvider,
|
|
STTProvider,
|
|
TTSProvider,
|
|
)
|
|
from .register import llm_tools, provider_cls_map
|
|
|
|
|
|
@runtime_checkable
|
|
class HasInitialize(Protocol):
|
|
async def initialize(self) -> None: ...
|
|
|
|
|
|
@runtime_checkable
|
|
class SupportsTerminate(Protocol):
|
|
def terminate(self) -> Awaitable[object]: ...
|
|
|
|
|
|
class ProviderManager:
|
|
def __init__(
|
|
self,
|
|
acm: AstrBotConfigManager,
|
|
db_helper: BaseDatabase,
|
|
persona_mgr: PersonaManager,
|
|
) -> None:
|
|
self.reload_lock = asyncio.Lock()
|
|
self.resource_lock = asyncio.Lock()
|
|
self.persona_mgr = persona_mgr
|
|
self.acm = acm
|
|
config = acm.confs["default"]
|
|
self.providers_config: list = config["provider"]
|
|
self.provider_sources_config: list = config.get("provider_sources", [])
|
|
self.provider_settings: dict = config["provider_settings"]
|
|
self.provider_stt_settings: dict = config.get("provider_stt_settings", {})
|
|
self.provider_tts_settings: dict = config.get("provider_tts_settings", {})
|
|
|
|
# 人格相关属性,v4.0.0 版本后被废弃,推荐使用 PersonaManager
|
|
self.default_persona_name = persona_mgr.default_persona
|
|
|
|
self.provider_insts: list[Provider] = []
|
|
"""加载的 Provider 的实例"""
|
|
self.stt_provider_insts: list[STTProvider] = []
|
|
"""加载的 Speech To Text Provider 的实例"""
|
|
self.tts_provider_insts: list[TTSProvider] = []
|
|
"""加载的 Text To Speech Provider 的实例"""
|
|
self.embedding_provider_insts: list[EmbeddingProvider] = []
|
|
"""加载的 Embedding Provider 的实例"""
|
|
self.rerank_provider_insts: list[RerankProvider] = []
|
|
"""加载的 Rerank Provider 的实例"""
|
|
self.inst_map: dict[
|
|
str,
|
|
Providers,
|
|
] = {}
|
|
"""Provider 实例映射. key: provider_id, value: Provider 实例"""
|
|
self.llm_tools = llm_tools
|
|
|
|
self.curr_provider_inst: Provider | None = None
|
|
"""默认的 Provider 实例。已弃用,请使用 get_using_provider() 方法获取当前使用的 Provider 实例。"""
|
|
self.curr_stt_provider_inst: STTProvider | None = None
|
|
"""默认的 Speech To Text Provider 实例。已弃用,请使用 get_using_provider() 方法获取当前使用的 Provider 实例。"""
|
|
self.curr_tts_provider_inst: TTSProvider | None = None
|
|
"""默认的 Text To Speech Provider 实例。已弃用,请使用 get_using_provider() 方法获取当前使用的 Provider 实例。"""
|
|
self.db_helper = db_helper
|
|
self._provider_change_callback: (
|
|
Callable[[str, ProviderType, str | None], None] | None
|
|
) = None
|
|
self._provider_change_hooks: list[
|
|
Callable[[str, ProviderType, str | None], None]
|
|
] = []
|
|
self._mcp_init_task: asyncio.Task | None = None
|
|
|
|
def set_provider_change_callback(
|
|
self,
|
|
cb: Callable[[str, ProviderType, str | None], None] | None,
|
|
) -> None:
|
|
# Backward-compatible single-callback setter.
|
|
# This callback coexists with register_provider_change_hook subscriptions.
|
|
self._provider_change_callback = cb
|
|
|
|
def register_provider_change_hook(
|
|
self,
|
|
hook: Callable[[str, ProviderType, str | None], None],
|
|
) -> None:
|
|
if hook not in self._provider_change_hooks:
|
|
self._provider_change_hooks.append(hook)
|
|
|
|
def unregister_provider_change_hook(
|
|
self,
|
|
hook: Callable[[str, ProviderType, str | None], None],
|
|
) -> None:
|
|
if hook in self._provider_change_hooks:
|
|
self._provider_change_hooks.remove(hook)
|
|
|
|
def _notify_provider_changed(
|
|
self,
|
|
provider_id: str,
|
|
provider_type: ProviderType,
|
|
umo: str | None,
|
|
) -> None:
|
|
if self._provider_change_callback is not None:
|
|
try:
|
|
self._provider_change_callback(provider_id, provider_type, umo)
|
|
except Exception as e:
|
|
logger.warning(
|
|
"调用 provider 变更回调失败: provider_id=%s, type=%s, err=%s",
|
|
provider_id,
|
|
provider_type,
|
|
safe_error("", e),
|
|
)
|
|
for hook in list(self._provider_change_hooks):
|
|
if hook is self._provider_change_callback:
|
|
continue
|
|
try:
|
|
hook(provider_id, provider_type, umo)
|
|
except Exception as e:
|
|
logger.warning(
|
|
"调用 provider 变更钩子失败: provider_id=%s, type=%s, err=%s",
|
|
provider_id,
|
|
provider_type,
|
|
safe_error("", e),
|
|
)
|
|
|
|
@property
|
|
def persona_configs(self) -> list:
|
|
"""动态获取最新的 persona 配置"""
|
|
return self.persona_mgr.persona_v3_config
|
|
|
|
@property
|
|
def personas(self) -> list:
|
|
"""动态获取最新的 personas 列表"""
|
|
return self.persona_mgr.personas_v3
|
|
|
|
@property
|
|
def selected_default_persona(self):
|
|
"""动态获取最新的默认选中 persona。已弃用,请使用 context.persona_mgr.get_default_persona_v3()"""
|
|
return self.persona_mgr.selected_default_persona_v3
|
|
|
|
async def set_provider(
|
|
self,
|
|
provider_id: str,
|
|
provider_type: ProviderType,
|
|
umo: str | None = None,
|
|
) -> None:
|
|
"""设置提供商。
|
|
|
|
Args:
|
|
provider_id (str): 提供商 ID。
|
|
provider_type (ProviderType): 提供商类型。
|
|
umo (str, optional): 用户会话 ID,用于提供商会话隔离。
|
|
|
|
Version 4.0.0: 这个版本下已经默认隔离提供商
|
|
|
|
"""
|
|
if provider_id not in self.inst_map:
|
|
raise ValueError(f"提供商 {provider_id} 不存在,无法设置。")
|
|
if umo:
|
|
await sp.session_put(
|
|
umo,
|
|
f"provider_perf_{provider_type.value}",
|
|
provider_id,
|
|
)
|
|
self._notify_provider_changed(provider_id, provider_type, umo)
|
|
return
|
|
# 不启用提供商会话隔离模式的情况
|
|
|
|
prov = self.inst_map[provider_id]
|
|
if provider_type == ProviderType.TEXT_TO_SPEECH and isinstance(
|
|
prov,
|
|
TTSProvider,
|
|
):
|
|
self.curr_tts_provider_inst = prov
|
|
await sp.put_async(
|
|
key="curr_provider_tts",
|
|
value=provider_id,
|
|
scope="global",
|
|
scope_id="global",
|
|
)
|
|
self._notify_provider_changed(provider_id, provider_type, umo)
|
|
elif provider_type == ProviderType.SPEECH_TO_TEXT and isinstance(
|
|
prov,
|
|
STTProvider,
|
|
):
|
|
self.curr_stt_provider_inst = prov
|
|
await sp.put_async(
|
|
key="curr_provider_stt",
|
|
value=provider_id,
|
|
scope="global",
|
|
scope_id="global",
|
|
)
|
|
self._notify_provider_changed(provider_id, provider_type, umo)
|
|
elif provider_type == ProviderType.CHAT_COMPLETION and isinstance(
|
|
prov,
|
|
Provider,
|
|
):
|
|
self.curr_provider_inst = prov
|
|
await sp.put_async(
|
|
key="curr_provider",
|
|
value=provider_id,
|
|
scope="global",
|
|
scope_id="global",
|
|
)
|
|
self._notify_provider_changed(provider_id, provider_type, umo)
|
|
|
|
async def get_provider_by_id(self, provider_id: str) -> Providers | None:
|
|
"""根据提供商 ID 获取提供商实例"""
|
|
return self.inst_map.get(provider_id)
|
|
|
|
def get_using_provider(
|
|
self, provider_type: ProviderType, umo=None
|
|
) -> Providers | None:
|
|
"""获取正在使用的提供商实例。
|
|
|
|
Args:
|
|
provider_type (ProviderType): 提供商类型。
|
|
umo (str, optional): 用户会话 ID,用于提供商会话隔离。
|
|
|
|
Returns:
|
|
Provider: 正在使用的提供商实例。
|
|
|
|
"""
|
|
provider = None
|
|
provider_id = None
|
|
if umo:
|
|
provider_id = sp.get(
|
|
f"provider_perf_{provider_type.value}",
|
|
None,
|
|
scope="umo",
|
|
scope_id=umo,
|
|
)
|
|
if provider_id:
|
|
provider = self.inst_map.get(provider_id)
|
|
if not provider:
|
|
# default setting
|
|
config = self.acm.get_conf(umo)
|
|
if provider_type == ProviderType.CHAT_COMPLETION:
|
|
provider_id = config["provider_settings"].get("default_provider_id")
|
|
provider = self.inst_map.get(provider_id)
|
|
if not provider:
|
|
provider = self.provider_insts[0] if self.provider_insts else None
|
|
elif provider_type == ProviderType.SPEECH_TO_TEXT:
|
|
provider_id = config["provider_stt_settings"].get("provider_id")
|
|
if not provider_id:
|
|
return None
|
|
provider = self.inst_map.get(provider_id)
|
|
if not provider:
|
|
provider = (
|
|
self.stt_provider_insts[0] if self.stt_provider_insts else None
|
|
)
|
|
elif provider_type == ProviderType.TEXT_TO_SPEECH:
|
|
provider_id = config["provider_tts_settings"].get("provider_id")
|
|
if not provider_id:
|
|
return None
|
|
provider = self.inst_map.get(provider_id)
|
|
if not provider:
|
|
provider = (
|
|
self.tts_provider_insts[0] if self.tts_provider_insts else None
|
|
)
|
|
else:
|
|
raise ValueError(f"Unknown provider type: {provider_type}")
|
|
|
|
if not provider and provider_id:
|
|
logger.warning(
|
|
f"没有找到 ID 为 {provider_id} 的提供商,这可能是由于您修改了提供商(模型)ID 导致的。"
|
|
)
|
|
|
|
return provider
|
|
|
|
async def initialize(self) -> None:
|
|
# 逐个初始化提供商
|
|
for provider_config in self.providers_config:
|
|
try:
|
|
await self.load_provider(provider_config)
|
|
except Exception as e:
|
|
logger.error(traceback.format_exc())
|
|
logger.error(e)
|
|
|
|
selected_provider_id = await sp.get_async(
|
|
key="curr_provider",
|
|
default=self.provider_settings.get("default_provider_id"),
|
|
scope="global",
|
|
scope_id="global",
|
|
)
|
|
selected_stt_provider_id = await sp.get_async(
|
|
key="curr_provider_stt",
|
|
default=self.provider_stt_settings.get("provider_id"),
|
|
scope="global",
|
|
scope_id="global",
|
|
)
|
|
selected_tts_provider_id = await sp.get_async(
|
|
key="curr_provider_tts",
|
|
default=self.provider_tts_settings.get("provider_id"),
|
|
scope="global",
|
|
scope_id="global",
|
|
)
|
|
|
|
temp_provider = (
|
|
self.inst_map.get(selected_provider_id)
|
|
if isinstance(selected_provider_id, str)
|
|
else None
|
|
)
|
|
self.curr_provider_inst = (
|
|
temp_provider if isinstance(temp_provider, Provider) else None
|
|
)
|
|
if not self.curr_provider_inst and self.provider_insts:
|
|
self.curr_provider_inst = self.provider_insts[0]
|
|
|
|
temp_stt = (
|
|
self.inst_map.get(selected_stt_provider_id)
|
|
if isinstance(selected_stt_provider_id, str)
|
|
else None
|
|
)
|
|
self.curr_stt_provider_inst = (
|
|
temp_stt if isinstance(temp_stt, STTProvider) else None
|
|
)
|
|
if not self.curr_stt_provider_inst and self.stt_provider_insts:
|
|
self.curr_stt_provider_inst = self.stt_provider_insts[0]
|
|
|
|
temp_tts = (
|
|
self.inst_map.get(selected_tts_provider_id)
|
|
if isinstance(selected_tts_provider_id, str)
|
|
else None
|
|
)
|
|
self.curr_tts_provider_inst = (
|
|
temp_tts if isinstance(temp_tts, TTSProvider) else None
|
|
)
|
|
if not self.curr_tts_provider_inst and self.tts_provider_insts:
|
|
self.curr_tts_provider_inst = self.tts_provider_insts[0]
|
|
|
|
async def _init_mcp_clients_bg() -> None:
|
|
try:
|
|
await self.llm_tools.init_mcp_clients()
|
|
except Exception:
|
|
logger.error("MCP init background task failed", exc_info=True)
|
|
|
|
if self._mcp_init_task is None or self._mcp_init_task.done():
|
|
self._mcp_init_task = asyncio.create_task(
|
|
_init_mcp_clients_bg(),
|
|
name="provider-manager:mcp-init",
|
|
)
|
|
|
|
def dynamic_import_provider(self, type: str) -> None:
|
|
"""动态导入提供商适配器模块
|
|
|
|
Args:
|
|
type (str): 提供商请求类型。
|
|
|
|
Raises:
|
|
ImportError: 如果提供商类型未知或无法导入对应模块,则抛出异常。
|
|
"""
|
|
match type:
|
|
case "openai_chat_completion":
|
|
from .sources.openai_source import (
|
|
ProviderOpenAIOfficial as ProviderOpenAIOfficial,
|
|
)
|
|
case "zhipu_chat_completion":
|
|
from .sources.zhipu_source import ProviderZhipu as ProviderZhipu
|
|
case "groq_chat_completion":
|
|
from .sources.groq_source import ProviderGroq as ProviderGroq
|
|
case "xai_chat_completion":
|
|
from .sources.xai_source import ProviderXAI as ProviderXAI
|
|
case "aihubmix_chat_completion":
|
|
from .sources.oai_aihubmix_source import (
|
|
ProviderAIHubMix as ProviderAIHubMix,
|
|
)
|
|
case "openrouter_chat_completion":
|
|
from .sources.openrouter_source import (
|
|
ProviderOpenRouter as ProviderOpenRouter,
|
|
)
|
|
case "anthropic_chat_completion":
|
|
from .sources.anthropic_source import (
|
|
ProviderAnthropic as ProviderAnthropic,
|
|
)
|
|
case "kimi_code_chat_completion":
|
|
from .sources.kimi_code_source import (
|
|
ProviderKimiCode as ProviderKimiCode,
|
|
)
|
|
case "googlegenai_chat_completion":
|
|
from .sources.gemini_source import (
|
|
ProviderGoogleGenAI as ProviderGoogleGenAI,
|
|
)
|
|
case "sensevoice_stt_selfhost":
|
|
from .sources.sensevoice_selfhosted_source import (
|
|
ProviderSenseVoiceSTTSelfHost as ProviderSenseVoiceSTTSelfHost,
|
|
)
|
|
case "openai_whisper_api":
|
|
from .sources.whisper_api_source import (
|
|
ProviderOpenAIWhisperAPI as ProviderOpenAIWhisperAPI,
|
|
)
|
|
case "mimo_stt_api":
|
|
from .sources.mimo_stt_api_source import (
|
|
ProviderMiMoSTTAPI as ProviderMiMoSTTAPI,
|
|
)
|
|
case "openai_whisper_selfhost":
|
|
from .sources.whisper_selfhosted_source import (
|
|
ProviderOpenAIWhisperSelfHost as ProviderOpenAIWhisperSelfHost,
|
|
)
|
|
case "xinference_stt":
|
|
from .sources.xinference_stt_provider import (
|
|
ProviderXinferenceSTT as ProviderXinferenceSTT,
|
|
)
|
|
case "openai_tts_api":
|
|
from .sources.openai_tts_api_source import (
|
|
ProviderOpenAITTSAPI as ProviderOpenAITTSAPI,
|
|
)
|
|
case "mimo_tts_api":
|
|
from .sources.mimo_tts_api_source import (
|
|
ProviderMiMoTTSAPI as ProviderMiMoTTSAPI,
|
|
)
|
|
case "genie_tts":
|
|
from .sources.genie_tts import (
|
|
GenieTTSProvider as GenieTTSProvider,
|
|
)
|
|
case "edge_tts":
|
|
from .sources.edge_tts_source import (
|
|
ProviderEdgeTTS as ProviderEdgeTTS,
|
|
)
|
|
case "gsv_tts_selfhost":
|
|
from .sources.gsv_selfhosted_source import (
|
|
ProviderGSVTTS as ProviderGSVTTS,
|
|
)
|
|
case "gsvi_tts_api":
|
|
from .sources.gsvi_tts_source import (
|
|
ProviderGSVITTS as ProviderGSVITTS,
|
|
)
|
|
case "fishaudio_tts_api":
|
|
from .sources.fishaudio_tts_api_source import (
|
|
ProviderFishAudioTTSAPI as ProviderFishAudioTTSAPI,
|
|
)
|
|
case "dashscope_tts":
|
|
from .sources.dashscope_tts import (
|
|
ProviderDashscopeTTSAPI as ProviderDashscopeTTSAPI,
|
|
)
|
|
case "azure_tts":
|
|
from .sources.azure_tts_source import (
|
|
AzureTTSProvider as AzureTTSProvider,
|
|
)
|
|
case "minimax_tts_api":
|
|
from .sources.minimax_tts_api_source import (
|
|
ProviderMiniMaxTTSAPI as ProviderMiniMaxTTSAPI,
|
|
)
|
|
case "volcengine_tts":
|
|
from .sources.volcengine_tts import (
|
|
ProviderVolcengineTTS as ProviderVolcengineTTS,
|
|
)
|
|
case "gemini_tts":
|
|
from .sources.gemini_tts_source import (
|
|
ProviderGeminiTTSAPI as ProviderGeminiTTSAPI,
|
|
)
|
|
case "openai_embedding":
|
|
from .sources.openai_embedding_source import (
|
|
OpenAIEmbeddingProvider as OpenAIEmbeddingProvider,
|
|
)
|
|
case "gemini_embedding":
|
|
from .sources.gemini_embedding_source import (
|
|
GeminiEmbeddingProvider as GeminiEmbeddingProvider,
|
|
)
|
|
case "vllm_rerank":
|
|
from .sources.vllm_rerank_source import (
|
|
VLLMRerankProvider as VLLMRerankProvider,
|
|
)
|
|
case "xinference_rerank":
|
|
from .sources.xinference_rerank_source import (
|
|
XinferenceRerankProvider as XinferenceRerankProvider,
|
|
)
|
|
case "bailian_rerank":
|
|
from .sources.bailian_rerank_source import (
|
|
BailianRerankProvider as BailianRerankProvider,
|
|
)
|
|
|
|
def get_merged_provider_config(self, provider_config: dict) -> dict:
|
|
"""获取 provider 配置和 provider_source 配置合并后的结果
|
|
|
|
Returns:
|
|
dict: 合并后的 provider 配置,key 为 provider id,value 为合并后的配置字典
|
|
"""
|
|
pc = copy.deepcopy(provider_config)
|
|
provider_source_id = pc.get("provider_source_id", "")
|
|
if provider_source_id:
|
|
provider_source = None
|
|
for ps in self.provider_sources_config:
|
|
if ps.get("id") == provider_source_id:
|
|
provider_source = ps
|
|
break
|
|
|
|
if provider_source:
|
|
# 合并配置,provider 的配置优先级更高
|
|
merged_config = {**provider_source, **pc}
|
|
# 保持 id 为 provider 的 id,而不是 source 的 id
|
|
merged_config["id"] = pc["id"]
|
|
pc = merged_config
|
|
return pc
|
|
|
|
def _resolve_env_key_list(self, provider_config: dict) -> dict:
|
|
keys = provider_config.get("key", [])
|
|
if not isinstance(keys, list):
|
|
return provider_config
|
|
resolved_keys = []
|
|
for idx, key in enumerate(keys):
|
|
if isinstance(key, str) and key.startswith("$"):
|
|
env_key = key[1:]
|
|
if env_key.startswith("{") and env_key.endswith("}"):
|
|
env_key = env_key[1:-1]
|
|
if env_key:
|
|
env_val = os.getenv(env_key)
|
|
if env_val is None:
|
|
provider_id = provider_config.get("id")
|
|
logger.warning(
|
|
f"Provider {provider_id} 配置项 key[{idx}] 使用环境变量 {env_key} 但未设置。",
|
|
)
|
|
resolved_keys.append("")
|
|
else:
|
|
resolved_keys.append(env_val)
|
|
else:
|
|
resolved_keys.append(key)
|
|
else:
|
|
resolved_keys.append(key)
|
|
provider_config["key"] = resolved_keys
|
|
return provider_config
|
|
|
|
async def load_provider(self, provider_config: dict) -> None:
|
|
# 如果 provider_source_id 存在且不为空,则从 provider_sources 中找到对应的配置并合并
|
|
provider_config = self.get_merged_provider_config(provider_config)
|
|
|
|
if provider_config.get("provider_type", "") == "chat_completion":
|
|
provider_config = self._resolve_env_key_list(provider_config)
|
|
|
|
if not provider_config["enable"]:
|
|
logger.info(f"Provider {provider_config['id']} is disabled, skipping")
|
|
return
|
|
if provider_config.get("provider_type", "") == "agent_runner":
|
|
return
|
|
|
|
logger.info(
|
|
f"载入 {provider_config['type']}({provider_config['id']}) 服务提供商 ...",
|
|
)
|
|
|
|
# 动态导入
|
|
try:
|
|
self.dynamic_import_provider(provider_config["type"])
|
|
except (ImportError, ModuleNotFoundError) as e:
|
|
logger.critical(
|
|
f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。可能是因为有未安装的依赖。",
|
|
exc_info=True,
|
|
)
|
|
return
|
|
except Exception as e:
|
|
logger.critical(
|
|
f"加载 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}。未知原因",
|
|
exc_info=True,
|
|
)
|
|
return
|
|
|
|
if provider_config["type"] not in provider_cls_map:
|
|
logger.error(
|
|
f"未找到适用于 {provider_config['type']}({provider_config['id']}) 的提供商适配器,请检查是否已经安装或者名称填写错误。已跳过。",
|
|
exc_info=True,
|
|
)
|
|
return
|
|
|
|
provider_metadata = provider_cls_map[provider_config["type"]]
|
|
try:
|
|
# 按任务实例化提供商
|
|
cls_type = provider_metadata.cls_type
|
|
if not cls_type:
|
|
logger.error(f"无法找到 {provider_metadata.type} 的类")
|
|
return
|
|
|
|
provider_metadata.id = provider_config["id"]
|
|
|
|
match provider_metadata.provider_type:
|
|
case ProviderType.SPEECH_TO_TEXT:
|
|
# STT 任务
|
|
if not issubclass(cls_type, STTProvider):
|
|
raise TypeError(
|
|
f"Provider class {cls_type} is not a subclass of STTProvider"
|
|
)
|
|
inst = cls_type(provider_config, self.provider_settings)
|
|
|
|
if isinstance(inst, HasInitialize):
|
|
await inst.initialize()
|
|
|
|
self.stt_provider_insts.append(inst)
|
|
if (
|
|
self.provider_stt_settings.get("provider_id")
|
|
== provider_config["id"]
|
|
):
|
|
self.curr_stt_provider_inst = inst
|
|
logger.info(
|
|
f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前语音转文本提供商适配器。",
|
|
)
|
|
if not self.curr_stt_provider_inst:
|
|
self.curr_stt_provider_inst = inst
|
|
|
|
case ProviderType.TEXT_TO_SPEECH:
|
|
# TTS 任务
|
|
if not issubclass(cls_type, TTSProvider):
|
|
raise TypeError(
|
|
f"Provider class {cls_type} is not a subclass of TTSProvider"
|
|
)
|
|
inst = cls_type(provider_config, self.provider_settings)
|
|
|
|
if isinstance(inst, HasInitialize):
|
|
await inst.initialize()
|
|
|
|
self.tts_provider_insts.append(inst)
|
|
if (
|
|
self.provider_settings.get("provider_id")
|
|
== provider_config["id"]
|
|
):
|
|
self.curr_tts_provider_inst = inst
|
|
logger.info(
|
|
f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前文本转语音提供商适配器。",
|
|
)
|
|
if not self.curr_tts_provider_inst:
|
|
self.curr_tts_provider_inst = inst
|
|
|
|
case ProviderType.CHAT_COMPLETION:
|
|
# 文本生成任务
|
|
if not issubclass(cls_type, Provider):
|
|
raise TypeError(
|
|
f"Provider class {cls_type} is not a subclass of Provider"
|
|
)
|
|
inst = cls_type(
|
|
provider_config,
|
|
self.provider_settings,
|
|
)
|
|
|
|
if isinstance(inst, HasInitialize):
|
|
await inst.initialize()
|
|
|
|
self.provider_insts.append(inst)
|
|
if (
|
|
self.provider_settings.get("default_provider_id")
|
|
== provider_config["id"]
|
|
):
|
|
self.curr_provider_inst = inst
|
|
logger.info(
|
|
f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前提供商适配器。",
|
|
)
|
|
if not self.curr_provider_inst:
|
|
self.curr_provider_inst = inst
|
|
|
|
case ProviderType.EMBEDDING:
|
|
if not issubclass(cls_type, EmbeddingProvider):
|
|
raise TypeError(
|
|
f"Provider class {cls_type} is not a subclass of EmbeddingProvider"
|
|
)
|
|
inst = cls_type(provider_config, self.provider_settings)
|
|
if isinstance(inst, HasInitialize):
|
|
await inst.initialize()
|
|
self.embedding_provider_insts.append(inst)
|
|
case ProviderType.RERANK:
|
|
if not issubclass(cls_type, RerankProvider):
|
|
raise TypeError(
|
|
f"Provider class {cls_type} is not a subclass of RerankProvider"
|
|
)
|
|
inst = cls_type(provider_config, self.provider_settings)
|
|
if isinstance(inst, HasInitialize):
|
|
await inst.initialize()
|
|
self.rerank_provider_insts.append(inst)
|
|
case _:
|
|
# 未知供应商抛出异常,确保inst初始化
|
|
# Should be unreachable
|
|
raise Exception(
|
|
f"未知的提供商类型:{provider_metadata.provider_type}"
|
|
)
|
|
|
|
self.inst_map[provider_config["id"]] = inst
|
|
except Exception as e:
|
|
logger.error(
|
|
f"实例化 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}",
|
|
)
|
|
raise Exception(
|
|
f"实例化 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}",
|
|
)
|
|
|
|
async def reload(self, provider_config: dict) -> None:
|
|
async with self.reload_lock:
|
|
await self.terminate_provider(provider_config["id"])
|
|
if provider_config["enable"]:
|
|
await self.load_provider(provider_config)
|
|
|
|
# 和配置文件保持同步
|
|
self.providers_config = astrbot_config["provider"]
|
|
self.provider_sources_config = astrbot_config.get("provider_sources", [])
|
|
config_ids = [provider["id"] for provider in self.providers_config]
|
|
logger.info(f"providers in user's config: {config_ids}")
|
|
for key in list(self.inst_map.keys()):
|
|
if key not in config_ids:
|
|
await self.terminate_provider(key)
|
|
|
|
if len(self.provider_insts) == 0:
|
|
self.curr_provider_inst = None
|
|
elif self.curr_provider_inst is None and len(self.provider_insts) > 0:
|
|
self.curr_provider_inst = self.provider_insts[0]
|
|
logger.info(
|
|
f"自动选择 {self.curr_provider_inst.meta().id} 作为当前提供商适配器。",
|
|
)
|
|
|
|
if len(self.stt_provider_insts) == 0:
|
|
self.curr_stt_provider_inst = None
|
|
elif (
|
|
self.curr_stt_provider_inst is None and len(self.stt_provider_insts) > 0
|
|
):
|
|
self.curr_stt_provider_inst = self.stt_provider_insts[0]
|
|
logger.info(
|
|
f"自动选择 {self.curr_stt_provider_inst.meta().id} 作为当前语音转文本提供商适配器。",
|
|
)
|
|
|
|
if len(self.tts_provider_insts) == 0:
|
|
self.curr_tts_provider_inst = None
|
|
elif (
|
|
self.curr_tts_provider_inst is None and len(self.tts_provider_insts) > 0
|
|
):
|
|
self.curr_tts_provider_inst = self.tts_provider_insts[0]
|
|
logger.info(
|
|
f"自动选择 {self.curr_tts_provider_inst.meta().id} 作为当前文本转语音提供商适配器。",
|
|
)
|
|
|
|
def get_insts(self):
|
|
return self.provider_insts
|
|
|
|
async def terminate_provider(self, provider_id: str) -> None:
|
|
if provider_id in self.inst_map:
|
|
logger.info(
|
|
f"终止 {provider_id} 提供商适配器({len(self.provider_insts)}, {len(self.stt_provider_insts)}, {len(self.tts_provider_insts)}) ...",
|
|
)
|
|
|
|
if self.inst_map[provider_id] in self.provider_insts:
|
|
prov_inst = self.inst_map[provider_id]
|
|
if isinstance(prov_inst, Provider):
|
|
self.provider_insts.remove(prov_inst)
|
|
if self.inst_map[provider_id] in self.stt_provider_insts:
|
|
prov_inst = self.inst_map[provider_id]
|
|
if isinstance(prov_inst, STTProvider):
|
|
self.stt_provider_insts.remove(prov_inst)
|
|
if self.inst_map[provider_id] in self.tts_provider_insts:
|
|
prov_inst = self.inst_map[provider_id]
|
|
if isinstance(prov_inst, TTSProvider):
|
|
self.tts_provider_insts.remove(prov_inst)
|
|
|
|
if self.inst_map[provider_id] == self.curr_provider_inst:
|
|
self.curr_provider_inst = None
|
|
if self.inst_map[provider_id] == self.curr_stt_provider_inst:
|
|
self.curr_stt_provider_inst = None
|
|
if self.inst_map[provider_id] == self.curr_tts_provider_inst:
|
|
self.curr_tts_provider_inst = None
|
|
|
|
inst = self.inst_map[provider_id]
|
|
if isinstance(inst, SupportsTerminate):
|
|
await inst.terminate()
|
|
|
|
logger.info(
|
|
f"{provider_id} 提供商适配器已终止({len(self.provider_insts)}, {len(self.stt_provider_insts)}, {len(self.tts_provider_insts)})",
|
|
)
|
|
del self.inst_map[provider_id]
|
|
|
|
async def delete_provider(
|
|
self, provider_id: str | None = None, provider_source_id: str | None = None
|
|
) -> None:
|
|
"""Delete provider and/or provider source from config and terminate the instances. Config will be saved after deletion."""
|
|
async with self.resource_lock:
|
|
# delete from config
|
|
target_prov_ids = []
|
|
if provider_id:
|
|
target_prov_ids.append(provider_id)
|
|
else:
|
|
for prov in self.providers_config:
|
|
if prov.get("provider_source_id") == provider_source_id:
|
|
target_prov_ids.append(prov.get("id"))
|
|
config = self.acm.default_conf
|
|
for tpid in target_prov_ids:
|
|
await self.terminate_provider(tpid)
|
|
config["provider"] = [
|
|
prov for prov in config["provider"] if prov.get("id") != tpid
|
|
]
|
|
config.save_config()
|
|
logger.info(f"Provider {target_prov_ids} 已从配置中删除。")
|
|
|
|
async def update_provider(self, origin_provider_id: str, new_config: dict) -> None:
|
|
"""Update provider config and reload the instance. Config will be saved after update."""
|
|
async with self.resource_lock:
|
|
npid = new_config.get("id", None)
|
|
if not npid:
|
|
raise ValueError("New provider config must have an 'id' field")
|
|
config = self.acm.default_conf
|
|
for provider in config["provider"]:
|
|
if (
|
|
provider.get("id", None) == npid
|
|
and provider.get("id", None) != origin_provider_id
|
|
):
|
|
raise ValueError(f"Provider ID {npid} already exists")
|
|
# update config
|
|
for idx, provider in enumerate(config["provider"]):
|
|
if provider.get("id", None) == origin_provider_id:
|
|
config["provider"][idx] = new_config
|
|
break
|
|
else:
|
|
raise ValueError(f"Provider ID {origin_provider_id} not found")
|
|
config.save_config()
|
|
# reload instance
|
|
await self.reload(new_config)
|
|
|
|
async def create_provider(self, new_config: dict) -> None:
|
|
"""Add new provider config and load the instance. Config will be saved after addition."""
|
|
async with self.resource_lock:
|
|
npid = new_config.get("id", None)
|
|
if not npid:
|
|
raise ValueError("New provider config must have an 'id' field")
|
|
config = self.acm.default_conf
|
|
for provider in config["provider"]:
|
|
if provider.get("id", None) == npid:
|
|
raise ValueError(f"Provider ID {npid} already exists")
|
|
# add to config
|
|
config["provider"].append(new_config)
|
|
config.save_config()
|
|
# load instance
|
|
await self.load_provider(new_config)
|
|
# sync in-memory config for API queries (e.g., embedding provider list)
|
|
self.providers_config = astrbot_config["provider"]
|
|
|
|
async def terminate(self) -> None:
|
|
if self._mcp_init_task and not self._mcp_init_task.done():
|
|
self._mcp_init_task.cancel()
|
|
try:
|
|
await self._mcp_init_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
for provider_inst in self.provider_insts:
|
|
if isinstance(provider_inst, SupportsTerminate):
|
|
await provider_inst.terminate()
|
|
try:
|
|
await self.llm_tools.disable_mcp_server()
|
|
except Exception:
|
|
logger.error("Error while disabling MCP servers", exc_info=True)
|