feat(coordinator): 添加团队节点默认最大工具迭代次数配置

添加 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): 修复节点证据评估中需求验证逻辑

更新节点证据评估逻辑,跳过自然语言证据需求的确定性验证,
只执行机器可读的需求验证,避免因自然语言需求导致的节点失败。
This commit is contained in:
2026-06-26 16:36:29 +08:00
parent 53b13e8eac
commit 520a21a027
360 changed files with 13271 additions and 1848 deletions

View File

@ -0,0 +1,2 @@
"""Local team workflow graph builders."""

View File

@ -0,0 +1,70 @@
"""AgentRearrange graph builder using arrow/comma flow syntax."""
from __future__ import annotations
from typing import Any, Iterable
from beaver.coordinator.models import ExecutionGraph
from .base import (
WorkflowAgentSpec,
agent_name_set,
build_graph_from_dependencies,
edges_to_dependencies,
parse_agents,
validate_no_disconnected_agents,
)
WORKFLOW_NAME = "AgentRearrange"
def build_graph(
*,
task: str,
agents: Iterable[WorkflowAgentSpec | dict[str, Any]],
flow: str,
) -> ExecutionGraph:
del task
parsed = parse_agents(agents)
edges = parse_flow(flow, known_agents=agent_name_set(parsed))
dependencies = edges_to_dependencies(agents=parsed, edges=edges)
validate_no_disconnected_agents(agents=parsed, dependencies=dependencies)
return build_graph_from_dependencies(
workflow_name=WORKFLOW_NAME,
strategy="dag",
agents=parsed,
dependencies=dependencies,
)
def parse_flow(flow: str, *, known_agents: set[str]) -> list[tuple[str, str]]:
stages = _parse_stages(flow)
edges: list[tuple[str, str]] = []
for stage in stages:
for name in stage:
if name not in known_agents:
raise ValueError(f"workflow flow references unknown agent: {name}")
for left, right in zip(stages, stages[1:], strict=False):
for source in left:
for target in right:
edge = (source, target)
if edge not in edges:
edges.append(edge)
return edges
def _parse_stages(flow: str) -> list[list[str]]:
raw_flow = str(flow or "").strip()
if not raw_flow:
raise ValueError("workflow flow is required")
stages: list[list[str]] = []
for raw_stage in raw_flow.split("->"):
names = [name.strip() for name in raw_stage.split(",") if name.strip()]
if not names:
raise ValueError("workflow flow contains an empty stage")
if len(names) != len(set(names)):
raise ValueError("workflow flow contains duplicate agent names in a stage")
stages.append(names)
if len(stages) < 2:
raise ValueError("workflow flow must contain at least two stages")
return stages

View File

