diff --git a/astrbot/core/agent/handoff.py b/astrbot/core/agent/handoff.py index 511fb5399..0e2d93435 100644 --- a/astrbot/core/agent/handoff.py +++ b/astrbot/core/agent/handoff.py @@ -31,6 +31,10 @@ class HandoffTool(FunctionTool, Generic[TContext]): **kwargs, ) + # Optional provider override for this subagent. When set, the handoff + # execution will use this chat provider id instead of the global/default. + self.provider_id: str | None = None + def default_parameters(self) -> dict: return { "type": "object", diff --git a/astrbot/core/astr_agent_tool_exec.py b/astrbot/core/astr_agent_tool_exec.py index 5d40f48fa..83c3512c2 100644 --- a/astrbot/core/astr_agent_tool_exec.py +++ b/astrbot/core/astr_agent_tool_exec.py @@ -74,7 +74,10 @@ class FunctionToolExecutor(BaseFunctionToolExecutor[AstrAgentContext]): ctx = run_context.context.context event = run_context.context.event umo = event.unified_msg_origin - prov_id = await ctx.get_current_chat_provider_id(umo) + + # Use per-subagent provider override if configured; otherwise fall back + # to the current/default provider resolution. + prov_id = getattr(tool, "provider_id", None) or await ctx.get_current_chat_provider_id(umo) llm_resp = await ctx.tool_loop_agent( event=event, chat_provider_id=prov_id, diff --git a/astrbot/core/subagent_orchestrator.py b/astrbot/core/subagent_orchestrator.py index d565aa7c5..38f384145 100644 --- a/astrbot/core/subagent_orchestrator.py +++ b/astrbot/core/subagent_orchestrator.py @@ -68,6 +68,9 @@ class SubAgentOrchestrator: instructions = str(item.get("system_prompt", "")).strip() public_description = str(item.get("public_description", "")).strip() + provider_id = item.get("provider_id") + if provider_id is not None: + provider_id = str(provider_id).strip() or None tools = item.get("tools", []) if not isinstance(tools, list): tools = [] @@ -82,6 +85,9 @@ class SubAgentOrchestrator: # while the subagent system prompt can be longer/more specific. handoff = HandoffTool(agent=agent, description=public_description or None) + # Optional per-subagent chat provider override. + handoff.provider_id = provider_id + # Mark as dynamic so we can replace/remove later. handoff.handler_module_path = "core.subagent_orchestrator" diff --git a/astrbot/dashboard/routes/subagent.py b/astrbot/dashboard/routes/subagent.py index 24cb2fcef..34b31c9e8 100644 --- a/astrbot/dashboard/routes/subagent.py +++ b/astrbot/dashboard/routes/subagent.py @@ -48,6 +48,13 @@ class SubAgentRoute(Route): data.setdefault("main_enable", False) data.setdefault("main_tools_policy", "handoff_only") data.setdefault("agents", []) + + # Backward/forward compatibility: ensure each agent contains provider_id. + # None means follow global/default provider settings. + if isinstance(data.get("agents"), list): + for a in data["agents"]: + if isinstance(a, dict): + a.setdefault("provider_id", None) return jsonify(Response().ok(data=data).__dict__) except Exception as e: logger.error(traceback.format_exc()) diff --git a/dashboard/src/views/SubAgentPage.vue b/dashboard/src/views/SubAgentPage.vue index 359828c57..632194885 100644 --- a/dashboard/src/views/SubAgentPage.vue +++ b/dashboard/src/views/SubAgentPage.vue @@ -29,18 +29,14 @@
- 启用后:主 LLM 只会看到 transfer_to_*,不会直接注入/调用其他工具;所有工具调用交给 SubAgent 完成。 - 关闭后:恢复原有行为(按 persona 选择并直接注入工具)。 +
+ 启用:主 LLM 仅负责对话与“转交”,只会看到 transfer_to_* 这类委派工具;需要调用工具时,会把任务交给对应 SubAgent 执行。SubAgent 负责真正的工具调用与结果整理,并把结论回传给主 LLM。 +
+
+ 关闭:恢复原有行为(按 persona 选择并直接注入工具)。 +
- - Router Prompt 当前使用系统内置默认值,暂不支持在 WebUI 中自定义。 - -
SubAgents
-
-
+
+
{{ agent.enabled ? '启用' : '停用' }} -
- {{ agent.name || '未命名 SubAgent' }} + +
+
{{ agent.name || '未命名 SubAgent' }}
+
transfer_to_{{ agent.name || '...' }}
-
+
+ + + + - - + + - - + - + import { onMounted, ref } from 'vue' import axios from 'axios' +import ProviderSelector from '@/components/shared/ProviderSelector.vue' type ToolOption = { title: string; value: string } @@ -193,6 +205,7 @@ type SubAgentItem = { system_prompt: string tools: string[] enabled: boolean + provider_id?: string } type SubAgentConfig = { @@ -240,6 +253,7 @@ function normalizeConfig(raw: any): SubAgentConfig { const system_prompt = (a?.system_prompt ?? '').toString() const tools = Array.isArray(a?.tools) ? a.tools.map((x: any) => String(x)) : [] const enabled = a?.enabled !== false + const provider_id = (a?.provider_id ?? undefined) as (string | undefined) return { __key: `${Date.now()}_${i}_${Math.random().toString(16).slice(2)}`, @@ -248,6 +262,8 @@ function normalizeConfig(raw: any): SubAgentConfig { system_prompt, tools, enabled + , + provider_id } }) @@ -316,7 +332,8 @@ function addAgent() { public_description: '', system_prompt: '', tools: [], - enabled: true + enabled: true, + provider_id: undefined }) } @@ -337,7 +354,8 @@ async function save() { public_description: a.public_description, system_prompt: a.system_prompt, tools: a.tools, - enabled: a.enabled + enabled: a.enabled, + provider_id: a.provider_id })) } @@ -369,6 +387,77 @@ onMounted(() => { padding-top: 8px; padding-bottom: 40px; } + +.subagent-panel-title { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.subagent-title-left { + min-width: 0; + display: flex; + align-items: center; + gap: 10px; +} + +.subagent-title-text { + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.subagent-title-name { + font-weight: 600; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 520px; +} + +.subagent-title-sub { + font-size: 12px; + opacity: 0.72; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 520px; +} + +.subagent-title-right { + display: flex; + align-items: center; + gap: 8px; +} + +.subagent-actions { + display: flex; + align-items: flex-start; + gap: 14px; +} + +.subagent-provider { + flex: 1; + min-width: 260px; +} + +.subagent-enabled-inline { + margin-right: 2px; +} + +/* Keep the switch compact inside the expansion-panel title row. */ +.subagent-enabled-inline :deep(.v-input__details) { + display: none; +} + +.subagent-enabled-inline :deep(.v-selection-control) { + min-height: 32px; +}