Skip to content

refactor:重构内部工具的注册机制,并进行解耦#6071

Open
advent259141 wants to merge 19 commits intoAstrBotDevs:masterfrom
advent259141:agent-fix-clean
Open

refactor:重构内部工具的注册机制,并进行解耦#6071
advent259141 wants to merge 19 commits intoAstrBotDevs:masterfrom
advent259141:agent-fix-clean

Conversation

@advent259141
Copy link
Member

@advent259141 advent259141 commented Mar 11, 2026

当前Astrbot的内部工具注册十分混乱,且和agent模块强耦合,估计重构

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

由 Sourcery 生成的总结

通过引入可复用的工具提供者和集中管理的提示词常量,将 AstrBot 的内部工具注册与主代理解耦,同时在 WebUI 中明确区分内置工具并禁止对其进行开关操作。

新功能:

  • 引入 ToolProvider 协议和上下文,在构建时向主代理注入工具和系统提示词附加内容。
  • 为电脑使用工具和定时任务(cron job)管理添加专用的提供者,以支持基于运行时环境的工具选择。
  • 通过统一的注册机制暴露内部工具(computer、cron、知识库搜索、send-message),使其对 WebUI 和子代理可用。

缺陷修复:

  • 通过在 payload 参数中定义数组项结构,收紧 Neo 技能 payload 的模式(schema)。

改进:

  • 将电脑使用和 cron 工具逻辑从主代理中重构出来,放入模块化的 provider 和工具模块,简化主代理构建逻辑。
  • 将常用的系统提示词字符串和模板集中到单一的 prompts 模块中,避免在各组件间重复。
  • 更新后台任务和 cron 唤醒流程,使其使用共享的提示词模板和新的工具提供者,而不是硬编码的工具和字符串。
  • FunctionTool 增加 source 属性,并通过 MCP 工具和仪表盘 API 进行传递,以便区分工具来源。
  • 调整仪表盘中的工具表格,视觉上标记内部工具,禁止对其进行开关操作,并将其来源显示为 AstrBot。
Original summary in English

Summary by Sourcery

Decouple AstrBot’s internal tool registration from the main agent by introducing reusable tool providers and centralized prompt constants, while surfacing built-in tools distinctly in the WebUI and preventing them from being toggled.

New Features:

  • Introduce a ToolProvider protocol and context to inject tools and system-prompt addons into the main agent at build time.
  • Add dedicated providers for computer-use tooling and cron job management, enabling runtime-dependent tool selection.
  • Expose internal tools (computer, cron, knowledge base search, send-message) via a unified registration mechanism so they are available to the WebUI and subagents.

Bug Fixes:

  • Tighten the schema for Neo skill payloads by defining array item structure in the payload parameter.

Enhancements:

  • Refactor computer-use and cron tooling logic out of the main agent into modular provider and tool modules, simplifying the main agent builder.
  • Centralize frequently used system prompt strings and templates into a single prompts module to avoid duplication across components.
  • Update background-task and cron wake flows to use shared prompt templates and the new tool providers instead of hardcoded tools and strings.
  • Extend FunctionTool with a source attribute and propagate it through MCP tools and dashboard APIs so origins of tools can be distinguished.
  • Adjust the dashboard tools table to visually mark internal tools, disable toggling them, and show their origin as AstrBot.

@auto-assign auto-assign bot requested review from LIghtJUNction and Soulter March 11, 2026 17:57
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Mar 11, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在通过引入一个解耦的工具提供者(ToolProvider)机制,全面重构Astrbot的内部工具注册和管理方式。这一改变将工具的定义、加载和系统提示词的注入从核心代理逻辑中分离出来,显著提升了代码的模块化、可扩展性和可维护性。通过将不同类型的工具(如计算机使用工具、定时任务工具)封装到各自的提供者中,系统能够更清晰地管理工具生命周期,并为未来的功能扩展奠定基础。同时,仪表盘界面也进行了相应更新,以更好地展示和管理这些工具。

Highlights

  • 工具注册机制重构: 重构了Astrbot内部工具的注册机制,将其从与agent模块的强耦合中解耦出来,提高了模块化和可维护性。
  • 引入ToolProvider协议: 定义了新的ToolProvider协议,允许以可插拔的方式注入工具和系统提示附加内容,使得工具管理更加灵活。
  • 专用工具提供者: 创建了ComputerToolProvider来统一管理本地、沙盒、浏览器和Neo技能相关的工具,以及CronToolProvider来管理定时任务工具。
  • 提示词和工具定义模块化: 将散布在astr_main_agent_resources.py中的各种系统提示词和工具定义拆分到独立的模块中(如astrbot/core/tools/prompts.pyastrbot/core/tools/kb_query.pyastrbot/core/tools/send_message.py),并删除了原文件。
  • 仪表盘UI更新: 更新了仪表盘界面,以显示工具的来源(如内部、插件、MCP),并禁止用户手动启用或禁用内部工具,提升了用户体验和系统稳定性。
