添加 DEFAULT_TEAM_NODE_MAX_TOOL_ITERATIONS 配置项以控制团队节点的最大工具迭代次数, 并修改 LocalAgentRunner 中的逻辑来使用此默认值当 envelope 中未指定时。 fix(runtime): 修复团队节点运行成功判断逻辑 更新运行成功判断条件,将 finish_reason 为 "max_tool_iterations_finalized" 的情况 视为运行失败,并添加对原始工具调用输出的检测,避免将其误判为成功完成。 feat(mcp): 添加团队工作流MCP工具类别支持 增加新的本地MCP工具类别 "team_workflow" 及其对应的工具创建功能, 为团队工作流提供本地工具支持。 refactor(engine): 调整AgentLoop最大工具迭代次数设置 将 AgentProfile 中的默认 max_tool_iterations 从 30 增加到 100, 同时移除 TaskExecutionPlanner 构造函数中的重复参数传递。 perf(mcp): 优化MCP连接管理避免重复连接 添加 mcp_connected 标志来跟踪MCP连接状态,确保 connect_all 只执行一次, 提高性能并避免不必要的重复连接。 refactor(skills): 移除技能团队模板相关功能 移除与技能团队模板相关的代码,包括解析、存储和处理逻辑, 简化技能记录结构和加载流程。 feat(process): 增强会话过程投影器功能 添加技能激活快照事件处理,改进团队运行完成消息显示, 并增强技能激活事件的时间戳记录功能。 refactor(tasks): 简化任务尝试编排器团队执行逻辑 移除团队执行相关代码,将所有任务统一按单步执行处理, 简化任务编排器的复杂度并提升执行效率。 fix(evidence): 修复节点证据评估中需求验证逻辑 更新节点证据评估逻辑,跳过自然语言证据需求的确定性验证, 只执行机器可读的需求验证,避免因自然语言需求导致的节点失败。
152 lines
4.7 KiB
Python
152 lines
4.7 KiB
Python
"""Internal Task execution planner for single-agent task attempts.
|
|
|
|
Team execution is now started explicitly through local Team Workflow MCP tools.
|
|
This planner only records why the normal Task attempt should continue as a
|
|
single root-agent run.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Literal
|
|
|
|
from beaver.coordinator.models import ExecutionGraph
|
|
from beaver.engine.context import SkillContext
|
|
from beaver.engine.providers import ProviderBundle
|
|
|
|
from .models import TaskRecord
|
|
from .skill_resolver import SkillResolutionReport
|
|
|
|
|
|
TaskExecutionMode = Literal["single", "team"]
|
|
|
|
|
|
def _agent_team_enabled() -> bool:
|
|
return os.getenv("BEAVER_AGENT_TEAM_ENABLED", "1").strip().lower() not in {"0", "false", "no", "off"}
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class TaskExecutionPlan:
|
|
mode: TaskExecutionMode
|
|
reason: str = ""
|
|
graph: ExecutionGraph | None = None
|
|
final_synthesis_instruction: str = ""
|
|
fallback_error: str | None = None
|
|
skill_resolution_report: list[SkillResolutionReport] = field(default_factory=list)
|
|
planner_adaptation: dict[str, Any] = field(default_factory=dict)
|
|
|
|
@property
|
|
def is_team(self) -> bool:
|
|
return self.mode == "team" and self.graph is not None
|
|
|
|
@classmethod
|
|
def single(
|
|
cls,
|
|
reason: str,
|
|
*,
|
|
fallback_error: str | None = None,
|
|
planner_adaptation: dict[str, Any] | None = None,
|
|
) -> "TaskExecutionPlan":
|
|
return cls(
|
|
mode="single",
|
|
reason=reason,
|
|
fallback_error=fallback_error,
|
|
planner_adaptation=dict(planner_adaptation or {}),
|
|
)
|
|
|
|
def to_event_payload(self) -> dict[str, Any]:
|
|
strategy = self.graph.strategy if self.graph is not None else None
|
|
nodes = self.graph.nodes if self.graph is not None else []
|
|
return {
|
|
"plan_mode": self.mode,
|
|
"reason": self.reason,
|
|
"strategy": strategy,
|
|
"node_ids": [node.node_id for node in nodes],
|
|
"skill_queries": [
|
|
str(node.agent.metadata.get("skill_query") or "")
|
|
for node in nodes
|
|
],
|
|
"selected_skill_names": [
|
|
name
|
|
for node in nodes
|
|
for name in node.inherited_pinned_skills
|
|
],
|
|
"ephemeral_guidance_ids": [
|
|
item.ephemeral_guidance_id
|
|
for item in self.skill_resolution_report
|
|
if item.ephemeral_guidance_id
|
|
],
|
|
"skill_resolution_report": [item.to_dict() for item in self.skill_resolution_report],
|
|
"planner_adaptation": dict(self.planner_adaptation),
|
|
"fallback_error": self.fallback_error,
|
|
}
|
|
|
|
|
|
class TaskExecutionPlanner:
|
|
"""Return the current Task execution mode for the root AgentLoop."""
|
|
|
|
async def plan(
|
|
self,
|
|
*,
|
|
task: TaskRecord,
|
|
user_message: str,
|
|
attempt_index: int,
|
|
provider_bundle: ProviderBundle | None = None,
|
|
timeout_seconds: float = 30.0,
|
|
skill_summaries: list[str] | None = None,
|
|
tool_hints: list[str] | None = None,
|
|
activated_skills: list[SkillContext] | None = None,
|
|
) -> TaskExecutionPlan:
|
|
if not _agent_team_enabled():
|
|
return TaskExecutionPlan.single("planner_disabled_by_environment")
|
|
if not self._needs_team_planning(task=task, user_message=user_message):
|
|
return TaskExecutionPlan.single("planner_skipped_simple_task")
|
|
return TaskExecutionPlan.single("planner_team_replaced_by_workflow_tools")
|
|
|
|
@staticmethod
|
|
def _needs_team_planning(*, task: TaskRecord, user_message: str) -> bool:
|
|
text = " ".join(
|
|
part
|
|
for part in (
|
|
task.goal,
|
|
task.description,
|
|
user_message,
|
|
)
|
|
if part
|
|
).lower()
|
|
if not text.strip():
|
|
return False
|
|
|
|
complex_markers = (
|
|
"agent team",
|
|
"sub-agent",
|
|
"multi-agent",
|
|
"parallel",
|
|
"dag",
|
|
"workflow",
|
|
"review",
|
|
"research",
|
|
"compare",
|
|
"comparison",
|
|
"architecture",
|
|
"refactor",
|
|
"multi-file",
|
|
"end-to-end",
|
|
"并行",
|
|
"团队",
|
|
"多智能体",
|
|
"子代理",
|
|
"工作流",
|
|
"评审",
|
|
"审查",
|
|
"调研",
|
|
"研究",
|
|
"对比",
|
|
"架构",
|
|
"重构",
|
|
"多文件",
|
|
"端到端",
|
|
)
|
|
return any(marker in text for marker in complex_markers)
|