Skip to content

feat: enhance persona tool management and update UI localization for subagent orchestration#4990

Merged
Soulter merged 2 commits intomasterfrom
fix/4971
Feb 9, 2026
Merged

feat: enhance persona tool management and update UI localization for subagent orchestration#4990
Soulter merged 2 commits intomasterfrom
fix/4971

Conversation

@Soulter
Copy link
Member

@Soulter Soulter commented Feb 9, 2026

fix: #4971

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.

Summary by Sourcery

优化人物(persona)工具分配与子代理(subagent)编排行为,并让仪表盘 UI 和本地化与更新后的子代理流程保持一致。

改进点:

  • 调整人物工具集构建逻辑,始终尊重当前激活的工具,与现有请求工具正确合并,并在子代理交接工具存在时仍能保留主代理工具而不丢失。
  • 修改子代理编排中的重复工具处理方式,使主代理保留自身工具,仅在配置指定的情况下选择性移除重叠工具。
  • 增加对最终 ProviderRequest 的详细调试日志记录,以便排查提示词、上下文及工具选择行为相关问题。
  • 明确子代理编排器配置注释,更好地描述主代理工具保留策略和重复项移除语义。
  • 整理子代理管理页面的 UI,移除已废弃的预览 chip 区域,并同步更新中英文相关本地化条目。
Original summary in English

Summary by Sourcery

Refine persona tool assignment and subagent orchestration behavior while aligning dashboard UI and localization with the updated subagent flow.

Enhancements:

  • Adjust persona toolset construction to always respect active tools, merge correctly with existing request tools, and coexist with subagent handoff tools without losing main tools.
  • Change duplicate-tool handling in subagent orchestration so the main agent retains its own tools while selectively removing overlaps only where configured.
  • Add detailed debug logging of the final ProviderRequest to aid in troubleshooting prompt, context, and tool selection behavior.
  • Clarify configuration comments for subagent orchestrator settings to better describe main agent tool retention and duplicate removal semantics.
  • Tidy the SubAgent management page UI by removing the obsolete preview chip section and synchronizing related localization entries in both English and Chinese.

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Feb 9, 2026
@dosubot
Copy link

dosubot bot commented Feb 9, 2026

Documentation Updates

1 document(s) were updated by changes in this PR:

pr4697的改动
View Changes
@@ -8,7 +8,7 @@
 #### 架构说明
 主动代理系统引入了 SubAgent(子代理)架构。主代理(MainAgent)不仅专注于对话和任务委派,还可以直接使用自身工具集。具体工具和任务也可以由子代理(SubAgent)处理。这样可以有效避免 prompt 膨胀和调用失败,同时提升主代理的灵活性。
 
-- 工具分配:每个 SubAgent 继承自 Persona,拥有独立的工具集、技能、名称和描述。主代理可以直接调用自身工具,也可以通过 `transfer_to_<subagent>` 工具将任务转交给指定子代理。
+- 工具分配:每个 SubAgent 继承自 Persona,拥有独立的工具集、技能、名称和描述。主代理可以直接调用自身工具,也可以通过 `transfer_to_<subagent>` 工具将任务转交给指定子代理。启用 SubAgent 编排后,主代理会保留自身工具,并自动挂载 handoff 工具(transfer_to_*),可根据配置决定是否移除与子代理重复的工具。
 - 任务流转:主代理既可以直接处理任务,也可以分发任务给子代理。子代理之间可通过 transfer 工具实现任务转移。主代理和子代理的工具集可以根据配置灵活分配,支持去重和 handoff 工具自动挂载。
 
 #### 使用示例
@@ -24,7 +24,15 @@
 这样,主代理既可以自主处理,也可以灵活委派任务。
 
 #### 配置说明