Changelog
  • astrbot/core/agent/mcp_client.py
    • MCPTool类添加了source属性,用于标识工具来源为'mcp'。
  • astrbot/core/agent/tool.py
    • FunctionTool类添加了source属性,用于指示工具的来源(如'plugin'、'internal'、'mcp'),并提供了相应的文档字符串。
  • astrbot/core/astr_agent_tool_exec.py
    • 更新了导入语句,移除了对旧astr_main_agent_resources模块的依赖,转而导入新的promptssend_message模块。
    • 重构了_get_runtime_computer_tools方法,现在通过ComputerToolProvider动态获取运行时计算机工具,取代了硬编码的工具列表。
    • _wake_main_agent_for_background_result函数中,为MainAgentBuildConfig添加了tool_providers配置,以注入ComputerToolProvider
    • 将系统提示词和用户提示词的字符串替换为从astrbot.core.tools.prompts模块导入的常量。
  • astrbot/core/astr_main_agent.py
    • 移除了platform模块的导入,并添加了sp模块的导入。
    • 更新了大量导入语句,将工具和提示词的定义从astr_main_agent_resources模块迁移到新的专用模块。
    • 删除了_apply_local_env_tools_build_local_mode_prompt_apply_sandbox_tools_proactive_cron_job_tools等旧的工具注入逻辑函数。
    • MainAgentBuildConfig中新增了tool_providers字段,用于接收解耦的工具提供者列表。
    • 更新了build_main_agent函数,实现了通过tool_providers动态注入工具和系统提示词附加内容的逻辑,并尊重WebUI的工具启用/禁用设置。
    • 将硬编码的提示词字符串替换为从astrbot.core.tools.prompts模块导入的常量。
  • astrbot/core/astr_main_agent_resources.py
    • 删除了该文件,其内容已拆分到新的专用模块中。
  • astrbot/core/computer/computer_tool_provider.py
    • 新增了ComputerToolProvider文件,实现了ToolProvider协议,负责提供所有计算机使用相关的工具(沙盒、本地、Neo技能、浏览器工具)及其系统提示词附加内容。
    • 定义了工具的懒加载单例,以优化性能。
    • 包含了沙盒模式和本地模式的系统提示词常量和逻辑。
  • astrbot/core/computer/tools/neo_skills.py
    • 更新了CreateSkillPayloadToolpayload参数类型定义,使其更精确地支持数组类型。
  • astrbot/core/cron/cron_tool_provider.py
    • 新增了CronToolProvider文件,实现了ToolProvider协议,负责提供定时任务管理工具。
  • astrbot/core/cron/manager.py
    • 更新了导入语句,移除了对旧astr_main_agent_resources模块的依赖,转而导入新的promptssend_message模块。
    • _woke_main_agent函数中,为MainAgentBuildConfig添加了tool_providers配置,以注入ComputerToolProvider
    • 将系统提示词和用户提示词的字符串替换为从astrbot.core.tools.prompts模块导入的常量。
  • astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py
    • initialize方法中,根据配置条件初始化ComputerToolProviderCronToolProvider
    • 将初始化后的工具提供者列表传递给MainAgentBuildConfigtool_providers参数。
  • astrbot/core/provider/func_tool_manager.py
    • 新增了_INTERNAL_TOOL_PROVIDERS列表,用于指定内部工具提供者模块的路径。
    • 新增了register_internal_tools方法,用于遍历这些提供者模块,加载并注册所有内部工具,并将其source属性设置为'internal'。
  • astrbot/core/star/context.py
    • __init__方法中,添加了调用self.provider_manager.llm_tools.register_internal_tools()的逻辑,以在核心上下文初始化时注册内置工具。
  • astrbot/core/tool_provider.py
    • 新增了ToolProvider文件,定义了ToolProviderContext类和ToolProvider协议(Protocol),为解耦的工具注入机制提供了接口规范。
  • astrbot/core/tools/cron_tools.py
    • 新增了get_all_tools函数,用于返回所有与定时任务相关的工具实例,以便统一注册。
  • astrbot/core/tools/kb_query.py
    • 新增了kb_query.py文件,将KnowledgeBaseQueryTool类和retrieve_knowledge_base函数从astr_main_agent_resources.py中提取出来。
    • 新增了get_all_tools函数,用于返回知识库查询工具实例。
  • astrbot/core/tools/prompts.py
    • 新增了prompts.py文件,集中管理了所有系统提示词常量,这些常量之前散布在astr_main_agent_resources.py中。
  • astrbot/core/tools/send_message.py
    • 新增了send_message.py文件,将SendMessageToUserTool类从astr_main_agent_resources.py中提取出来。
    • 新增了get_all_tools函数,用于返回发送消息工具实例。
  • astrbot/dashboard/routes/tools.py
    • 更新了get_tool_list方法,现在会从工具对象中获取source属性,并根据来源设置originorigin_name
    • 在返回的工具信息中添加了source字段。
    • toggle_tool方法中增加了逻辑,禁止用户手动启用或禁用source为'internal'的内置工具。
  • dashboard/src/components/extension/componentPanel/components/ToolTable.vue
    • 在Vue组件中添加了isInternal计算属性,用于判断工具是否为内部工具。
    • 更新了工具名称的显示样式,内部工具会显示一个锁图标并变为灰色。
    • 修改了工具状态的显示,内部工具会显示'内置'标签。
    • 修改了工具操作(开关)的显示,内部工具的开关被替换为横线,表示不可操作。
  • dashboard/src/components/extension/componentPanel/types.ts
    • ToolItem接口中添加了可选的source属性,用于表示工具的来源。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 3 个问题,并给出了一些高层次反馈:

  • source 的取值('plugin', 'internal', 'mcp')现在在前后端都会使用;建议把它们集中定义为共享的常量/枚举(并加上类型),以避免不同组件之间出现偏差或拼写错误。
  • ComputerToolProvider._sandbox_tools 中,当 Shipyard 配置不完整时,该函数只会记录错误日志并返回空的工具列表;你可能需要将其作为更明确的运行时错误抛出,或提供某种回退行为,以便更容易定位缺失的 computer-use 工具问题。
  • 对于 WebUI 中的 internal 工具,后端通过 getattr(tool, 'source', 'plugin')source 提供默认值,但前端把 source 视为可选字段;建议统一行为,始终返回非空的 source 值,使 UI 逻辑(包括 internal lock 的样式)在所有工具上表现一致。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `source` values ('plugin', 'internal', 'mcp') are now used across the backend and frontend; consider centralizing them as shared constants/enums (and typing them) to avoid drift or typos between components.
