chore: import upstream snapshot with attribution
Validate YAML Workflows / Validate YAML Configuration Files (push) Has been cancelled
Validate YAML Workflows / Validate YAML Configuration Files (push) Has been cancelled
This commit is contained in:
Executable
+19
@@ -0,0 +1,19 @@
|
||||
"""Runtime utilities for workflow execution."""
|
||||
|
||||
from .runtime_context import RuntimeContext
|
||||
from .runtime_builder import RuntimeBuilder
|
||||
from .execution_strategy import (
|
||||
DagExecutionStrategy,
|
||||
CycleExecutionStrategy,
|
||||
MajorityVoteStrategy,
|
||||
)
|
||||
from .result_archiver import ResultArchiver
|
||||
|
||||
__all__ = [
|
||||
"RuntimeContext",
|
||||
"RuntimeBuilder",
|
||||
"DagExecutionStrategy",
|
||||
"CycleExecutionStrategy",
|
||||
"MajorityVoteStrategy",
|
||||
"ResultArchiver",
|
||||
]
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
"""Execution strategies for different graph topologies."""
|
||||
|
||||
from collections import Counter
|
||||
from typing import Callable, Dict, List, Sequence
|
||||
|
||||
from entity.configs import Node
|
||||
from entity.messages import Message
|
||||
from utils.log_manager import LogManager
|
||||
from workflow.executor.dag_executor import DAGExecutor
|
||||
from workflow.executor.cycle_executor import CycleExecutor
|
||||
from workflow.executor.parallel_executor import ParallelExecutor
|
||||
|
||||
|
||||
class DagExecutionStrategy:
|
||||
"""Executes acyclic graphs using the DAGExecutor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
log_manager: LogManager,
|
||||
nodes: Dict[str, Node],
|
||||
layers: List[List[str]],
|
||||
execute_node_func: Callable[[Node], None],
|
||||
) -> None:
|
||||
self.log_manager = log_manager
|
||||
self.nodes = nodes
|
||||
self.layers = layers
|
||||
self.execute_node_func = execute_node_func
|
||||
|
||||
def run(self) -> None:
|
||||
dag_executor = DAGExecutor(
|
||||
log_manager=self.log_manager,
|
||||
nodes=self.nodes,
|
||||
layers=self.layers,
|
||||
execute_node_func=self.execute_node_func,
|
||||
)
|
||||
dag_executor.execute()
|
||||
|
||||
|
||||
class CycleExecutionStrategy:
|
||||
"""Executes graphs containing cycles via CycleExecutor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
log_manager: LogManager,
|
||||
nodes: Dict[str, Node],
|
||||
cycle_execution_order: List[Dict[str, str]],
|
||||
cycle_manager,
|
||||
execute_node_func: Callable[[Node], None],
|
||||
) -> None:
|
||||
self.log_manager = log_manager
|
||||
self.nodes = nodes
|
||||
self.cycle_execution_order = cycle_execution_order
|
||||
self.cycle_manager = cycle_manager
|
||||
self.execute_node_func = execute_node_func
|
||||
|
||||
def run(self) -> None:
|
||||
cycle_executor = CycleExecutor(
|
||||
log_manager=self.log_manager,
|
||||
nodes=self.nodes,
|
||||
cycle_execution_order=self.cycle_execution_order,
|
||||
cycle_manager=self.cycle_manager,
|
||||
execute_node_func=self.execute_node_func,
|
||||
)
|
||||
cycle_executor.execute()
|
||||
|
||||
|
||||
class MajorityVoteStrategy:
|
||||
"""Executes graphs configured for majority voting (no edges)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
log_manager: LogManager,
|
||||
nodes: Dict[str, Node],
|
||||
initial_messages: Sequence[Message],
|
||||
execute_node_func: Callable[[Node], None],
|
||||
payload_to_text_func: Callable[[object], str],
|
||||
) -> None:
|
||||
self.log_manager = log_manager
|
||||
self.nodes = nodes
|
||||
self.initial_messages = initial_messages
|
||||
self.execute_node_func = execute_node_func
|
||||
self.payload_to_text = payload_to_text_func
|
||||
|
||||
def run(self) -> str:
|
||||
self.log_manager.info("Executing graph with majority voting approach")
|
||||
all_nodes = list(self.nodes.values())
|
||||
if not all_nodes:
|
||||
self.log_manager.error("No nodes to execute in majority voting mode")
|
||||
return ""
|
||||
|
||||
for node in all_nodes:
|
||||
node.clear_input()
|
||||
for message in self.initial_messages:
|
||||
node.append_input(message.clone())
|
||||
|
||||
node_ids = [node.id for node in all_nodes]
|
||||
|
||||
def _execute(node_id: str) -> None:
|
||||
self.execute_node_func(self.nodes[node_id])
|
||||
|
||||
parallel_executor = ParallelExecutor(self.log_manager, self.nodes)
|
||||
parallel_executor.execute_nodes_parallel(node_ids, _execute)
|
||||
|
||||
return self._collect_majority_result()
|
||||
|
||||
def _collect_majority_result(self) -> str:
|
||||
node_outputs: List[Dict[str, str]] = []
|
||||
for node_id, node in self.nodes.items():
|
||||
if node.output:
|
||||
output_text = self.payload_to_text(node.output[-1])
|
||||
else:
|
||||
output_text = ""
|
||||
node_outputs.append(
|
||||
{
|
||||
"node_id": node_id,
|
||||
"node_type": node.node_type,
|
||||
"output": output_text,
|
||||
}
|
||||
)
|
||||
|
||||
output_values = [item["output"] for item in node_outputs]
|
||||
output_counts = Counter(output_values)
|
||||
|
||||
non_empty_outputs = [value for value in output_values if value.strip()]
|
||||
if non_empty_outputs:
|
||||
output_counts = Counter(non_empty_outputs)
|
||||
|
||||
if not output_counts:
|
||||
self.log_manager.warning("No outputs available for majority voting")
|
||||
return ""
|
||||
|
||||
majority_output, count = output_counts.most_common(1)[0]
|
||||
self.log_manager.info(
|
||||
"Majority output determined",
|
||||
details={"result": majority_output, "votes": count},
|
||||
)
|
||||
self.log_manager.info(
|
||||
"All node outputs",
|
||||
details={
|
||||
"outputs": [
|
||||
(
|
||||
item["node_id"],
|
||||
item["output"][:50] + "..." if len(item["output"]) > 50 else item["output"],
|
||||
)
|
||||
for item in node_outputs
|
||||
]
|
||||
},
|
||||
)
|
||||
return majority_output
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
"""Utilities for persisting execution artifacts."""
|
||||
|
||||
from utils.log_manager import LogManager
|
||||
from utils.token_tracker import TokenTracker
|
||||
from workflow.graph_context import GraphContext
|
||||
|
||||
|
||||
class ResultArchiver:
|
||||
"""Handles post-execution persistence (tokens, logs, metadata)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
graph: GraphContext,
|
||||
log_manager: LogManager,
|
||||
token_tracker: TokenTracker,
|
||||
) -> None:
|
||||
self.graph = graph
|
||||
self.log_manager = log_manager
|
||||
self.token_tracker = token_tracker
|
||||
|
||||
def export(self, final_result: str) -> None:
|
||||
token_usage_path = self.graph.directory / f"token_usage_{self.graph.name}.json"
|
||||
self.token_tracker.export_to_file(str(token_usage_path))
|
||||
self.log_manager.record_workflow_end(
|
||||
success=True,
|
||||
details={
|
||||
"token_usage": self.token_tracker.get_token_usage(),
|
||||
"final_result": final_result,
|
||||
},
|
||||
)
|
||||
log_file_path = self.graph.directory / "execution_logs.json"
|
||||
self.log_manager.save_logs(str(log_file_path))
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
"""Builder that assembles the runtime context for workflow execution."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from runtime.node.agent import ToolManager
|
||||
from utils.attachments import AttachmentStore
|
||||
from utils.function_manager import EDGE_FUNCTION_DIR, EDGE_PROCESSOR_FUNCTION_DIR, get_function_manager
|
||||
from utils.log_manager import LogManager
|
||||
from utils.logger import WorkflowLogger
|
||||
from utils.token_tracker import TokenTracker
|
||||
from workflow.graph_context import GraphContext
|
||||
|
||||
from .runtime_context import RuntimeContext
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeBuilder:
|
||||
"""Constructs RuntimeContext instances for GraphExecutor."""
|
||||
|
||||
graph: GraphContext
|
||||
|
||||
def build(self, logger: Optional[WorkflowLogger] = None, *, session_id: Optional[str] = None) -> RuntimeContext:
|
||||
tool_manager = ToolManager()
|
||||
function_manager = get_function_manager(EDGE_FUNCTION_DIR)
|
||||
processor_function_manager = get_function_manager(EDGE_PROCESSOR_FUNCTION_DIR)
|
||||
logger = logger or WorkflowLogger(self.graph.name, self.graph.log_level)
|
||||
log_manager = LogManager(logger)
|
||||
token_tracker = TokenTracker(workflow_id=self.graph.name)
|
||||
|
||||
code_workspace = (self.graph.directory / "code_workspace").resolve()
|
||||
code_workspace.mkdir(parents=True, exist_ok=True)
|
||||
attachments_dir = code_workspace / "attachments"
|
||||
attachments_dir.mkdir(parents=True, exist_ok=True)
|
||||
attachment_store = AttachmentStore(attachments_dir)
|
||||
|
||||
global_state: Dict[str, Any] = {
|
||||
"graph_directory": self.graph.directory,
|
||||
"vars": self.graph.config.vars,
|
||||
"python_workspace_root": code_workspace,
|
||||
"attachment_store": attachment_store,
|
||||
}
|
||||
|
||||
context = RuntimeContext(
|
||||
tool_manager=tool_manager,
|
||||
function_manager=function_manager,
|
||||
edge_processor_function_manager=processor_function_manager,
|
||||
logger=logger,
|
||||
log_manager=log_manager,
|
||||
token_tracker=token_tracker,
|
||||
attachment_store=attachment_store,
|
||||
code_workspace=code_workspace,
|
||||
global_state=global_state,
|
||||
)
|
||||
context.session_id = session_id
|
||||
if session_id:
|
||||
context.global_state.setdefault("session_id", session_id)
|
||||
return context
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
"""Shared runtime context for workflow execution."""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from runtime.node.agent import ToolManager
|
||||
from utils.function_manager import FunctionManager
|
||||
from utils.logger import WorkflowLogger
|
||||
from utils.log_manager import LogManager
|
||||
from utils.token_tracker import TokenTracker
|
||||
from utils.attachments import AttachmentStore
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeContext:
|
||||
"""Container for runtime-wide dependencies required by GraphExecutor."""
|
||||
|
||||
tool_manager: ToolManager
|
||||
function_manager: FunctionManager
|
||||
edge_processor_function_manager: FunctionManager
|
||||
logger: WorkflowLogger
|
||||
log_manager: LogManager
|
||||
token_tracker: TokenTracker
|
||||
attachment_store: AttachmentStore
|
||||
code_workspace: Path
|
||||
global_state: Dict[str, Any] = field(default_factory=dict)
|
||||
cycle_manager: Optional[Any] = None # Late-bound by GraphManager
|
||||
session_id: Optional[str] = None
|
||||
workspace_hook: Optional[Any] = None
|
||||
Reference in New Issue
Block a user