-SubAgent 的定义与 Persona 配置一致,需在配置文件中指定 tools、skills、name、description 等。主代理在未启用 SubAgent 编排时,可直接挂载自身工具集(按 persona 规则,默认全部),并直接调用工具。启用 SubAgent 编排后,主代理仅挂载 transfer_to_* 委派工具,不再直接挂载 persona/全局工具;由 SubAgent 负责工具调用与结果整理。工具分配可通过 UI 或配置文件完成。
+SubAgent 的定义与 Persona 配置一致,需在配置文件中指定 tools、skills、name、description 等。
+
+主代理在未启用 SubAgent 编排时,可直接挂载自身工具集(按 persona 规则,默认全部),并直接调用工具。
+
+启用 SubAgent 编排后,主代理会保留自身工具,并自动挂载 transfer_to_* 委派工具。是否移除与子代理重复的工具由 `remove_main_duplicate_tools` 配置项控制:
+- `remove_main_duplicate_tools: false`(默认):主代理保留自身工具,即使与子代理重复。
+- `remove_main_duplicate_tools: true`:主代理会移除与子代理重复的工具,仅保留独有工具和 handoff 工具。
+
+工具分配可通过 UI 或配置文件完成。相关配置项说明已在配置文件注释中明确。
 
 ### 2. FutureTask(定时任务)机制
 
@@ -96,7 +104,7 @@
 - Persona 选择与工具分配
 - 配置持久化
 - 供应商类型选择:现在支持在“选择供应商”下拉菜单中选择 `chat_completion` 和 `agent_runner` 类型,便于根据不同需求配置子代理的执行方式。
-- 页面副标题已更新为:“主 LLM 负责聊天与委派(transfer_to_*),工具挂载在各个 SubAgent 上。”
+- 页面副标题已更新为:“主 LLM 可直接使用自身工具,也可通过 handoff 分派给各个 SubAgent。”
 
 主代理和子代理的工具分配逻辑在 UI 中有清晰展示,支持灵活配置。
 

How did I do? Any feedback?  Join Discord

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 - 我发现了 1 个问题,并留下了一些总体反馈:

  • 子代理 orchestrator 代码块里的去重逻辑看起来被反转了:现在在 remove_dup 为 true 时,不再是从主工具集里移除交接工具(handoff tools),而是从 req.func_tool 中移除任何不在 handoff_names 中的 assigned_tools,这很可能会把非交接工具也无意中删掉;建议恢复为基于主工具集名称来检查/移除。
  • build_main_agent 中新增的调试日志会打印完整的 promptsystem_promptcontexts 以及工具名称,这可能过于冗长且潜在包含敏感信息;建议对这些字段做截断,或者把这条日志放到更严格的配置/详细等级标志后面。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The duplicate-removal logic in the subagent orchestrator block looks inverted: instead of removing handoff tools from the main toolset when `remove_dup` is true, it now removes any `assigned_tools` not in `handoff_names` from `req.func_tool`, which is likely to strip non-handoff tools unintentionally; consider reverting to checking/removing against the main toolset’s names.
- The new debug log in `build_main_agent` prints full `prompt`, `system_prompt`, `contexts`, and tool names, which may be verbose and potentially sensitive; consider truncating these fields or gating this log behind a more restrictive config/verbosity flag.

## Individual Comments

### Comment 1
<location> `astrbot/core/astr_main_agent.py:961-966` </location>
<code_context>
     if action_type == "live":
         req.system_prompt += f"\n{LIVE_MODE_SYSTEM_PROMPT}\n"

+    logger.debug("Final ProviderRequest: prompt=%s, system_prompt=%s, image_urls=%s, contexts=%s, func_tool=%s",
+        req.prompt,
+        req.system_prompt,
+        req.image_urls,
+        req.contexts,
+        req.func_tool.names() if req.func_tool else None,
+    )
+
</code_context>

<issue_to_address>
**🚨 suggestion (security):** 最终的调试日志可能过于冗长,并可能暴露大量或敏感内容。

在一条调试日志中包含完整的 `prompt``system_prompt``contexts`,可能会泄露用户数据,并生成非常长的日志行,尤其是在生产或高并发环境中。请考虑对这些字段进行截断、根据配置做脱敏处理,或者把这条日志隐藏在更细粒度的调试标志之后,以便在需要时可以关闭完整内容的日志。

建议实现方式:

```python
    if action_type == "live":
        req.system_prompt += f"\n{LIVE_MODE_SYSTEM_PROMPT}\n"

    def _preview_for_logging(value, max_len: int = 512):
        """Return a truncated string representation for safe logging."""
        if value is None:
            return None
        text = str(value)
        if len(text) <= max_len:
            return text
        return text[:max_len] + f"... [truncated {len(text) - max_len} chars]"

    logger.debug(
        "Final ProviderRequest: prompt_preview=%s, system_prompt_preview=%s, image_urls=%s, contexts_preview=%s, func_tool=%s",
        _preview_for_logging(req.prompt),
        _preview_for_logging(req.system_prompt),
        req.image_urls,
        _preview_for_logging(req.contexts),
        req.func_tool.names() if req.func_tool else None,
    )

    # inject toolset in the persona
    if (persona and persona.get("tools") is None) or not persona:

```