- In `ComputerToolProvider._sandbox_tools`, when Shipyard config is incomplete the function just logs an error and returns an empty tool list; you may want to surface this as a clearer runtime error or fall back behavior so the missing computer-use tools are easier to diagnose.
- For internal tools in the WebUI, the backend defaults `source` via `getattr(tool, 'source', 'plugin')` but the frontend treats `source` as optional; consider normalizing this to always return a non-empty `source` value so the UI logic (including the internal lock styling) behaves consistently for all tools.

## Individual Comments

### Comment 1
<location path="astrbot/core/tools/send_message.py" line_range="92" />
<code_context>
-                context.context.event.unified_msg_origin,
-            )
-            # Use shell to check if the file exists in sandbox
-            result = await sb.shell.exec(f"test -f {path} && echo '_&exists_'")
-            if "_&exists_" in json.dumps(result):
-                # Download the file from sandbox
</code_context>
<issue_to_address>
**🚨 issue (security):** Quote sandbox paths in shell command to avoid breakage and injection risk.

Passing `path` directly into `test -f {path}` will fail for paths with spaces or shell metacharacters and also introduces command injection risk. Please quote/escape the path (e.g. with `shlex.quote(path)`) or, preferably, avoid shell string construction altogether by using a non-shell API to test file existence. If you must use `sb.shell.exec`, ensure the path is safely quoted before execution.
</issue_to_address>

### Comment 2
<location path="astrbot/core/astr_main_agent.py" line_range="1037-1038" />
<code_context>
+            session_id=req.session_id or "",
+        )
+        # Respect WebUI tool enable/disable settings
+        _inactivated: set[str] = set(
+            sp.get("inactivated_llm_tools", [], scope="global", scope_id="global")
+        )
+        for _tp in config.tool_providers:
</code_context>
<issue_to_address>
**question (bug_risk):** Clarify how global inactivated tools interact with non-togglable internal tools.

Because internal tools are no longer user-togglable and `toggle_tool` rejects `source == "internal"`, any legacy entries for internal tools in `inactivated_llm_tools` will still disable them at runtime with no way for users to fix this via the UI. Please either (1) ignore `inactivated_llm_tools` for internal tools, or (2) migrate/clean internal tools out of that list so runtime behavior aligns with the new UX.
</issue_to_address>

### Comment 3
<location path="astrbot/core/tools/prompts.py" line_range="134-136" />
<code_context>
+    "{file_content}\nFile Name: {file_name}"
+)
+
+CONVERSATION_HISTORY_INJECT_PREFIX = (
+    "\n\nBellow is you and user previous conversation history:\n"
+)
+
</code_context>
<issue_to_address>
**nitpick (typo):** Fix typo in conversation history prefix prompt.

This string currently reads `"Bellow is you and user previous conversation history"`; please correct the typo and grammar (e.g., `"Below is your and the user's previous conversation history:"`) so prompts using `CONVERSATION_HISTORY_INJECT_PREFIX` don’t propagate the error.