@ -0,0 +1,273 @@
"""Shared builders for local team workflow graph construction."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Iterable, Literal
from beaver.coordinator.models import AgentDescriptor, ExecutionGraph, ExecutionNode
GraphStrategy = Literal["sequence", "parallel", "dag"]
@dataclass(slots=True)
class WorkflowAgentSpec:
name: str
instruction: str
use_skill: str | None = None
skill_query: str | None = None
allowed_tool_names: list[str] | None = None
required_evidence: list[str] = field(default_factory=list)
evidence_contract: dict[str, Any] = field(default_factory=dict)
validation_rules: list[str] = field(default_factory=list)
required_for_completion: bool = True
block_downstream_on_partial: bool = False
max_tool_iterations: int | None = None
constraints: list[str] = field(default_factory=list)
expected_output: str | None = None
input_contract: dict[str, Any] = field(default_factory=dict)
output_contract: dict[str, Any] = field(default_factory=dict)
@dataclass(slots=True)
class WorkflowBuildResult:
graph: ExecutionGraph
workflow_name: str
def parse_agents(raw_agents: Iterable[WorkflowAgentSpec | dict[str, Any]]) -> list[WorkflowAgentSpec]:
agents: list[WorkflowAgentSpec] = []
for index, raw in enumerate(raw_agents, start=1):
if isinstance(raw, WorkflowAgentSpec):
spec = raw
elif isinstance(raw, dict):
spec = _agent_from_dict(raw, index=index)
else:
raise ValueError("workflow agents must be objects")
agents.append(spec)
validate_agent_names(agents)
return agents
def validate_agent_names(agents: list[WorkflowAgentSpec]) -> None:
if not agents:
raise ValueError("workflow requires at least one agent")
seen: set[str] = set()
for agent in agents:
if not agent.name:
raise ValueError("workflow agent name is required")
if not agent.instruction:
raise ValueError(f"workflow agent {agent.name!r} requires instruction")
if agent.name in seen:
raise ValueError(f"workflow agent names must be unique: {agent.name}")
seen.add(agent.name)
def agent_name_set(agents: list[WorkflowAgentSpec]) -> set[str]:
return {agent.name for agent in agents}
def build_graph_from_dependencies(
*,
workflow_name: str,
strategy: GraphStrategy,
agents: list[WorkflowAgentSpec],
dependencies: dict[str, list[str]],
) -> ExecutionGraph:
nodes = [
build_node(
workflow_name=workflow_name,
agent=agent,
depends_on=dependencies.get(agent.name, []),
)
for agent in agents
]
graph = ExecutionGraph(strategy=strategy, nodes=nodes)
graph.validate()
return graph
def build_node(
*,
workflow_name: str,
agent: WorkflowAgentSpec,
depends_on: list[str],
) -> ExecutionNode:
metadata = {
"sub_agent_kind": "generic_skill_worker",
"workflow_tool": workflow_name,
"workflow_agent_name": agent.name,
}
if agent.use_skill:
metadata["use_skill"] = agent.use_skill
if agent.skill_query:
metadata["skill_query"] = agent.skill_query
return ExecutionNode(
node_id=agent.name,
task=agent.instruction,
agent=AgentDescriptor(
name=agent.name,
role="",
system_prompt="",
metadata=metadata,
),
depends_on=list(depends_on),
constraints=list(agent.constraints),
expected_output=agent.expected_output,
input_contract=dict(agent.input_contract),
output_contract=dict(agent.output_contract),
allowed_tool_names=(
None if agent.allowed_tool_names is None else list(agent.allowed_tool_names)
),
required_evidence=list(agent.required_evidence),
evidence_contract=dict(agent.evidence_contract),
validation_rules=list(agent.validation_rules),
required_for_completion=agent.required_for_completion,
block_downstream_on_partial=agent.block_downstream_on_partial,
max_tool_iterations=agent.max_tool_iterations,
)
def edges_to_dependencies(
*,
agents: list[WorkflowAgentSpec],
edges: Iterable[tuple[str, str] | list[str]],
) -> dict[str, list[str]]:
known = agent_name_set(agents)
dependencies = {agent.name: [] for agent in agents}
for raw_edge in edges:
source, target = _parse_edge(raw_edge)
if source not in known:
raise ValueError(f"workflow edge references unknown agent: {source}")
if target not in known:
raise ValueError(f"workflow edge references unknown agent: {target}")
if source == target:
raise ValueError(f"workflow edge creates a self-cycle: {source}")
if source not in dependencies[target]:
dependencies[target].append(source)
return dependencies
def validate_output_agent(
*,
agents: list[WorkflowAgentSpec],
dependencies: dict[str, list[str]],
output_agent: str,
allow_disconnected: bool = False,
) -> None:
known = agent_name_set(agents)
if output_agent not in known:
raise ValueError(f"workflow output_agent references unknown agent: {output_agent}")
upstream = _upstream_nodes(output_agent, dependencies)
if not upstream:
raise ValueError(f"workflow output_agent {output_agent!r} must be reachable from upstream agents")
if allow_disconnected:
return
connected = set(upstream)
connected.add(output_agent)
disconnected = sorted(known - connected)
if disconnected:
raise ValueError(f"workflow has disconnected agent(s): {', '.join(disconnected)}")
def validate_no_disconnected_agents(
*,
agents: list[WorkflowAgentSpec],
dependencies: dict[str, list[str]],
) -> None:
known = agent_name_set(agents)
connected: set[str] = set()
for target, sources in dependencies.items():
if sources:
connected.add(target)
connected.update(sources)
disconnected = sorted(known - connected)
if disconnected:
raise ValueError(f"workflow has disconnected agent(s): {', '.join(disconnected)}")
def _agent_from_dict(raw: dict[str, Any], *, index: int) -> WorkflowAgentSpec:
name = _required_str(raw.get("name"), f"agents[{index}].name")
instruction = _required_str(raw.get("instruction"), f"agents[{index}].instruction")
return WorkflowAgentSpec(
name=name,
instruction=instruction,
use_skill=_optional_str(raw.get("use_skill")),
skill_query=_optional_str(raw.get("skill_query")),
allowed_tool_names=_optional_string_list(raw.get("allowed_tool_names")),
required_evidence=_string_list(raw.get("required_evidence")),
evidence_contract=_dict(raw.get("evidence_contract")),
validation_rules=_string_list(raw.get("validation_rules")),
required_for_completion=bool(raw.get("required_for_completion", True)),
block_downstream_on_partial=bool(raw.get("block_downstream_on_partial", False)),
max_tool_iterations=_optional_int(raw.get("max_tool_iterations")),
constraints=_string_list(raw.get("constraints")),
expected_output=_optional_str(raw.get("expected_output")),
input_contract=_dict(raw.get("input_contract")),
output_contract=_dict(raw.get("output_contract")),
)
def _parse_edge(raw_edge: tuple[str, str] | list[str]) -> tuple[str, str]:
if not isinstance(raw_edge, (list, tuple)) or len(raw_edge) != 2:
raise ValueError("workflow edges must be [source, target] pairs")
source = _required_str(raw_edge[0], "edge source")
target = _required_str(raw_edge[1], "edge target")
return source, target
def _upstream_nodes(node_id: str, dependencies: dict[str, list[str]]) -> set[str]:
result: set[str] = set()
def visit(current: str) -> None:
for dependency in dependencies.get(current, []):
if dependency in result:
continue
result.add(dependency)
visit(dependency)
visit(node_id)
return result
def _required_str(value: Any, label: str) -> str:
text = str(value or "").strip()
if not text:
raise ValueError(f"{label} is required")
return text
def _optional_str(value: Any) -> str | None:
text = str(value or "").strip()
return text or None
def _string_list(value: Any) -> list[str]:
if value is None:
return []
if not isinstance(value, list):
raise ValueError("expected a list of strings")
return [str(item).strip() for item in value if str(item).strip()]
def _optional_string_list(value: Any) -> list[str] | None:
if value is None:
return None
return _string_list(value)
def _dict(value: Any) -> dict[str, Any]:
return dict(value) if isinstance(value, dict) else {}
def _optional_int(value: Any) -> int | None:
if value is None:
return None
try:
return int(value)
except (TypeError, ValueError) as exc:
raise ValueError("max_tool_iterations must be an integer") from exc

View File

@ -0,0 +1,26 @@
"""ConcurrentWorkflow graph builder."""
from __future__ import annotations
from typing import Any, Iterable
from beaver.coordinator.models import ExecutionGraph
from .base import WorkflowAgentSpec, build_graph_from_dependencies, parse_agents
WORKFLOW_NAME = "ConcurrentWorkflow"
def build_graph(
*,
task: str,
agents: Iterable[WorkflowAgentSpec | dict[str, Any]],
) -> ExecutionGraph:
del task
parsed = parse_agents(agents)
return build_graph_from_dependencies(
workflow_name=WORKFLOW_NAME,
strategy="parallel",
agents=parsed,
dependencies={agent.name: [] for agent in parsed},
)

View File

@ -0,0 +1,174 @@
"""Runtime bridge for local team workflow MCP tools."""
from __future__ import annotations
import json
from typing import Any, Callable
from beaver.coordinator.models import ExecutionGraph, TeamRunResult
from beaver.tools.base import ToolContext, ToolResult
from . import agent_rearrange, concurrent, graph, mixture_of_agents, sequential
GraphBuilder = Callable[..., ExecutionGraph]
class TeamWorkflowExecutor:
"""Execute workflow MCP calls inside the current Beaver runtime."""
_BUILDERS: dict[str, GraphBuilder] = {
"SequentialWorkflow": sequential.build_graph,
"ConcurrentWorkflow": concurrent.build_graph,
"MixtureOfAgents": mixture_of_agents.build_graph,
"AgentRearrange": agent_rearrange.build_graph,
"GraphWorkflow": graph.build_graph,
}
async def execute(
self,
workflow_name: str,
arguments: dict[str, Any],
context: ToolContext,
*,
tool_name: str | None = None,
) -> ToolResult:
exposed_name = tool_name or workflow_name
try:
if str(context.metadata.get("source") or "").startswith("team:"):
raise ValueError("nested_team_workflow_not_allowed")
builder = self._BUILDERS.get(workflow_name)
if builder is None:
raise ValueError(f"unknown team workflow tool: {workflow_name}")
graph = builder(**dict(arguments or {}))
parent_task_id = _task_id(context)
parent_session_id = _session_id(context)
result = await self._run_team(
context=context,
graph=graph,
parent_task_id=parent_task_id,
parent_session_id=parent_session_id,
)
payload = _success_payload(
workflow_name=workflow_name,
graph=graph,
result=result,
)
return ToolResult(
success=True,
content=json.dumps(payload, ensure_ascii=False),
tool_name=exposed_name,
raw_output=payload,
)
except Exception as exc:
payload = {
"success": False,
"workflow": workflow_name,
"error": str(exc),
}
return ToolResult(
success=False,
content=json.dumps(payload, ensure_ascii=False),
tool_name=exposed_name,
error=str(exc),
raw_output=payload,
)
async def _run_team(
self,
*,
context: ToolContext,
graph: ExecutionGraph,
parent_task_id: str,
parent_session_id: str,
) -> TeamRunResult:
runner = context.services.get("agent_team_runner")
parent_run_id = _run_id(context)
if runner is not None:
return await runner(
graph,
parent_task_id=parent_task_id,
parent_session_id=parent_session_id,
parent_run_id=parent_run_id,
)
agent_loop = context.services.get("agent_loop")
if agent_loop is None:
raise ValueError("team workflow execution requires agent_loop or agent_team_runner")
provider_bundle = context.services.get("provider_bundle")
def provider_bundle_factory(_node: Any) -> Any:
return provider_bundle
from beaver.engine import AgentLoop
from beaver.services.team_service import TeamService
loaded = context.services.get("loaded")
team_loop = AgentLoop(profile=agent_loop.profile, loader=agent_loop.loader)
team_loop.loaded = loaded
return await TeamService(team_loop).run_team(
graph,
parent_task_id=parent_task_id,
parent_session_id=parent_session_id,
parent_run_id=parent_run_id,
provider_bundle_factory=provider_bundle_factory if provider_bundle is not None else None,
allow_candidate_generation=False,
)
def _task_id(context: ToolContext) -> str:
value = str(context.services.get("task_id") or context.metadata.get("task_id") or "").strip()
if not value:
raise ValueError("team workflow execution requires task_id")
return value
def _session_id(context: ToolContext) -> str:
value = str(context.session_id or context.services.get("session_id") or "").strip()
if not value:
raise ValueError("team workflow execution requires session_id")
return value
def _run_id(context: ToolContext) -> str | None:
return str(context.services.get("run_id") or context.metadata.get("run_id") or "").strip() or None
def _success_payload(
*,
workflow_name: str,
graph: ExecutionGraph,
result: TeamRunResult,
) -> dict[str, Any]:
return {
"success": result.success,
"workflow": workflow_name,
"summary": result.summary,
"run_ids": list(result.run_ids),
"session_ids": list(result.session_ids),
"node_results": [item.to_dict() for item in result.node_results],
"graph": _graph_to_dict(graph),
}
def _graph_to_dict(graph: ExecutionGraph) -> dict[str, Any]:
return {
"strategy": graph.strategy,
"nodes": [
{
"node_id": node.node_id,
"task": node.task,
"depends_on": list(node.depends_on),
"allowed_tool_names": (
None if node.allowed_tool_names is None else list(node.allowed_tool_names)
),
"required_evidence": list(node.required_evidence),
"evidence_contract": dict(node.evidence_contract),
"validation_rules": list(node.validation_rules),
"required_for_completion": node.required_for_completion,
"block_downstream_on_partial": node.block_downstream_on_partial,
"max_tool_iterations": node.max_tool_iterations,
"metadata": dict(node.agent.metadata),
}
for node in graph.nodes
],
}

View File

@ -0,0 +1,45 @@
"""GraphWorkflow explicit DAG builder."""
from __future__ import annotations
from typing import Any, Iterable
from beaver.coordinator.models import ExecutionGraph
from .base import (
WorkflowAgentSpec,
build_graph_from_dependencies,
edges_to_dependencies,
parse_agents,
validate_output_agent,
)
WORKFLOW_NAME = "GraphWorkflow"
def build_graph(
*,
task: str,
agents: Iterable[WorkflowAgentSpec | dict[str, Any]],
edges: Iterable[tuple[str, str] | list[str]],
output_agent: str,
allow_disconnected: bool = False,
) -> ExecutionGraph:
del task
parsed = parse_agents(agents)
edge_list = list(edges or [])
if not edge_list:
raise ValueError("GraphWorkflow requires edges")
dependencies = edges_to_dependencies(agents=parsed, edges=edge_list)
validate_output_agent(
agents=parsed,
dependencies=dependencies,
output_agent=str(output_agent or "").strip(),
allow_disconnected=allow_disconnected,
)
return build_graph_from_dependencies(
workflow_name=WORKFLOW_NAME,
strategy="dag",
agents=parsed,
dependencies=dependencies,
)

View File

@ -0,0 +1,261 @@
"""MCP schema tools for local team workflow graph builders."""
from __future__ import annotations
import json
from typing import Any, Callable
from beaver.coordinator.models import ExecutionGraph
from beaver.tools.base import BaseTool, ToolContext, ToolResult, ToolSpec
from . import agent_rearrange, concurrent, graph, mixture_of_agents, sequential
GraphBuilder = Callable[..., ExecutionGraph]
def create_team_workflow_tools() -> list[BaseTool]:
return [
TeamWorkflowSchemaTool(
name="SequentialWorkflow",
description=(
"Build a sequential Beaver team workflow graph. Use this for staged work "
"where each agent depends on the previous agent's output."
),
input_schema=_sequential_schema(),
builder=sequential.build_graph,
),
TeamWorkflowSchemaTool(
name="ConcurrentWorkflow",
description=(
"Build a concurrent Beaver team workflow graph. Use this only when agents "
"can work independently on the same task."
),
input_schema=_concurrent_schema(),
builder=concurrent.build_graph,
),
TeamWorkflowSchemaTool(
name="MixtureOfAgents",
description=(
"Build a mixture-of-agents Beaver team workflow graph where independent "
"expert agents feed one aggregator agent."
),
input_schema=_mixture_schema(),
builder=mixture_of_agents.build_graph,
),
TeamWorkflowSchemaTool(
name="AgentRearrange",
description=(
"Build a Beaver team workflow graph from strict flow syntax. Use '->' for "
"stage order and ',' for agents in the same parallel stage."
),
input_schema=_agent_rearrange_schema(),
builder=agent_rearrange.build_graph,
),
TeamWorkflowSchemaTool(
name="GraphWorkflow",
description=(
"Build an explicit Beaver DAG workflow graph. Use this advanced tool only "
"when the dependency edges must be specified directly."
),
input_schema=_graph_schema(),
builder=graph.build_graph,
),
]
class TeamWorkflowSchemaTool(BaseTool):
def __init__(
self,
*,
name: str,
description: str,
input_schema: dict[str, Any],
builder: GraphBuilder,
) -> None:
self._spec = ToolSpec(
name=name,
description=description,
input_schema=input_schema,
toolset="team_workflow",
always_available=False,
metadata={"category": "team_workflow"},
)
self._builder = builder
@property
def spec(self) -> ToolSpec:
return self._spec
async def invoke(self, arguments: dict[str, Any], context: ToolContext) -> ToolResult:
del context
try:
graph = self._builder(**dict(arguments or {}))
payload = {
"success": True,
"workflow": self.spec.name,
"graph": _graph_to_dict(graph),
}
return ToolResult(
success=True,
content=json.dumps(payload, ensure_ascii=False),
tool_name=self.spec.name,
raw_output=payload,
)
except Exception as exc:
payload = {"success": False, "workflow": self.spec.name, "error": str(exc)}
return ToolResult(
success=False,
content=json.dumps(payload, ensure_ascii=False),
tool_name=self.spec.name,
error=str(exc),
raw_output=payload,
)
def _graph_to_dict(graph: ExecutionGraph) -> dict[str, Any]:
return {
"strategy": graph.strategy,
"nodes": [
{
"node_id": node.node_id,
"task": node.task,
"depends_on": list(node.depends_on),
"allowed_tool_names": (
None if node.allowed_tool_names is None else list(node.allowed_tool_names)
),
"required_evidence": list(node.required_evidence),
"evidence_contract": dict(node.evidence_contract),
"validation_rules": list(node.validation_rules),
"required_for_completion": node.required_for_completion,
"block_downstream_on_partial": node.block_downstream_on_partial,
"max_tool_iterations": node.max_tool_iterations,
"metadata": dict(node.agent.metadata),
}
for node in graph.nodes
],
}
def _sequential_schema() -> dict[str, Any]:
return {
"type": "object",
"properties": {
"task": _task_schema(),
"agents": _agents_schema(),
},
"required": ["task", "agents"],
"additionalProperties": False,
}
def _concurrent_schema() -> dict[str, Any]:
return {
"type": "object",
"properties": {
"task": _task_schema(),
"agents": _agents_schema(),
},
"required": ["task", "agents"],
"additionalProperties": False,
}
def _mixture_schema() -> dict[str, Any]:
return {
"type": "object",
"properties": {
"task": _task_schema(),
"agents": _agents_schema(description="Expert agents that run independently before aggregation."),
"aggregator": _agent_schema(description="Aggregator agent that synthesizes expert outputs."),
},
"required": ["task", "agents", "aggregator"],
"additionalProperties": False,
}
def _agent_rearrange_schema() -> dict[str, Any]:
return {
"type": "object",
"properties": {
"task": _task_schema(),
"agents": _agents_schema(),
"flow": {
"type": "string",
"description": "Strict flow syntax, e.g. 'collector -> tactics, players -> synthesizer'.",
},
},
"required": ["task", "agents", "flow"],
"additionalProperties": False,
}
def _graph_schema() -> dict[str, Any]:
return {
"type": "object",
"properties": {
"task": _task_schema(),
"agents": _agents_schema(),
"edges": {
"type": "array",
"description": "Directed dependency edges as [source_agent, target_agent] pairs.",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {"type": "string"},
},
},
"output_agent": {
"type": "string",
"description": "Final output/synthesis agent. Must be reachable from upstream agents.",
},
"allow_disconnected": {
"type": "boolean",
"description": "Allow agents that are not connected to output_agent. Defaults to false.",
},
},
"required": ["task", "agents", "edges", "output_agent"],
"additionalProperties": False,
}
def _task_schema() -> dict[str, Any]:
return {
"type": "string",
"description": "Overall user task this workflow supports.",
}
def _agents_schema(*, description: str = "Workflow agents in the order or set used by this workflow.") -> dict[str, Any]:
return {
"type": "array",
"description": description,
"items": _agent_schema(),
"minItems": 1,
}
def _agent_schema(*, description: str = "One workflow agent slot.") -> dict[str, Any]:
return {
"type": "object",
"description": description,
"properties": {
"name": {"type": "string"},
"instruction": {"type": "string"},
"use_skill": {"type": "string"},
"skill_query": {"type": "string"},
"allowed_tool_names": {"type": "array", "items": {"type": "string"}},
"required_evidence": {"type": "array", "items": {"type": "string"}},
"evidence_contract": {"type": "object"},
"validation_rules": {"type": "array", "items": {"type": "string"}},
"required_for_completion": {"type": "boolean"},
"block_downstream_on_partial": {"type": "boolean"},
"max_tool_iterations": {"type": "integer"},
"constraints": {"type": "array", "items": {"type": "string"}},
"expected_output": {"type": "string"},
"input_contract": {"type": "object"},
"output_contract": {"type": "object"},
},
"required": ["name", "instruction"],
"additionalProperties": False,
}

View File

@ -0,0 +1,37 @@
"""MixtureOfAgents graph builder."""
from __future__ import annotations
from typing import Any, Iterable
from beaver.coordinator.models import ExecutionGraph
from .base import (
WorkflowAgentSpec,
build_graph_from_dependencies,
parse_agents,
validate_agent_names,
)
WORKFLOW_NAME = "MixtureOfAgents"
def build_graph(
*,
task: str,
agents: Iterable[WorkflowAgentSpec | dict[str, Any]],
aggregator: WorkflowAgentSpec | dict[str, Any],
) -> ExecutionGraph:
del task
experts = parse_agents(agents)
parsed_aggregator = parse_agents([aggregator])[0]
all_agents = [*experts, parsed_aggregator]
validate_agent_names(all_agents)
dependencies = {agent.name: [] for agent in all_agents}
dependencies[parsed_aggregator.name] = [agent.name for agent in experts]
return build_graph_from_dependencies(
workflow_name=WORKFLOW_NAME,
strategy="dag",
agents=all_agents,
dependencies=dependencies,
)

View File

@ -0,0 +1,29 @@
"""SequentialWorkflow graph builder."""
from __future__ import annotations
from typing import Any, Iterable
from beaver.coordinator.models import ExecutionGraph
from .base import WorkflowAgentSpec, build_graph_from_dependencies, parse_agents
WORKFLOW_NAME = "SequentialWorkflow"
def build_graph(
*,
task: str,
agents: Iterable[WorkflowAgentSpec | dict[str, Any]],
) -> ExecutionGraph:
del task
parsed = parse_agents(agents)
dependencies = {agent.name: [] for agent in parsed}
for previous, current in zip(parsed, parsed[1:], strict=False):
dependencies[current.name].append(previous.name)
return build_graph_from_dependencies(
workflow_name=WORKFLOW_NAME,
strategy="sequence",
agents=parsed,
dependencies=dependencies,
)