如果你希望对敏感内容的日志有更严格的控制,可以考虑:
1. 引入一个配置开关(例如 `settings.LOG_FULL_LLM_CONTENT`),并:
   - 当该开关为 `False` 时仅使用 `_preview_for_logging`(如上)。
   - 只在非生产环境中为调试目的显式开启时,才记录完整内容。
2. 如果其他地方也需要类似的截断逻辑,可以将 `_preview_for_logging` 移动到一个共享的 logging/util 模块中。
</issue_to_address>

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

Hey - I've found 1 issue, and left some high level feedback:

  • The duplicate-removal logic in the subagent orchestrator block looks inverted: instead of removing handoff tools from the main toolset when remove_dup is true, it now removes any assigned_tools not in handoff_names from req.func_tool, which is likely to strip non-handoff tools unintentionally; consider reverting to checking/removing against the main toolset’s names.
  • The new debug log in build_main_agent prints full prompt, system_prompt, contexts, and tool names, which may be verbose and potentially sensitive; consider truncating these fields or gating this log behind a more restrictive config/verbosity flag.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The duplicate-removal logic in the subagent orchestrator block looks inverted: instead of removing handoff tools from the main toolset when `remove_dup` is true, it now removes any `assigned_tools` not in `handoff_names` from `req.func_tool`, which is likely to strip non-handoff tools unintentionally; consider reverting to checking/removing against the main toolset’s names.
- The new debug log in `build_main_agent` prints full `prompt`, `system_prompt`, `contexts`, and tool names, which may be verbose and potentially sensitive; consider truncating these fields or gating this log behind a more restrictive config/verbosity flag.

## Individual Comments

### Comment 1
<location> `astrbot/core/astr_main_agent.py:961-966` </location>
<code_context>
     if action_type == "live":
         req.system_prompt += f"\n{LIVE_MODE_SYSTEM_PROMPT}\n"

+    logger.debug("Final ProviderRequest: prompt=%s, system_prompt=%s, image_urls=%s, contexts=%s, func_tool=%s",
+        req.prompt,
+        req.system_prompt,
+        req.image_urls,
+        req.contexts,
+        req.func_tool.names() if req.func_tool else None,
+    )
+
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Final debug log may be overly verbose and could expose large or sensitive content.

Including full `prompt`, `system_prompt`, and `contexts` in a single debug log can leak user data and produce very large log lines, especially in production or high-traffic environments. Please consider truncating these fields, redacting them based on configuration, or guarding this behind a more targeted debug flag so full content logging can be disabled where needed.

Suggested implementation:

```python
    if action_type == "live":
        req.system_prompt += f"\n{LIVE_MODE_SYSTEM_PROMPT}\n"

    def _preview_for_logging(value, max_len: int = 512):
        """Return a truncated string representation for safe logging."""
        if value is None:
            return None
        text = str(value)
        if len(text) <= max_len:
            return text
        return text[:max_len] + f"... [truncated {len(text) - max_len} chars]"

    logger.debug(
        "Final ProviderRequest: prompt_preview=%s, system_prompt_preview=%s, image_urls=%s, contexts_preview=%s, func_tool=%s",
        _preview_for_logging(req.prompt),
        _preview_for_logging(req.system_prompt),
        req.image_urls,
        _preview_for_logging(req.contexts),
        req.func_tool.names() if req.func_tool else None,
    )

    # inject toolset in the persona
    if (persona and persona.get("tools") is None) or not persona:

```

If you want even stricter control over logging sensitive content, consider:
1. Introducing a configuration flag (e.g. `settings.LOG_FULL_LLM_CONTENT`) and:
   - Only use `_preview_for_logging` (as above) when the flag is `False`.
   - Log full content only when explicitly enabled for debugging in non-production environments.
2. Moving `_preview_for_logging` to a shared logging/util module if similar truncation is needed elsewhere.
</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.