```suggestion
CONVERSATION_HISTORY_INJECT_PREFIX = (
    "\n\nBelow is your and the user's previous conversation history:\n"
)
```
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得这些 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈来改进后续的 Review。
Original comment in English

Hey - I've found 3 issues, and left some high level feedback:

  • The source values ('plugin', 'internal', 'mcp') are now used across the backend and frontend; consider centralizing them as shared constants/enums (and typing them) to avoid drift or typos between components.
  • In ComputerToolProvider._sandbox_tools, when Shipyard config is incomplete the function just logs an error and returns an empty tool list; you may want to surface this as a clearer runtime error or fall back behavior so the missing computer-use tools are easier to diagnose.
  • For internal tools in the WebUI, the backend defaults source via getattr(tool, 'source', 'plugin') but the frontend treats source as optional; consider normalizing this to always return a non-empty source value so the UI logic (including the internal lock styling) behaves consistently for all tools.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `source` values ('plugin', 'internal', 'mcp') are now used across the backend and frontend; consider centralizing them as shared constants/enums (and typing them) to avoid drift or typos between components.
- In `ComputerToolProvider._sandbox_tools`, when Shipyard config is incomplete the function just logs an error and returns an empty tool list; you may want to surface this as a clearer runtime error or fall back behavior so the missing computer-use tools are easier to diagnose.
- For internal tools in the WebUI, the backend defaults `source` via `getattr(tool, 'source', 'plugin')` but the frontend treats `source` as optional; consider normalizing this to always return a non-empty `source` value so the UI logic (including the internal lock styling) behaves consistently for all tools.

## Individual Comments

### Comment 1
<location path="astrbot/core/tools/send_message.py" line_range="92" />
<code_context>
-                context.context.event.unified_msg_origin,
-            )
-            # Use shell to check if the file exists in sandbox
-            result = await sb.shell.exec(f"test -f {path} && echo '_&exists_'")
-            if "_&exists_" in json.dumps(result):
-                # Download the file from sandbox
</code_context>
<issue_to_address>
**🚨 issue (security):** Quote sandbox paths in shell command to avoid breakage and injection risk.

Passing `path` directly into `test -f {path}` will fail for paths with spaces or shell metacharacters and also introduces command injection risk. Please quote/escape the path (e.g. with `shlex.quote(path)`) or, preferably, avoid shell string construction altogether by using a non-shell API to test file existence. If you must use `sb.shell.exec`, ensure the path is safely quoted before execution.
</issue_to_address>

### Comment 2
<location path="astrbot/core/astr_main_agent.py" line_range="1037-1038" />
<code_context>
+            session_id=req.session_id or "",
+        )
+        # Respect WebUI tool enable/disable settings
+        _inactivated: set[str] = set(
+            sp.get("inactivated_llm_tools", [], scope="global", scope_id="global")
+        )
+        for _tp in config.tool_providers:
</code_context>
<issue_to_address>
**question (bug_risk):** Clarify how global inactivated tools interact with non-togglable internal tools.

Because internal tools are no longer user-togglable and `toggle_tool` rejects `source == "internal"`, any legacy entries for internal tools in `inactivated_llm_tools` will still disable them at runtime with no way for users to fix this via the UI. Please either (1) ignore `inactivated_llm_tools` for internal tools, or (2) migrate/clean internal tools out of that list so runtime behavior aligns with the new UX.
</issue_to_address>

### Comment 3
<location path="astrbot/core/tools/prompts.py" line_range="134-136" />
<code_context>
+    "{file_content}\nFile Name: {file_name}"
+)
+
+CONVERSATION_HISTORY_INJECT_PREFIX = (
+    "\n\nBellow is you and user previous conversation history:\n"
+)
+
</code_context>
<issue_to_address>
**nitpick (typo):** Fix typo in conversation history prefix prompt.

This string currently reads `"Bellow is you and user previous conversation history"`; please correct the typo and grammar (e.g., `"Below is your and the user's previous conversation history:"`) so prompts using `CONVERSATION_HISTORY_INJECT_PREFIX` don’t propagate the error.

