Skip to content

Commit 7dbfb6e

Browse files
committed
后台subagent任务:若主agent已结束,回退到原始唤醒机制
1 parent 47b61ec commit 7dbfb6e

4 files changed

Lines changed: 61 additions & 72 deletions

File tree

astrbot/core/astr_agent_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class AstrAgentContext:
1414
"""The star context instance"""
1515
event: AstrMessageEvent
1616
"""The message event associated with the agent context."""
17-
extra: dict[str, str] = Field(default_factory=dict)
17+
extra: dict[str, any] = Field(default_factory=dict)
1818
"""Customized extra data."""
1919

2020

astrbot/core/astr_agent_tool_exec.py

Lines changed: 48 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
PYTHON_TOOL,
2929
)
3030
from astrbot.core.cron.events import CronMessageEvent
31+
from astrbot.core.dynamic_subagent_manager import DynamicSubAgentManager
3132
from astrbot.core.message.components import Image
3233
from astrbot.core.message.message_event_result import (
3334
CommandResult,
@@ -281,7 +282,6 @@ async def _execute_handoff(
281282

282283
# Build handoff toolset from registered tools plus runtime computer tools.
283284
toolset = cls._build_handoff_toolset(run_context, tool.agent.tools)
284-
285285
ctx = run_context.context.context
286286
event = run_context.context.event
287287
umo = event.unified_msg_origin
@@ -342,13 +342,13 @@ async def _execute_handoff(
342342
prov_settings: dict = ctx.get_config(umo=umo).get("provider_settings", {})
343343
agent_max_step = int(prov_settings.get("max_agent_step", 30))
344344
stream = prov_settings.get("streaming_response", False)
345+
345346
# 如果有历史上下文,合并到 contexts 中
346347
if subagent_history:
347348
if contexts is None:
348349
contexts = subagent_history
349350
else:
350351
contexts = subagent_history + contexts
351-
352352
# 构建子代理的 system_prompt,添加 skills 提示词和公共上下文
353353
subagent_system_prompt = tool.agent.instructions or ""
354354
subagent_system_prompt = f"# Role\nYour name is {agent_name}(used for tool calling)\n{subagent_system_prompt}\n"
@@ -439,17 +439,21 @@ async def _execute_handoff_background(
439439
):
440440
"""Execute a handoff as a background task.
441441
442+
Immediately yields a success response with a task_id, then runs
443+
the subagent asynchronously. When the subagent finishes, a
444+
``CronMessageEvent`` is created so the main LLM can inform the
445+
user of the result – the same pattern used by
446+
``_execute_background`` for regular background tasks.
447+
442448
当启用增强SubAgent时,会在 DynamicSubAgentManager 中创建 pending 任务,
443449
并返回 task_id 给主 Agent,以便后续通过 wait_for_subagent 获取结果。
444450
"""
445451
event = run_context.context.event
446452
umo = event.unified_msg_origin
447453
agent_name = getattr(tool.agent, "name", None)
448454

449-
# 生成 subagent_task_id(用于 DynamicSubAgentManager)
455+
# check if enhanced subAgent
450456
subagent_task_id = None
451-
452-
# 检查是否启用增强版 SubAgent
453457
try:
454458
from astrbot.core.dynamic_subagent_manager import DynamicSubAgentManager
455459

@@ -463,7 +467,9 @@ async def _execute_handoff_background(
463467
)
464468

465469
if subagent_task_id.startswith("__PENDING_TASK_CREATE_FAILED__"):
466-
logger.info(subagent_task_id)
470+
logger.info(
471+
f"[EnhancedSubAgent] Failed to created background task {subagent_task_id} for {agent_name}"
472+
)
467473
else:
468474
DynamicSubAgentManager.set_subagent_status(
469475
session_id=umo,
@@ -475,9 +481,10 @@ async def _execute_handoff_background(
475481
f"[EnhancedSubAgent] Created background task {subagent_task_id} for {agent_name}"
476482
)
477483
except Exception as e:
478-
logger.info(f"[EnhancedSubAgent] Failed to create pending task: {e}")
484+
logger.info(
485+
f"[EnhancedSubAgent] Failed to created background task {subagent_task_id} for {agent_name}: {e}"
486+
)
479487

480-
# 生成原始的 task_id(用于唤醒机制等)
481488
original_task_id = uuid.uuid4().hex
482489

483490
async def _run_handoff_in_background() -> None:
@@ -498,7 +505,6 @@ async def _run_handoff_in_background() -> None:
498505

499506
asyncio.create_task(_run_handoff_in_background())
500507

501-
# 构建返回消息
502508
if subagent_task_id:
503509
text_content = mcp.types.TextContent(
504510
type="text",
@@ -565,26 +571,12 @@ async def _do_handoff_background(
565571

566572
execution_time = time.time() - start_time
567573
success = error_text is None
568-
569-
enhanced_subagent_enabled = False
570-
try:
571-
from astrbot.core.dynamic_subagent_manager import DynamicSubAgentManager
572-
573-
session = DynamicSubAgentManager.get_session(umo)
574-
if session and agent_name:
575-
# 检查是否是动态创建的 SubAgent
576-
if agent_name in session.subagents:
577-
enhanced_subagent_enabled = True
578-
except Exception:
579-
session = None
580-
581-
subagent_task_id = tool_args.get("subagent_task_id", None)
582-
583-
if enhanced_subagent_enabled and session and agent_name and subagent_task_id:
584-
# 如果增强版subagent正在运行:存储结果到 DynamicSubAgentManager,使得主Agent可以访问
585-
try:
586-
from astrbot.core.dynamic_subagent_manager import DynamicSubAgentManager
587-
574+
session = DynamicSubAgentManager.get_session(umo)
575+
if session and agent_name:
576+
# if it is enhanced subagent
577+
if agent_name in session.subagents:
578+
subagent_task_id = tool_args.get("subagent_task_id", None)
579+
# store the results of background enhanced subagent task
588580
DynamicSubAgentManager.store_subagent_result(
589581
session_id=umo,
590582
agent_name=agent_name,
@@ -594,6 +586,7 @@ async def _do_handoff_background(
594586
error=error_text,
595587
execution_time=execution_time,
596588
)
589+
# update subagent status
597590
if error_text:
598591
DynamicSubAgentManager.set_subagent_status(
599592
session_id=umo,
@@ -607,48 +600,47 @@ async def _do_handoff_background(
607600
status="COMPLETED",
608601
)
609602

610-
# 如果启用了 shared_context,发布完成状态
603+
# if shared_context is enabled, publish status
611604
if session.shared_context_enabled:
612-
status_content = f" SubAgent '{agent_name}' 任务'{subagent_task_id}'完成,耗时 {execution_time:.1f}s"
605+
status_content = f"[EnhancedSubAgent] SubAgent '{agent_name}' Task '{subagent_task_id}' Complete. Execution Time: {execution_time:.1f}s"
613606
if error_text:
614-
status_content = f" SubAgent '{agent_name}' 任务'{subagent_task_id}' 失败: {error_text}"
615-
607+
status_content = f"[EnhancedSubAgent] SubAgent '{agent_name}' Task '{subagent_task_id}' Failed: {error_text}"
616608
DynamicSubAgentManager.add_shared_context(
617609
session_id=umo,
618610
sender=agent_name,
619611
context_type="status",
620612
content=status_content,
621613
target="all",
622614
)
623-
624615
logger.info(
625616
f"[EnhancedSubAgent] Stored result for {agent_name} task {subagent_task_id}: "
626617
f"success={success}, time={execution_time:.1f}s"
627618
)
628619

629-
except Exception as e:
630-
logger.error(
631-
f"[EnhancedSubAgent] Failed to store result for {agent_name}: {e}"
632-
)
633-
# 存储失败时,回退到原有的唤醒机制
634-
await cls._wake_main_agent_for_background_result(
635-
run_context=run_context,
636-
task_id=task_id,
637-
tool_name=tool.name,
638-
result_text=result_text,
639-
tool_args=tool_args,
640-
note=(
641-
event.get_extra("background_note")
642-
or f"Background task for subagent '{agent_name}' finished."
643-
),
644-
summary_name=f"Dedicated to subagent `{agent_name}`",
645-
extra_result_fields={
646-
"subagent_name": agent_name,
647-
"subagent_task_id": subagent_task_id,
648-
},
649-
)
620+
try:
621+
context_extra = getattr(run_context.context, "extra", None)
622+
main_agent_runner = context_extra.get("main_agent_runner", None)
623+
main_agent_is_running = not main_agent_runner.done()
624+
except Exception as e:
625+
logger.error("get main agent failed:", e)
626+
main_agent_is_running = False
627+
628+
# Inform user through _wake_main_agent_for_background_result if main agent is over
629+
if not main_agent_is_running:
630+
await cls._wake_main_agent_for_background_result(
631+
run_context=run_context,
632+
task_id=task_id,
633+
tool_name=tool.name,
634+
result_text=result_text,
635+
tool_args=tool_args,
636+
note=(
637+
event.get_extra("background_note")
638+
or f"Background task for subagent '{agent_name}' finished."
639+
),
640+
summary_name=f"Dedicated to subagent `{agent_name}`",
641+
extra_result_fields={"subagent_name": agent_name},
642+
)
650643
else:
651-
# 未开启增强subagent:使用原有的唤醒机制
652644
await cls._wake_main_agent_for_background_result(
653645
run_context=run_context,
654646
task_id=task_id,

astrbot/core/astr_main_agent.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,8 +1449,7 @@ async def build_main_agent(
14491449

14501450
agent_runner = AgentRunner()
14511451
astr_agent_ctx = AstrAgentContext(
1452-
context=plugin_context,
1453-
event=event,
1452+
context=plugin_context, event=event, extra={"main_agent_runner": agent_runner}
14541453
)
14551454

14561455
if config.add_cron_tools:

astrbot/core/dynamic_subagent_manager.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
class DynamicSubAgentConfig:
2626
name: str
2727
system_prompt: str = ""
28-
tools: list | None = None
29-
skills: list | None = None
28+
tools: set | None = None
29+
skills: set | None = None
3030
provider_id: str | None = None
3131
description: str = ""
3232
workdir: str | None = None
@@ -650,7 +650,7 @@ def set_subagent_status(cls, session_id: str, agent_name: str, status: str) -> N
650650

651651
@classmethod
652652
def get_session(cls, session_id: str) -> DynamicSubAgentSession | None:
653-
return cls._sessions.get(session_id)
653+
return cls._sessions.get(session_id, None)
654654

655655
@classmethod
656656
def get_or_create_session(cls, session_id: str) -> DynamicSubAgentSession:
@@ -685,20 +685,19 @@ async def create_subagent(
685685
# When shared_context is enabled, the send_shared_context tool is allocated regardless of whether the main agent allocates the tool to the subagent
686686
if session.shared_context_enabled:
687687
if config.tools is None:
688-
config.tools = []
689-
if "send_shared_context_for_main_agent" in config.tools:
690-
config.tools.remove("send_shared_context_for_main_agent")
691-
config.tools.append("send_shared_context")
688+
config.tools = {}
689+
config.tools.discard("send_shared_context_for_main_agent")
690+
config.tools.add("send_shared_context")
692691
if "astrbot_execute_python" not in config.tools:
693-
config.tools.append("astrbot_execute_python")
692+
config.tools.add("astrbot_execute_python")
694693
if "astrbot_execute_shell" not in config.tools:
695-
config.tools.append("astrbot_execute_shell")
694+
config.tools.add("astrbot_execute_shell")
696695

697696
session.subagents[config.name] = config
698697
agent = Agent(
699698
name=config.name,
700699
instructions=config.system_prompt,
701-
tools=config.tools,
700+
tools=list(config.tools),
702701
)
703702
handoff_tool = HandoffTool(
704703
agent=agent,
@@ -1246,7 +1245,6 @@ async def call(self, context, **kwargs) -> str:
12461245
tools = kwargs.get("tools")
12471246
skills = kwargs.get("skills")
12481247
workdir = kwargs.get("workdir")
1249-
12501248
# 检查工作路径是否非法
12511249
if not self._check_path_safety(workdir):
12521250
workdir = get_astrbot_temp_path()
@@ -1255,8 +1253,8 @@ async def call(self, context, **kwargs) -> str:
12551253
config = DynamicSubAgentConfig(
12561254
name=name,
12571255
system_prompt=system_prompt,
1258-
tools=tools,
1259-
skills=skills,
1256+
tools=set(tools),
1257+
skills=set(skills),
12601258
workdir=workdir,
12611259
)
12621260

0 commit comments

Comments
 (0)