Comment on lines 961 to 966
logger.debug("Final ProviderRequest: prompt=%s, system_prompt=%s, image_urls=%s, contexts=%s, func_tool=%s",
req.prompt,
req.system_prompt,
req.image_urls,
req.contexts,
req.func_tool.names() if req.func_tool else None,
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 suggestion (security): 最终的调试日志可能过于冗长,并可能暴露大量或敏感内容。

在一条调试日志中包含完整的 promptsystem_promptcontexts,可能会泄露用户数据,并生成非常长的日志行,尤其是在生产或高并发环境中。请考虑对这些字段进行截断、根据配置做脱敏处理,或者把这条日志隐藏在更细粒度的调试标志之后,以便在需要时可以关闭完整内容的日志。

建议实现方式:

    if action_type == "live":
        req.system_prompt += f"\n{LIVE_MODE_SYSTEM_PROMPT}\n"

    def _preview_for_logging(value, max_len: int = 512):
        """Return a truncated string representation for safe logging."""
        if value is None:
            return None
        text = str(value)
        if len(text) <= max_len:
            return text
        return text[:max_len] + f"... [truncated {len(text) - max_len} chars]"

    logger.debug(
        "Final ProviderRequest: prompt_preview=%s, system_prompt_preview=%s, image_urls=%s, contexts_preview=%s, func_tool=%s",
        _preview_for_logging(req.prompt),
        _preview_for_logging(req.system_prompt),
        req.image_urls,
        _preview_for_logging(req.contexts),
        req.func_tool.names() if req.func_tool else None,
    )

    # inject toolset in the persona
    if (persona and persona.get("tools") is None) or not persona:

如果你希望对敏感内容的日志有更严格的控制,可以考虑:

  1. 引入一个配置开关(例如 settings.LOG_FULL_LLM_CONTENT),并:
    • 当该开关为 False 时仅使用 _preview_for_logging(如上)。
    • 只在非生产环境中为调试目的显式开启时,才记录完整内容。
  2. 如果其他地方也需要类似的截断逻辑,可以将 _preview_for_logging 移动到一个共享的 logging/util 模块中。
Original comment in English

🚨 suggestion (security): Final debug log may be overly verbose and could expose large or sensitive content.

Including full prompt, system_prompt, and contexts in a single debug log can leak user data and produce very large log lines, especially in production or high-traffic environments. Please consider truncating these fields, redacting them based on configuration, or guarding this behind a more targeted debug flag so full content logging can be disabled where needed.

Suggested implementation:

    if action_type == "live":
        req.system_prompt += f"\n{LIVE_MODE_SYSTEM_PROMPT}\n"

    def _preview_for_logging(value, max_len: int = 512):
        """Return a truncated string representation for safe logging."""
        if value is None:
            return None
        text = str(value)
        if len(text) <= max_len:
            return text
        return text[:max_len] + f"... [truncated {len(text) - max_len} chars]"

    logger.debug(
        "Final ProviderRequest: prompt_preview=%s, system_prompt_preview=%s, image_urls=%s, contexts_preview=%s, func_tool=%s",
        _preview_for_logging(req.prompt),
        _preview_for_logging(req.system_prompt),
        req.image_urls,
        _preview_for_logging(req.contexts),
        req.func_tool.names() if req.func_tool else None,
    )

    # inject toolset in the persona
    if (persona and persona.get("tools") is None) or not persona:

If you want even stricter control over logging sensitive content, consider:

  1. Introducing a configuration flag (e.g. settings.LOG_FULL_LLM_CONTENT) and:
    • Only use _preview_for_logging (as above) when the flag is False.
    • Log full content only when explicitly enabled for debugging in non-production environments.
  2. Moving _preview_for_logging to a shared logging/util module if similar truncation is needed elsewhere.

@dosubot dosubot bot added area:webui The bug / feature is about webui(dashboard) of astrbot. feature:persona The bug / feature is about astrbot AI persona system (system prompt) labels Feb 9, 2026
@letr007 letr007 mentioned this pull request Feb 9, 2026
5 tasks
@Soulter Soulter merged commit da4bb65 into master Feb 9, 2026
6 checks passed
@Soulter Soulter deleted the fix/4971 branch February 9, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. feature:persona The bug / feature is about astrbot AI persona system (system prompt) size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] SubAgent 编排启用时主 LLM 可用工具与说明描述不符

1 participant