```suggestion
CONVERSATION_HISTORY_INJECT_PREFIX = (
    "\n\nBelow is your and the user's previous conversation history:\n"
)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

context.context.event.unified_msg_origin,
)
# Use shell to check if the file exists in sandbox
result = await sb.shell.exec(f"test -f {path} && echo '_&exists_'")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): 在 shell 命令中为 sandbox 路径加上引号,以避免出错和命令注入风险。

path 直接拼入 test -f {path} 会在路径包含空格或 shell 元字符时失败,同时也带来命令注入风险。请为该路径添加引号/转义(例如使用 shlex.quote(path)),或者更好的做法是完全避免构造 shell 字符串,改用非 shell 的 API 来检测文件是否存在。如果必须使用 sb.shell.exec,请确保在执行前对路径进行安全引用。

Original comment in English

🚨 issue (security): Quote sandbox paths in shell command to avoid breakage and injection risk.

Passing path directly into test -f {path} will fail for paths with spaces or shell metacharacters and also introduces command injection risk. Please quote/escape the path (e.g. with shlex.quote(path)) or, preferably, avoid shell string construction altogether by using a non-shell API to test file existence. If you must use sb.shell.exec, ensure the path is safely quoted before execution.

Comment on lines +1037 to +1038
_inactivated: set[str] = set(
sp.get("inactivated_llm_tools", [], scope="global", scope_id="global")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): 需要澄清全局禁用工具与不可切换的 internal 工具之间是如何交互的。

由于 internal 工具已经不再允许用户切换,且 toggle_tool 会拒绝 source == "internal",任何遗留在 inactivated_llm_tools 中的 internal 工具条目仍会在运行时被禁用,而用户无法通过 UI 来修复这一点。请考虑:(1)对 internal 工具忽略 inactivated_llm_tools;或者(2)在该列表中迁移/清理掉 internal 工具条目,使运行时行为与新的 UX 保持一致。

Original comment in English

question (bug_risk): Clarify how global inactivated tools interact with non-togglable internal tools.

Because internal tools are no longer user-togglable and toggle_tool rejects source == "internal", any legacy entries for internal tools in inactivated_llm_tools will still disable them at runtime with no way for users to fix this via the UI. Please either (1) ignore inactivated_llm_tools for internal tools, or (2) migrate/clean internal tools out of that list so runtime behavior aligns with the new UX.

Comment on lines +134 to +136
CONVERSATION_HISTORY_INJECT_PREFIX = (
"\n\nBellow is you and user previous conversation history:\n"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): 修正会话历史前缀提示文案中的拼写错误。

该字符串当前为 "Bellow is you and user previous conversation history";请修正其中的拼写和语法错误(例如改为 "Below is your and the user's previous conversation history:"),以避免使用 CONVERSATION_HISTORY_INJECT_PREFIX 的提示继续传播这一问题。

Suggested change
CONVERSATION_HISTORY_INJECT_PREFIX = (
"\n\nBellow is you and user previous conversation history:\n"
)
CONVERSATION_HISTORY_INJECT_PREFIX = (
"\n\nBelow is your and the user's previous conversation history:\n"
)
Original comment in English

nitpick (typo): Fix typo in conversation history prefix prompt.

This string currently reads "Bellow is you and user previous conversation history"; please correct the typo and grammar (e.g., "Below is your and the user's previous conversation history:") so prompts using CONVERSATION_HISTORY_INJECT_PREFIX don’t propagate the error.

Suggested change
CONVERSATION_HISTORY_INJECT_PREFIX = (
"\n\nBellow is you and user previous conversation history:\n"
)
CONVERSATION_HISTORY_INJECT_PREFIX = (
"\n\nBelow is your and the user's previous conversation history:\n"
)

@dosubot dosubot bot added the area:core The bug / feature is about astrbot's core, backend label Mar 11, 2026
@dosubot
Copy link

dosubot bot commented Mar 11, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

AstrBotTeam's Space

pr4697的改动
View Suggested Changes
@@ -91,7 +91,7 @@
 
 **运行时工具解析**
 
-新增 `_get_runtime_computer_tools()` 方法,根据 `provider_settings.computer_use_runtime` 配置动态解析运行时工具。
+Computer Use 工具现在通过 `ComputerToolProvider` 统一提供(`astrbot/core/computer/computer_tool_provider.py`)。主代理在构建时调用 `provider.get_tools(ctx)` 方法,根据 `provider_settings.computer_use_runtime` 配置动态获取运行时工具。
 
 **Booter 选择优化(PR #6064)**
 
@@ -200,8 +200,8 @@
 
 **技术实现**
 
-- 实现读取 `provider_settings.computer_use_runtime` 配置,确定需要挂载的运行时工具类型
-- `HandoffExecutor.build_handoff_toolset()` 方法替代了原有 `_build_handoff_toolset()` 方法,在 `handoff_executor.py` 模块中实现
+- `ComputerToolProvider` 负责根据 `provider_settings.computer_use_runtime` 配置决定需要提供的运行时工具
+- `HandoffExecutor.build_handoff_toolset()` 方法在子代理 handoff 时通过 `ComputerToolProvider.get_tools(ctx)` 获取运行时工具
 - 工具解析优先级:已注册工具 → 运行时工具,确保灵活性和正确性
 
 **权限控制(PR #5402)**
@@ -369,7 +369,93 @@
 
 ### 4. 工具注册与配置加载
 
+#### 架构重构(PR #6071)
+
+[PR #6071](https://github.com/AstrBotDevs/AstrBot/pull/6071) 对内部工具注册机制进行了全面重构,引入 `ToolProvider` 协议实现工具注入与主代理的解耦,提升了系统的可扩展性和模块化程度。
+
+**新增 ToolProvider 协议**(`astrbot/core/tool_provider.py`)
+
+引入 `ToolProvider` 协议和 `ToolProviderContext` 上下文类,提供标准化的工具注入接口:
+
+- **`ToolProvider` 协议**:定义两个核心方法
+  - `get_tools(ctx: ToolProviderContext) -> list[FunctionTool]`:返回当前会话可用的工具列表
+  - `get_system_prompt_addon(ctx: ToolProviderContext) -> str`:返回需要附加到系统提示词的文本(可选)
+  
+- **`ToolProviderContext` 类**:封装会话级上下文信息
+  - `computer_use_runtime`:Computer Use 运行时类型(none、local、sandbox)
+  - `sandbox_cfg`:沙箱配置字典
+  - `session_id`:会话 ID
+
+该协议使工具提供者与主代理构建逻辑完全解耦,主代理无需知道具体的工具实现细节。
+
+**核心 ToolProvider 实现**
+
+系统现提供以下内置 ToolProvider:
+
+1. **`ComputerToolProvider`**(`astrbot/core/computer/computer_tool_provider.py`)
+   - 提供所有 Computer Use 工具(Shell、Python、文件传输、浏览器自动化、Neo 技能生命周期工具)
+   - `get_all_tools()` 方法:返回所有 Computer Use 工具的非活跃实例,用于 WebUI 显示和工具分配
+   - `get_tools(ctx)` 方法:根据 `computer_use_runtime` 配置过滤并返回当前运行时可用的工具
+   - `get_system_prompt_addon(ctx)` 方法:根据运行时类型返回对应的系统提示词(sandbox/local 模式提示)
+   - 沙箱能力感知:通过 `session_booter.get(session_id)` 查询沙箱能力,条件性地包含浏览器工具
+
+2. **`CronToolProvider`**(`astrbot/core/cron/cron_tool_provider.py`)
+   - 提供定时任务管理工具(CREATE_CRON_JOB_TOOL、DELETE_CRON_JOB_TOOL、LIST_CRON_JOBS_TOOL)
+   - 遵循相同的 ToolProvider 协议
+
+**系统提示词集中管理**(`astrbot/core/tools/prompts.py`)
+
+所有系统提示词已从 `astr_main_agent_resources.py` 迁移到专用模块,便于其他模块导入而无需引入工具类或重量级依赖:
+
+- `FILE_EXTRACT_CONTEXT_TEMPLATE`:文件提取上下文模板
+- `IMAGE_CAPTION_DEFAULT_PROMPT`:图片描述默认提示词
+- `COMPUTER_USE_DISABLED_PROMPT`:Computer Use 禁用提示
+- `WEBCHAT_TITLE_GENERATOR_SYSTEM_PROMPT` / `WEBCHAT_TITLE_GENERATOR_USER_PROMPT`:WebChat 标题生成提示词
+- `LIVE_MODE_SYSTEM_PROMPT`、`LLM_SAFETY_MODE_SYSTEM_PROMPT`、`TOOL_CALL_PROMPT` 等
+
+**内部工具注册(FunctionToolManager)**
+
+`FunctionToolManager` 新增 `register_internal_tools()` 方法,使用模块列表动态加载工具:
+
+```python
+_INTERNAL_TOOL_PROVIDERS = [
+    "astrbot.core.tools.cron_tools",
+    "astrbot.core.tools.kb_query",
+    "astrbot.core.tools.send_message",
+    "astrbot.core.computer.computer_tool_provider",
+]
+```
+
+每个模块暴露 `get_all_tools()` 函数,返回该模块的所有工具。工具自动标记 `source='internal'`,便于 WebUI 区分内部工具、插件工具和 MCP 工具。
+
+**主代理构建逻辑调整(build_main_agent)**
+
+`build_main_agent` 函数的工具注入逻辑已重构:
+
+1. **移除旧的工具注入函数**:`_apply_sandbox_tools()`、`_apply_local_env_tools()`、`_proactive_cron_job_tools()` 等函数已删除
+2. **新增 `tool_providers` 参数**:`MainAgentBuildConfig` 新增 `tool_providers: list[ToolProvider]` 参数
+3. **统一工具注入流程**:
+   - 构建 `ToolProviderContext`,包含 `computer_use_runtime`、`sandbox_cfg`、`session_id`
+   - 遍历所有 `tool_providers`,调用 `provider.get_tools(ctx)` 获取工具并添加到 `req.func_tool`
+   - 调用 `provider.get_system_prompt_addon(ctx)` 获取系统提示词附加内容
+   - 自动尊重 WebUI 工具启用/禁用设置(`inactivated_llm_tools`)
+
+**工具模块拆分**
+
+原 `astr_main_agent_resources.py` 文件已删除,工具类迁移到以下模块:
+
+- `astrbot/core/computer/computer_tool_provider.py`:Computer Use 工具(Shell、Python、文件传输等),带懒加载缓存
+- `astrbot/core/tools/cron_tools.py`:定时任务管理工具
+- `astrbot/core/tools/kb_query.py`:知识库查询工具
+- `astrbot/core/tools/send_message.py`:主动消息发送工具
+- `astrbot/core/tools/prompts.py`:系统提示词常量
+
+**WebUI 工具管理集成**
+
+WebUI 工具管理现在调用 `ComputerToolProvider.get_all_tools()` 获取所有可用的 Computer Use 工具(返回非活跃、仅用于注册的实例),用于在界面中显示所有工具选项。运行时工具注入通过 `get_tools(ctx)` 在实际 LLM 请求时独立处理。
+
 #### 逻辑改进
+
 工具注册和配置加载逻辑已优化,确保子代理配置的正确性和工具的动态注册。FunctionTool 新增 `is_background_task` 属性,支持异步后台任务。
 
 #### MCP 客户端初始化(PR #5993)
@@ -526,7 +612,7 @@
 - 供应商类型选择:现在支持在“选择供应商”下拉菜单中选择 `chat_completion` 和 `agent_runner` 类型,便于根据不同需求配置子代理的执行方式。
 - 页面副标题已更新为:“主 LLM 可直接使用自身工具,也可通过 handoff 分派给各个 SubAgent。”
 
-主代理和子代理的工具分配逻辑在 UI 中有清晰展示,支持灵活配置。
+主代理和子代理的工具分配逻辑在 UI 中有清晰展示,支持灵活配置。WebUI 工具管理通过调用 `ComputerToolProvider.get_all_tools()` 获取所有可用的 Computer Use 工具(返回非活跃、仅用于注册的实例),用于在界面中显示所有可配置的工具选项。运行时工具注入通过 `provider.get_tools(ctx)` 在实际 LLM 请求时独立处理。
 
 #### 定时任务管理
 新增 Cron Job 管理页面(CronJobPage.vue),支持:
@@ -679,12 +765,13 @@
 #### 主要改动
 
 ##### 调整 build_main_agent 逻辑顺序
-修复的核心是调整内置工具的加载时机,确保在去重装饰逻辑(`_decorate_llm_request`)执行前已加载所有内置工具。调整后的执行顺序为:
-
-1. **内置工具加载**(提前至去重前):
-   - Computer Use 工具(sandbox/local 运行时)
-   - Cron 工具(定时任务工具)
-   - send_message_to_user 工具(主动消息工具)
+
+修复的核心是调整内置工具的加载时机,通过 ToolProvider 机制实现工具注入与去重逻辑的正确配合。调整后的执行顺序为:
+
+1. **ToolProvider 工具注入**:
+   - 通过 `tool_providers` 列表注入所有内部工具(Computer Use、Cron、send_message_to_user 等)
+   - 工具通过 `ComputerToolProvider.get_tools(ctx)` 和 `CronToolProvider.get_tools(ctx)` 动态加载
+   - 自动尊重 WebUI 工具启用/禁用设置
 
 2. **知识库应用**(`_apply_kb`)
 
@@ -692,7 +779,7 @@
    - 此时所有内置工具已完整加载
    - 子智能体去重逻辑可以正确识别并移除主智能体中与子智能体重复的内置工具
 
-修复前,内置工具在去重逻辑之后添加,导致去重功能无法识别这些工具,造成重复加载。
+修复前,内置工具在去重逻辑之后添加,导致去重功能无法识别这些工具,造成重复加载。PR #6071 的 ToolProvider 重构将工具注入逻辑前置,彻底解决了该问题。
 
 ##### 修复人格工具查找逻辑
 改进了 `_ensure_persona_and_skills()` 方法中的人格工具查找逻辑:
@@ -717,24 +804,32 @@
 
 #### 技术细节
 
-**内置工具加载时机调整:**
-- Computer Use 工具(`_apply_sandbox_tools` / `_apply_local_env_tools`)
-- Cron 工具(`_proactive_cron_job_tools`)
-- send_message_to_user 工具(`SEND_MESSAGE_TO_USER_TOOL`)
-
-这些工具现在在 `await _decorate_llm_request()` 之前添加,确保子智能体的去重逻辑能够正确处理这些工具。
+**工具注入时机调整:**
+- Computer Use 工具、Cron 工具、send_message_to_user 工具现在通过 `ToolProvider` 机制统一注入
+- `ComputerToolProvider` 和 `CronToolProvider` 在 `build_main_agent` 中通过 `tool_providers` 参数传递
+- 工具注入在 `await _decorate_llm_request()` 之前完成,确保子智能体的去重逻辑能够正确处理这些工具
 
 **知识库应用调用位置:**
 知识库应用(`_apply_kb`)的调用位置保持在去重逻辑之前,确保知识库工具也能被正确去重。
 
 #### 影响范围
-此修复确保子智能体去重功能(`remove_main_duplicate_tools`)正常工作,解决了以下问题:
+
+PR #5459 和 PR #6071 的 ToolProvider 重构共同确保子智能体去重功能(`remove_main_duplicate_tools`)正常工作,解决了以下问题:
 
 - 内置工具重复加载问题
 - 人格配置兼容性问题(`tools=None` 语义)
 - 插件过滤时误删内置工具和 MCP 工具的问题
 
 修复后,当启用 `remove_main_duplicate_tools: true` 时,主智能体会正确移除与子智能体重复的所有工具(包括内置工具),仅保留独有工具和 handoff 工具。
+
+**可扩展性提升**
+
+ToolProvider 机制显著简化了新工具类别的添加流程:
+- 新增内部工具类别(如未来的网络工具、多媒体处理工具等)只需:
+  1. 实现 `ToolProvider` 协议
+  2. 在 `FunctionToolManager._INTERNAL_TOOL_PROVIDERS` 列表中注册模块路径
+- 主代理无需导入具体的工具类,仅依赖 `ToolProvider` 接口
+- 工具的懒加载和缓存模式在各 Provider 内部封装,不影响主代理逻辑
 
 ---
 

[Accept] [Decline]

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

@gemini-code-assist
Copy link
Contributor

Warning

Gemini is experiencing higher than usual traffic and was unable to create the review. Please try again in a few hours by commenting /gemini review.

w31r4 added 10 commits March 12, 2026 02:43
- Add get_default_tools/get_tools/get_system_prompt_parts to ComputerBooter base
- Each booter subclass (ShipyardNeo, Shipyard, Boxlite) declares its own tools
- ComputerToolProvider now delegates to booter API via computer_client helpers
- Add unified query API: get_sandbox_tools, get_default_sandbox_tools, etc.
- Extract Neo prompts to dedicated computer/prompts.py module
- Add booter type constants (booters/constants.py)
- Fix subagent tool path to pass sandbox_cfg and session_id
- Fix Sourcery issues: shell injection in send_message, typo in prompts,
  internal tools bypass inactivated_llm_tools check
Two changes to make the tool schema sent to the LLM deterministic:
1. ToolSet.normalize() — sort tools by name before serialization.
   Called at the end of build_main_agent() after all injection passes.
   Eliminates ordering drift from plugin load order, MCP reconnection,
   and persona tool list differences.
2. Always inject full sandbox tool set — ComputerToolProvider now
   returns get_default_sandbox_tools() unconditionally, regardless of
   sandbox boot state. Browser tools are always in the schema even if
   the sandbox profile lacks browser capability. The executor rejects
   calls to unavailable browser tools with a descriptive error instead
   of silently omitting them from the schema.
   This eliminates the pre-boot/post-boot tool set jump that caused
   prefix cache misses on the second request of a conversation.
- Rewrite TestApplySandboxToolsRefactored to test ComputerToolProvider
  directly (_apply_sandbox_tools was removed; old tests permanently skipped)
- Add TestExecutorCapabilityGuard with 5 strict tests for browser
  capability rejection at executor level
- Fix typo: "on hehalf you" -> "on behalf of you" in subagent result
- Remove extra blank lines (ruff E303) after _apply_sandbox_tools comment
- Remove dead _build_sync_and_scan_command (no callers after refactor)
- shipyard_neo: browser property now returns None when not initialized
  instead of raising RuntimeError, matching ComputerBooter base contract
- computer_tool_provider: remove dead os.environ writes for shipyard
  (SHIPYARD_ENDPOINT / SHIPYARD_ACCESS_TOKEN are never read anywhere)
  and remove unused os import
@w31r4
Copy link
Contributor

w31r4 commented Mar 12, 2026

f16edd4 — Booter 自描述 API:各 booter 子类声明自身工具集,ComputerToolProvider
委托调用,解除与 agent 的硬耦合
e85eef0 — 工具注入稳定性:ToolSet.normalize() 排序 + 沙箱全量注入,减少 prefix cache miss 情况
3440dcd — 测试:booter 解耦 & profile-aware 工具测试
ad3911a — 日志:sandbox tool resolution debug 日志
e1d7611 — 日志:各 booter 统一结构化日志格式
a5a1ba7 — 日志:computer_client 结构化日志 + get_sandbox_capabilities API
7c3cc7b — 日志:工具绑定时记录 capabilities
dfc0c34 — 修复:重写死测试(TestApplySandboxTools)、typo、多余空行、死函数
048c511 — 修复:browser property 对齐基类契约返回 None;移除 os.environ 死写
855483c — style:ruff I001/F401 修复

- .gitignore: keep both .serena and .worktrees/ entries
- astr_main_agent_resources.py: keep deletion (refactored to tools/)
- send_message.py: port video message type support from master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants