attune-ai 2.1.5__py3-none-any.whl → 2.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- attune/cli/__init__.py +3 -59
- attune/cli/commands/batch.py +4 -12
- attune/cli/commands/cache.py +8 -16
- attune/cli/commands/provider.py +17 -0
- attune/cli/commands/routing.py +3 -1
- attune/cli/commands/setup.py +122 -0
- attune/cli/commands/tier.py +1 -3
- attune/cli/commands/workflow.py +31 -0
- attune/cli/parsers/cache.py +1 -0
- attune/cli/parsers/help.py +1 -3
- attune/cli/parsers/provider.py +7 -0
- attune/cli/parsers/routing.py +1 -3
- attune/cli/parsers/setup.py +7 -0
- attune/cli/parsers/status.py +1 -3
- attune/cli/parsers/tier.py +1 -3
- attune/cli_minimal.py +9 -3
- attune/cli_router.py +9 -7
- attune/cli_unified.py +3 -0
- attune/dashboard/app.py +3 -1
- attune/dashboard/simple_server.py +3 -1
- attune/dashboard/standalone_server.py +7 -3
- attune/mcp/server.py +54 -102
- attune/memory/long_term.py +0 -2
- attune/memory/short_term/__init__.py +84 -0
- attune/memory/short_term/base.py +465 -0
- attune/memory/short_term/batch.py +219 -0
- attune/memory/short_term/caching.py +227 -0
- attune/memory/short_term/conflicts.py +265 -0
- attune/memory/short_term/cross_session.py +122 -0
- attune/memory/short_term/facade.py +653 -0
- attune/memory/short_term/pagination.py +207 -0
- attune/memory/short_term/patterns.py +271 -0
- attune/memory/short_term/pubsub.py +286 -0
- attune/memory/short_term/queues.py +244 -0
- attune/memory/short_term/security.py +300 -0
- attune/memory/short_term/sessions.py +250 -0
- attune/memory/short_term/streams.py +242 -0
- attune/memory/short_term/timelines.py +234 -0
- attune/memory/short_term/transactions.py +184 -0
- attune/memory/short_term/working.py +252 -0
- attune/meta_workflows/cli_commands/__init__.py +3 -0
- attune/meta_workflows/cli_commands/agent_commands.py +0 -4
- attune/meta_workflows/cli_commands/analytics_commands.py +0 -6
- attune/meta_workflows/cli_commands/config_commands.py +0 -5
- attune/meta_workflows/cli_commands/memory_commands.py +0 -5
- attune/meta_workflows/cli_commands/template_commands.py +0 -5
- attune/meta_workflows/cli_commands/workflow_commands.py +0 -6
- attune/meta_workflows/plan_generator.py +2 -4
- attune/models/adaptive_routing.py +4 -8
- attune/models/auth_cli.py +3 -9
- attune/models/auth_strategy.py +2 -4
- attune/models/telemetry/analytics.py +0 -2
- attune/models/telemetry/backend.py +0 -3
- attune/models/telemetry/storage.py +0 -2
- attune/monitoring/alerts.py +6 -10
- attune/orchestration/_strategies/__init__.py +156 -0
- attune/orchestration/_strategies/base.py +227 -0
- attune/orchestration/_strategies/conditional_strategies.py +365 -0
- attune/orchestration/_strategies/conditions.py +369 -0
- attune/orchestration/_strategies/core_strategies.py +479 -0
- attune/orchestration/_strategies/data_classes.py +64 -0
- attune/orchestration/_strategies/nesting.py +233 -0
- attune/orchestration/execution_strategies.py +58 -1567
- attune/orchestration/meta_orchestrator.py +1 -3
- attune/project_index/scanner.py +1 -3
- attune/project_index/scanner_parallel.py +7 -5
- attune/socratic/storage.py +2 -4
- attune/socratic_router.py +1 -3
- attune/telemetry/agent_coordination.py +9 -3
- attune/telemetry/agent_tracking.py +16 -3
- attune/telemetry/approval_gates.py +22 -5
- attune/telemetry/cli.py +1 -3
- attune/telemetry/commands/dashboard_commands.py +24 -8
- attune/telemetry/event_streaming.py +8 -2
- attune/telemetry/feedback_loop.py +10 -2
- attune/tools.py +2 -1
- attune/workflow_commands.py +1 -3
- attune/workflow_patterns/structural.py +4 -8
- attune/workflows/__init__.py +54 -10
- attune/workflows/autonomous_test_gen.py +158 -102
- attune/workflows/base.py +48 -672
- attune/workflows/batch_processing.py +1 -3
- attune/workflows/compat.py +156 -0
- attune/workflows/cost_mixin.py +141 -0
- attune/workflows/data_classes.py +92 -0
- attune/workflows/document_gen/workflow.py +11 -14
- attune/workflows/history.py +16 -9
- attune/workflows/llm_base.py +1 -3
- attune/workflows/migration.py +432 -0
- attune/workflows/output.py +2 -7
- attune/workflows/parsing_mixin.py +427 -0
- attune/workflows/perf_audit.py +3 -1
- attune/workflows/progress.py +9 -11
- attune/workflows/release_prep.py +5 -1
- attune/workflows/routing.py +0 -2
- attune/workflows/secure_release.py +4 -1
- attune/workflows/security_audit.py +20 -14
- attune/workflows/security_audit_phase3.py +28 -22
- attune/workflows/seo_optimization.py +27 -27
- attune/workflows/test_gen/test_templates.py +1 -4
- attune/workflows/test_gen/workflow.py +0 -2
- attune/workflows/test_gen_behavioral.py +6 -19
- attune/workflows/test_gen_parallel.py +8 -6
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/METADATA +4 -3
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/RECORD +121 -96
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/entry_points.txt +0 -2
- attune_healthcare/monitors/monitoring/__init__.py +9 -9
- attune_llm/agent_factory/__init__.py +6 -6
- attune_llm/agent_factory/adapters/haystack_adapter.py +1 -4
- attune_llm/commands/__init__.py +10 -10
- attune_llm/commands/models.py +3 -3
- attune_llm/config/__init__.py +8 -8
- attune_llm/learning/__init__.py +3 -3
- attune_llm/learning/extractor.py +5 -3
- attune_llm/learning/storage.py +5 -3
- attune_llm/security/__init__.py +17 -17
- attune_llm/utils/tokens.py +3 -1
- attune/cli_legacy.py +0 -3978
- attune/memory/short_term.py +0 -2192
- attune/workflows/manage_docs.py +0 -87
- attune/workflows/test5.py +0 -125
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/WHEEL +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +0 -0
- {attune_ai-2.1.5.dist-info → attune_ai-2.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Execution strategies for agent composition patterns.
|
|
2
|
+
|
|
3
|
+
This submodule implements the grammar rules for composing agents:
|
|
4
|
+
1. Sequential (A → B → C)
|
|
5
|
+
2. Parallel (A || B || C)
|
|
6
|
+
3. Debate (A ⇄ B ⇄ C → Synthesis)
|
|
7
|
+
4. Teaching (Junior → Expert validation)
|
|
8
|
+
5. Refinement (Draft → Review → Polish)
|
|
9
|
+
6. Adaptive (Classifier → Specialist)
|
|
10
|
+
7. Conditional (if X then A else B) - branching based on gates
|
|
11
|
+
|
|
12
|
+
This package provides modular organization of execution strategies:
|
|
13
|
+
- data_classes: AgentResult, StrategyResult
|
|
14
|
+
- conditions: ConditionType, Condition, Branch, ConditionEvaluator
|
|
15
|
+
- nesting: WorkflowReference, InlineWorkflow, NestingContext, WorkflowDefinition
|
|
16
|
+
- base: ExecutionStrategy ABC
|
|
17
|
+
- core_strategies: Sequential, Parallel, Debate, Teaching, Refinement, Adaptive
|
|
18
|
+
- conditional_strategies: Conditional, MultiConditional, Nested, NestedSequential
|
|
19
|
+
- advanced: ToolEnhanced, PromptCached, etc. (TODO)
|
|
20
|
+
|
|
21
|
+
Copyright 2025 Smart-AI-Memory
|
|
22
|
+
Licensed under Fair Source License 0.9
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
# Base strategy class
|
|
28
|
+
from .base import ExecutionStrategy
|
|
29
|
+
|
|
30
|
+
# Conditional and nested strategies
|
|
31
|
+
from .conditional_strategies import (
|
|
32
|
+
ConditionalStrategy,
|
|
33
|
+
MultiConditionalStrategy,
|
|
34
|
+
NestedSequentialStrategy,
|
|
35
|
+
NestedStrategy,
|
|
36
|
+
StepDefinition,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Condition types and evaluator
|
|
40
|
+
from .conditions import Branch, Condition, ConditionEvaluator, ConditionType
|
|
41
|
+
|
|
42
|
+
# Core strategies
|
|
43
|
+
from .core_strategies import (
|
|
44
|
+
AdaptiveStrategy,
|
|
45
|
+
DebateStrategy,
|
|
46
|
+
ParallelStrategy,
|
|
47
|
+
RefinementStrategy,
|
|
48
|
+
SequentialStrategy,
|
|
49
|
+
TeachingStrategy,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Data classes
|
|
53
|
+
from .data_classes import AgentResult, StrategyResult
|
|
54
|
+
|
|
55
|
+
# Nesting support
|
|
56
|
+
from .nesting import (
|
|
57
|
+
WORKFLOW_REGISTRY,
|
|
58
|
+
InlineWorkflow,
|
|
59
|
+
NestingContext,
|
|
60
|
+
WorkflowDefinition,
|
|
61
|
+
WorkflowReference,
|
|
62
|
+
get_workflow,
|
|
63
|
+
register_workflow,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Strategy registry for lookup by name (partial - advanced strategies added in main module)
|
|
67
|
+
_STRATEGY_REGISTRY: dict[str, type[ExecutionStrategy]] = {
|
|
68
|
+
# Core patterns (1-6)
|
|
69
|
+
"sequential": SequentialStrategy,
|
|
70
|
+
"parallel": ParallelStrategy,
|
|
71
|
+
"debate": DebateStrategy,
|
|
72
|
+
"teaching": TeachingStrategy,
|
|
73
|
+
"refinement": RefinementStrategy,
|
|
74
|
+
"adaptive": AdaptiveStrategy,
|
|
75
|
+
# Conditional patterns (7+)
|
|
76
|
+
"conditional": ConditionalStrategy,
|
|
77
|
+
"multi_conditional": MultiConditionalStrategy,
|
|
78
|
+
"nested": NestedStrategy,
|
|
79
|
+
"nested_sequential": NestedSequentialStrategy,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_strategy(strategy_name: str) -> ExecutionStrategy:
|
|
84
|
+
"""Get strategy instance by name.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
strategy_name: Strategy name (e.g., "sequential", "parallel")
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
ExecutionStrategy instance
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If strategy name is invalid
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> strategy = get_strategy("sequential")
|
|
97
|
+
>>> isinstance(strategy, SequentialStrategy)
|
|
98
|
+
True
|
|
99
|
+
"""
|
|
100
|
+
if strategy_name not in _STRATEGY_REGISTRY:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Unknown strategy: {strategy_name}. Available: {list(_STRATEGY_REGISTRY.keys())}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
strategy_class = _STRATEGY_REGISTRY[strategy_name]
|
|
106
|
+
return strategy_class()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def register_strategy(name: str, strategy_class: type[ExecutionStrategy]) -> None:
|
|
110
|
+
"""Register a strategy class by name.
|
|
111
|
+
|
|
112
|
+
Used by main module to add advanced strategies.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: Strategy name
|
|
116
|
+
strategy_class: Strategy class
|
|
117
|
+
"""
|
|
118
|
+
_STRATEGY_REGISTRY[name] = strategy_class
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
__all__ = [
|
|
122
|
+
# Data classes
|
|
123
|
+
"AgentResult",
|
|
124
|
+
"StrategyResult",
|
|
125
|
+
# Conditions
|
|
126
|
+
"Branch",
|
|
127
|
+
"Condition",
|
|
128
|
+
"ConditionEvaluator",
|
|
129
|
+
"ConditionType",
|
|
130
|
+
# Nesting
|
|
131
|
+
"InlineWorkflow",
|
|
132
|
+
"NestingContext",
|
|
133
|
+
"WORKFLOW_REGISTRY",
|
|
134
|
+
"WorkflowDefinition",
|
|
135
|
+
"WorkflowReference",
|
|
136
|
+
"get_workflow",
|
|
137
|
+
"register_workflow",
|
|
138
|
+
# Base strategy
|
|
139
|
+
"ExecutionStrategy",
|
|
140
|
+
# Core strategies
|
|
141
|
+
"AdaptiveStrategy",
|
|
142
|
+
"DebateStrategy",
|
|
143
|
+
"ParallelStrategy",
|
|
144
|
+
"RefinementStrategy",
|
|
145
|
+
"SequentialStrategy",
|
|
146
|
+
"TeachingStrategy",
|
|
147
|
+
# Conditional strategies
|
|
148
|
+
"ConditionalStrategy",
|
|
149
|
+
"MultiConditionalStrategy",
|
|
150
|
+
"NestedSequentialStrategy",
|
|
151
|
+
"NestedStrategy",
|
|
152
|
+
"StepDefinition",
|
|
153
|
+
# Functions
|
|
154
|
+
"get_strategy",
|
|
155
|
+
"register_strategy",
|
|
156
|
+
]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Base class for agent composition strategies.
|
|
2
|
+
|
|
3
|
+
This module defines the ExecutionStrategy abstract base class that all
|
|
4
|
+
strategy implementations must inherit from.
|
|
5
|
+
|
|
6
|
+
Security:
|
|
7
|
+
- All agent outputs validated before passing to next agent
|
|
8
|
+
- No eval() or exec() usage
|
|
9
|
+
- Timeout enforcement at strategy level
|
|
10
|
+
|
|
11
|
+
Copyright 2025 Smart-AI-Memory
|
|
12
|
+
Licensed under Fair Source License 0.9
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from .data_classes import AgentResult, StrategyResult
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..agent_templates import AgentTemplate
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ExecutionStrategy(ABC):
|
|
30
|
+
"""Base class for agent composition strategies.
|
|
31
|
+
|
|
32
|
+
All strategies must implement execute() method to define
|
|
33
|
+
how agents are coordinated and results aggregated.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
38
|
+
"""Execute agents using this strategy.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
agents: List of agent templates to execute
|
|
42
|
+
context: Initial context for execution
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
StrategyResult with aggregated outputs
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If agents list is empty
|
|
49
|
+
TimeoutError: If execution exceeds timeout
|
|
50
|
+
"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
async def _execute_agent(self, agent: AgentTemplate, context: dict[str, Any]) -> AgentResult:
|
|
54
|
+
"""Execute a single agent with real analysis tools.
|
|
55
|
+
|
|
56
|
+
Maps agent capabilities to real tool implementations and executes them.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
agent: Agent template to execute
|
|
60
|
+
context: Execution context
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
AgentResult with execution outcome
|
|
64
|
+
"""
|
|
65
|
+
import time
|
|
66
|
+
|
|
67
|
+
from ..real_tools import (
|
|
68
|
+
RealCodeQualityAnalyzer,
|
|
69
|
+
RealCoverageAnalyzer,
|
|
70
|
+
RealDocumentationAnalyzer,
|
|
71
|
+
RealSecurityAuditor,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
logger.info(f"Executing agent: {agent.id} ({agent.role})")
|
|
75
|
+
start_time = time.perf_counter()
|
|
76
|
+
|
|
77
|
+
# Get project root from context
|
|
78
|
+
project_root = context.get("project_root", ".")
|
|
79
|
+
target_path = context.get("target_path", "src")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Map agent ID to real tool implementation
|
|
83
|
+
if agent.id == "security_auditor" or "security" in agent.role.lower():
|
|
84
|
+
auditor = RealSecurityAuditor(project_root)
|
|
85
|
+
report = auditor.audit(target_path)
|
|
86
|
+
|
|
87
|
+
output = {
|
|
88
|
+
"agent_role": agent.role,
|
|
89
|
+
"total_issues": report.total_issues,
|
|
90
|
+
"critical_issues": report.critical_count, # Match workflow field name
|
|
91
|
+
"high_issues": report.high_count, # Match workflow field name
|
|
92
|
+
"medium_issues": report.medium_count, # Match workflow field name
|
|
93
|
+
"passed": report.passed,
|
|
94
|
+
"issues_by_file": report.issues_by_file,
|
|
95
|
+
}
|
|
96
|
+
success = report.passed
|
|
97
|
+
confidence = 1.0 if report.total_issues == 0 else 0.7
|
|
98
|
+
|
|
99
|
+
elif agent.id == "test_coverage_analyzer" or "coverage" in agent.role.lower():
|
|
100
|
+
analyzer = RealCoverageAnalyzer(project_root)
|
|
101
|
+
report = analyzer.analyze() # Analyzes all packages automatically
|
|
102
|
+
|
|
103
|
+
output = {
|
|
104
|
+
"agent_role": agent.role,
|
|
105
|
+
"coverage_percent": report.total_coverage, # Match workflow field name
|
|
106
|
+
"total_coverage": report.total_coverage, # Keep for compatibility
|
|
107
|
+
"files_analyzed": report.files_analyzed,
|
|
108
|
+
"uncovered_files": report.uncovered_files,
|
|
109
|
+
"passed": report.total_coverage >= 80.0,
|
|
110
|
+
}
|
|
111
|
+
success = report.total_coverage >= 80.0
|
|
112
|
+
confidence = min(report.total_coverage / 100.0, 1.0)
|
|
113
|
+
|
|
114
|
+
elif agent.id == "code_reviewer" or "quality" in agent.role.lower():
|
|
115
|
+
analyzer = RealCodeQualityAnalyzer(project_root)
|
|
116
|
+
report = analyzer.analyze(target_path)
|
|
117
|
+
|
|
118
|
+
output = {
|
|
119
|
+
"agent_role": agent.role,
|
|
120
|
+
"quality_score": report.quality_score,
|
|
121
|
+
"ruff_issues": report.ruff_issues,
|
|
122
|
+
"mypy_issues": report.mypy_issues,
|
|
123
|
+
"total_files": report.total_files,
|
|
124
|
+
"passed": report.passed,
|
|
125
|
+
}
|
|
126
|
+
success = report.passed
|
|
127
|
+
confidence = report.quality_score / 10.0
|
|
128
|
+
|
|
129
|
+
elif agent.id == "documentation_writer" or "documentation" in agent.role.lower():
|
|
130
|
+
analyzer = RealDocumentationAnalyzer(project_root)
|
|
131
|
+
report = analyzer.analyze(target_path)
|
|
132
|
+
|
|
133
|
+
output = {
|
|
134
|
+
"agent_role": agent.role,
|
|
135
|
+
"completeness": report.completeness_percentage,
|
|
136
|
+
"coverage_percent": report.completeness_percentage, # Match Release Prep field name
|
|
137
|
+
"total_functions": report.total_functions,
|
|
138
|
+
"documented_functions": report.documented_functions,
|
|
139
|
+
"total_classes": report.total_classes,
|
|
140
|
+
"documented_classes": report.documented_classes,
|
|
141
|
+
"missing_docstrings": report.missing_docstrings,
|
|
142
|
+
"passed": report.passed,
|
|
143
|
+
}
|
|
144
|
+
success = report.passed
|
|
145
|
+
confidence = report.completeness_percentage / 100.0
|
|
146
|
+
|
|
147
|
+
elif agent.id == "performance_optimizer" or "performance" in agent.role.lower():
|
|
148
|
+
# Performance analysis placeholder - mark as passed for now
|
|
149
|
+
# TODO: Implement real performance profiling
|
|
150
|
+
logger.warning("Performance analysis not yet implemented, returning placeholder")
|
|
151
|
+
output = {
|
|
152
|
+
"agent_role": agent.role,
|
|
153
|
+
"message": "Performance analysis not yet implemented",
|
|
154
|
+
"passed": True,
|
|
155
|
+
"placeholder": True,
|
|
156
|
+
}
|
|
157
|
+
success = True
|
|
158
|
+
confidence = 1.0
|
|
159
|
+
|
|
160
|
+
elif agent.id == "test_generator":
|
|
161
|
+
# Test generation requires different handling (LLM-based)
|
|
162
|
+
logger.info("Test generation requires manual invocation, returning placeholder")
|
|
163
|
+
output = {
|
|
164
|
+
"agent_role": agent.role,
|
|
165
|
+
"message": "Test generation requires manual invocation",
|
|
166
|
+
"passed": True,
|
|
167
|
+
}
|
|
168
|
+
success = True
|
|
169
|
+
confidence = 0.8
|
|
170
|
+
|
|
171
|
+
else:
|
|
172
|
+
# Unknown agent type - log warning and return placeholder
|
|
173
|
+
logger.warning(f"Unknown agent type: {agent.id}, returning placeholder")
|
|
174
|
+
output = {
|
|
175
|
+
"agent_role": agent.role,
|
|
176
|
+
"agent_id": agent.id,
|
|
177
|
+
"message": "Unknown agent type - no real implementation",
|
|
178
|
+
"passed": True,
|
|
179
|
+
}
|
|
180
|
+
success = True
|
|
181
|
+
confidence = 0.5
|
|
182
|
+
|
|
183
|
+
duration = time.perf_counter() - start_time
|
|
184
|
+
|
|
185
|
+
logger.info(
|
|
186
|
+
f"Agent {agent.id} completed: success={success}, "
|
|
187
|
+
f"confidence={confidence:.2f}, duration={duration:.2f}s"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
return AgentResult(
|
|
191
|
+
agent_id=agent.id,
|
|
192
|
+
success=success,
|
|
193
|
+
output=output,
|
|
194
|
+
confidence=confidence,
|
|
195
|
+
duration_seconds=duration,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
duration = time.perf_counter() - start_time
|
|
200
|
+
logger.error(f"Agent {agent.id} failed: {e}")
|
|
201
|
+
|
|
202
|
+
return AgentResult(
|
|
203
|
+
agent_id=agent.id,
|
|
204
|
+
success=False,
|
|
205
|
+
output={"agent_role": agent.role, "error_details": str(e)},
|
|
206
|
+
error=str(e),
|
|
207
|
+
confidence=0.0,
|
|
208
|
+
duration_seconds=duration,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def _aggregate_results(self, results: list[AgentResult]) -> dict[str, Any]:
|
|
212
|
+
"""Aggregate results from multiple agents.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
results: List of agent results
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Aggregated output dictionary
|
|
219
|
+
"""
|
|
220
|
+
return {
|
|
221
|
+
"num_agents": len(results),
|
|
222
|
+
"all_succeeded": all(r.success for r in results),
|
|
223
|
+
"avg_confidence": (
|
|
224
|
+
sum(r.confidence for r in results) / len(results) if results else 0.0
|
|
225
|
+
),
|
|
226
|
+
"outputs": [r.output for r in results],
|
|
227
|
+
}
|