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,479 @@
|
|
|
1
|
+
"""Core execution strategies for agent composition.
|
|
2
|
+
|
|
3
|
+
This module contains the 6 foundational strategies 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
|
+
|
|
11
|
+
Security:
|
|
12
|
+
- All agent outputs validated before passing to next agent
|
|
13
|
+
- No eval() or exec() usage
|
|
14
|
+
- Timeout enforcement at strategy level
|
|
15
|
+
|
|
16
|
+
Copyright 2025 Smart-AI-Memory
|
|
17
|
+
Licensed under Fair Source License 0.9
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import logging
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
|
+
|
|
26
|
+
from .base import ExecutionStrategy
|
|
27
|
+
from .data_classes import AgentResult, StrategyResult
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from ..agent_templates import AgentTemplate
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SequentialStrategy(ExecutionStrategy):
|
|
36
|
+
"""Sequential composition (A → B → C).
|
|
37
|
+
|
|
38
|
+
Executes agents one after another, passing results forward.
|
|
39
|
+
Each agent receives output from previous agent in context.
|
|
40
|
+
|
|
41
|
+
Use when:
|
|
42
|
+
- Tasks must be done in order
|
|
43
|
+
- Each step depends on previous results
|
|
44
|
+
- Pipeline processing needed
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
Coverage Analyzer → Test Generator → Quality Validator
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
51
|
+
"""Execute agents sequentially.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
agents: List of agents to execute in order
|
|
55
|
+
context: Initial context
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
StrategyResult with sequential execution results
|
|
59
|
+
"""
|
|
60
|
+
if not agents:
|
|
61
|
+
raise ValueError("agents list cannot be empty")
|
|
62
|
+
|
|
63
|
+
logger.info(f"Sequential execution of {len(agents)} agents")
|
|
64
|
+
|
|
65
|
+
results: list[AgentResult] = []
|
|
66
|
+
current_context = context.copy()
|
|
67
|
+
total_duration = 0.0
|
|
68
|
+
|
|
69
|
+
for agent in agents:
|
|
70
|
+
try:
|
|
71
|
+
result = await self._execute_agent(agent, current_context)
|
|
72
|
+
results.append(result)
|
|
73
|
+
total_duration += result.duration_seconds
|
|
74
|
+
|
|
75
|
+
# Pass output to next agent's context
|
|
76
|
+
if result.success:
|
|
77
|
+
current_context[f"{agent.id}_output"] = result.output
|
|
78
|
+
else:
|
|
79
|
+
logger.error(f"Agent {agent.id} failed: {result.error}")
|
|
80
|
+
# Continue or stop based on error handling policy
|
|
81
|
+
# For now: continue to next agent
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.exception(f"Error executing agent {agent.id}: {e}")
|
|
85
|
+
results.append(
|
|
86
|
+
AgentResult(
|
|
87
|
+
agent_id=agent.id,
|
|
88
|
+
success=False,
|
|
89
|
+
output={},
|
|
90
|
+
error=str(e),
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return StrategyResult(
|
|
95
|
+
success=all(r.success for r in results),
|
|
96
|
+
outputs=results,
|
|
97
|
+
aggregated_output=self._aggregate_results(results),
|
|
98
|
+
total_duration=total_duration,
|
|
99
|
+
errors=[r.error for r in results if not r.success],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ParallelStrategy(ExecutionStrategy):
|
|
104
|
+
"""Parallel composition (A || B || C).
|
|
105
|
+
|
|
106
|
+
Executes all agents simultaneously, aggregates results.
|
|
107
|
+
Each agent receives same initial context.
|
|
108
|
+
|
|
109
|
+
Use when:
|
|
110
|
+
- Independent validations needed
|
|
111
|
+
- Multi-perspective review desired
|
|
112
|
+
- Time optimization important
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
Security Audit || Performance Check || Code Quality || Docs Check
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
119
|
+
"""Execute agents in parallel.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
agents: List of agents to execute concurrently
|
|
123
|
+
context: Initial context for all agents
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
StrategyResult with parallel execution results
|
|
127
|
+
"""
|
|
128
|
+
if not agents:
|
|
129
|
+
raise ValueError("agents list cannot be empty")
|
|
130
|
+
|
|
131
|
+
logger.info(f"Parallel execution of {len(agents)} agents")
|
|
132
|
+
|
|
133
|
+
# Execute all agents concurrently
|
|
134
|
+
tasks = [self._execute_agent(agent, context) for agent in agents]
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.exception(f"Error in parallel execution: {e}")
|
|
140
|
+
raise
|
|
141
|
+
|
|
142
|
+
# Process results (handle exceptions)
|
|
143
|
+
processed_results: list[AgentResult] = []
|
|
144
|
+
for i, result in enumerate(results):
|
|
145
|
+
if isinstance(result, Exception):
|
|
146
|
+
logger.error(f"Agent {agents[i].id} raised exception: {result}")
|
|
147
|
+
processed_results.append(
|
|
148
|
+
AgentResult(
|
|
149
|
+
agent_id=agents[i].id,
|
|
150
|
+
success=False,
|
|
151
|
+
output={},
|
|
152
|
+
error=str(result),
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
# Type checker doesn't know we already filtered out exceptions
|
|
157
|
+
assert isinstance(result, AgentResult)
|
|
158
|
+
processed_results.append(result)
|
|
159
|
+
|
|
160
|
+
total_duration = max((r.duration_seconds for r in processed_results), default=0.0)
|
|
161
|
+
|
|
162
|
+
return StrategyResult(
|
|
163
|
+
success=all(r.success for r in processed_results),
|
|
164
|
+
outputs=processed_results,
|
|
165
|
+
aggregated_output=self._aggregate_results(processed_results),
|
|
166
|
+
total_duration=total_duration,
|
|
167
|
+
errors=[r.error for r in processed_results if not r.success],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class DebateStrategy(ExecutionStrategy):
|
|
172
|
+
"""Debate/Consensus composition (A ⇄ B ⇄ C → Synthesis).
|
|
173
|
+
|
|
174
|
+
Agents provide independent opinions, then a synthesizer
|
|
175
|
+
aggregates and resolves conflicts.
|
|
176
|
+
|
|
177
|
+
Use when:
|
|
178
|
+
- Multiple expert opinions needed
|
|
179
|
+
- Architecture decisions require debate
|
|
180
|
+
- Tradeoff analysis needed
|
|
181
|
+
|
|
182
|
+
Example:
|
|
183
|
+
Architect(scale) || Architect(cost) || Architect(simplicity) → Synthesizer
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
187
|
+
"""Execute debate pattern.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
agents: List of agents to debate (recommend 2-4)
|
|
191
|
+
context: Initial context
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
StrategyResult with synthesized consensus
|
|
195
|
+
"""
|
|
196
|
+
if not agents:
|
|
197
|
+
raise ValueError("agents list cannot be empty")
|
|
198
|
+
|
|
199
|
+
if len(agents) < 2:
|
|
200
|
+
logger.warning("Debate pattern works best with 2+ agents")
|
|
201
|
+
|
|
202
|
+
logger.info(f"Debate execution with {len(agents)} agents")
|
|
203
|
+
|
|
204
|
+
# Phase 1: Parallel execution for independent opinions
|
|
205
|
+
parallel_strategy = ParallelStrategy()
|
|
206
|
+
phase1_result = await parallel_strategy.execute(agents, context)
|
|
207
|
+
|
|
208
|
+
# Phase 2: Synthesis (simplified - no actual synthesizer agent)
|
|
209
|
+
# In production: would use dedicated synthesizer agent
|
|
210
|
+
synthesis = {
|
|
211
|
+
"debate_participants": [r.agent_id for r in phase1_result.outputs],
|
|
212
|
+
"opinions": [r.output for r in phase1_result.outputs],
|
|
213
|
+
"consensus": self._synthesize_opinions(phase1_result.outputs),
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return StrategyResult(
|
|
217
|
+
success=phase1_result.success,
|
|
218
|
+
outputs=phase1_result.outputs,
|
|
219
|
+
aggregated_output=synthesis,
|
|
220
|
+
total_duration=phase1_result.total_duration,
|
|
221
|
+
errors=phase1_result.errors,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _synthesize_opinions(self, results: list[AgentResult]) -> dict[str, Any]:
|
|
225
|
+
"""Synthesize multiple agent opinions into consensus.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
results: Agent results to synthesize
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Synthesized consensus
|
|
232
|
+
"""
|
|
233
|
+
# Simplified synthesis: majority vote on success
|
|
234
|
+
success_votes = sum(1 for r in results if r.success)
|
|
235
|
+
consensus_reached = success_votes > len(results) / 2
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
"consensus_reached": consensus_reached,
|
|
239
|
+
"success_votes": success_votes,
|
|
240
|
+
"total_votes": len(results),
|
|
241
|
+
"avg_confidence": (
|
|
242
|
+
sum(r.confidence for r in results) / len(results) if results else 0.0
|
|
243
|
+
),
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class TeachingStrategy(ExecutionStrategy):
|
|
248
|
+
"""Teaching/Validation (Junior → Expert Review).
|
|
249
|
+
|
|
250
|
+
Junior agent attempts task (cheap tier), expert validates.
|
|
251
|
+
If validation fails, expert takes over.
|
|
252
|
+
|
|
253
|
+
Use when:
|
|
254
|
+
- Cost-effective generation desired
|
|
255
|
+
- Quality assurance critical
|
|
256
|
+
- Simple tasks with review needed
|
|
257
|
+
|
|
258
|
+
Example:
|
|
259
|
+
Junior Writer(CHEAP) → Quality Gate → (pass ? done : Expert Review(CAPABLE))
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(self, quality_threshold: float = 0.7):
|
|
263
|
+
"""Initialize teaching strategy.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
quality_threshold: Minimum confidence for junior to pass (0-1)
|
|
267
|
+
"""
|
|
268
|
+
self.quality_threshold = quality_threshold
|
|
269
|
+
|
|
270
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
271
|
+
"""Execute teaching pattern.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
agents: [junior_agent, expert_agent] (exactly 2)
|
|
275
|
+
context: Initial context
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
StrategyResult with teaching outcome
|
|
279
|
+
"""
|
|
280
|
+
if len(agents) != 2:
|
|
281
|
+
raise ValueError("Teaching strategy requires exactly 2 agents")
|
|
282
|
+
|
|
283
|
+
junior, expert = agents
|
|
284
|
+
logger.info(f"Teaching: {junior.id} → {expert.id} validation")
|
|
285
|
+
|
|
286
|
+
results: list[AgentResult] = []
|
|
287
|
+
total_duration = 0.0
|
|
288
|
+
|
|
289
|
+
# Phase 1: Junior attempt
|
|
290
|
+
junior_result = await self._execute_agent(junior, context)
|
|
291
|
+
results.append(junior_result)
|
|
292
|
+
total_duration += junior_result.duration_seconds
|
|
293
|
+
|
|
294
|
+
# Phase 2: Quality gate
|
|
295
|
+
if junior_result.success and junior_result.confidence >= self.quality_threshold:
|
|
296
|
+
logger.info(f"Junior passed quality gate (confidence={junior_result.confidence:.2f})")
|
|
297
|
+
aggregated = {"outcome": "junior_success", "junior_output": junior_result.output}
|
|
298
|
+
else:
|
|
299
|
+
logger.info(
|
|
300
|
+
f"Junior failed quality gate, expert taking over "
|
|
301
|
+
f"(confidence={junior_result.confidence:.2f})"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Phase 3: Expert takeover
|
|
305
|
+
expert_context = context.copy()
|
|
306
|
+
expert_context["junior_attempt"] = junior_result.output
|
|
307
|
+
expert_result = await self._execute_agent(expert, expert_context)
|
|
308
|
+
results.append(expert_result)
|
|
309
|
+
total_duration += expert_result.duration_seconds
|
|
310
|
+
|
|
311
|
+
aggregated = {
|
|
312
|
+
"outcome": "expert_takeover",
|
|
313
|
+
"junior_output": junior_result.output,
|
|
314
|
+
"expert_output": expert_result.output,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return StrategyResult(
|
|
318
|
+
success=all(r.success for r in results),
|
|
319
|
+
outputs=results,
|
|
320
|
+
aggregated_output=aggregated,
|
|
321
|
+
total_duration=total_duration,
|
|
322
|
+
errors=[r.error for r in results if not r.success],
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class RefinementStrategy(ExecutionStrategy):
|
|
327
|
+
"""Progressive Refinement (Draft → Review → Polish).
|
|
328
|
+
|
|
329
|
+
Iterative improvement through multiple quality levels.
|
|
330
|
+
Each agent refines output from previous stage.
|
|
331
|
+
|
|
332
|
+
Use when:
|
|
333
|
+
- Iterative improvement needed
|
|
334
|
+
- Quality ladder desired
|
|
335
|
+
- Multi-stage refinement beneficial
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
Drafter(CHEAP) → Reviewer(CAPABLE) → Polisher(PREMIUM)
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
342
|
+
"""Execute refinement pattern.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
agents: [drafter, reviewer, polisher] (3+ agents)
|
|
346
|
+
context: Initial context
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
StrategyResult with refined output
|
|
350
|
+
"""
|
|
351
|
+
if len(agents) < 2:
|
|
352
|
+
raise ValueError("Refinement strategy requires at least 2 agents")
|
|
353
|
+
|
|
354
|
+
logger.info(f"Refinement with {len(agents)} stages")
|
|
355
|
+
|
|
356
|
+
results: list[AgentResult] = []
|
|
357
|
+
current_context = context.copy()
|
|
358
|
+
total_duration = 0.0
|
|
359
|
+
|
|
360
|
+
for i, agent in enumerate(agents):
|
|
361
|
+
stage_name = f"stage_{i + 1}"
|
|
362
|
+
logger.info(f"Refinement {stage_name}: {agent.id}")
|
|
363
|
+
|
|
364
|
+
result = await self._execute_agent(agent, current_context)
|
|
365
|
+
results.append(result)
|
|
366
|
+
total_duration += result.duration_seconds
|
|
367
|
+
|
|
368
|
+
if result.success:
|
|
369
|
+
# Pass refined output to next stage
|
|
370
|
+
current_context[f"{stage_name}_output"] = result.output
|
|
371
|
+
current_context["previous_output"] = result.output
|
|
372
|
+
else:
|
|
373
|
+
logger.error(f"Refinement stage {i + 1} failed: {result.error}")
|
|
374
|
+
break # Stop refinement on failure
|
|
375
|
+
|
|
376
|
+
# Final output is from last successful stage
|
|
377
|
+
final_output = results[-1].output if results[-1].success else {}
|
|
378
|
+
|
|
379
|
+
return StrategyResult(
|
|
380
|
+
success=all(r.success for r in results),
|
|
381
|
+
outputs=results,
|
|
382
|
+
aggregated_output={
|
|
383
|
+
"refinement_stages": len(results),
|
|
384
|
+
"final_output": final_output,
|
|
385
|
+
"stage_outputs": [r.output for r in results],
|
|
386
|
+
},
|
|
387
|
+
total_duration=total_duration,
|
|
388
|
+
errors=[r.error for r in results if not r.success],
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class AdaptiveStrategy(ExecutionStrategy):
|
|
393
|
+
"""Adaptive Routing (Classifier → Specialist).
|
|
394
|
+
|
|
395
|
+
Classifier assesses task complexity, routes to appropriate specialist.
|
|
396
|
+
Right-sizing: match agent tier to task needs.
|
|
397
|
+
|
|
398
|
+
Use when:
|
|
399
|
+
- Variable task complexity
|
|
400
|
+
- Cost optimization desired
|
|
401
|
+
- Right-sizing important
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
Classifier(CHEAP) → route(simple|moderate|complex) → Specialist(tier)
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
async def execute(self, agents: list[AgentTemplate], context: dict[str, Any]) -> StrategyResult:
|
|
408
|
+
"""Execute adaptive routing pattern.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
agents: [classifier, *specialists] (2+ agents)
|
|
412
|
+
context: Initial context
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
StrategyResult with routed execution
|
|
416
|
+
"""
|
|
417
|
+
if len(agents) < 2:
|
|
418
|
+
raise ValueError("Adaptive strategy requires at least 2 agents")
|
|
419
|
+
|
|
420
|
+
classifier = agents[0]
|
|
421
|
+
specialists = agents[1:]
|
|
422
|
+
|
|
423
|
+
logger.info(f"Adaptive: {classifier.id} → {len(specialists)} specialists")
|
|
424
|
+
|
|
425
|
+
results: list[AgentResult] = []
|
|
426
|
+
total_duration = 0.0
|
|
427
|
+
|
|
428
|
+
# Phase 1: Classification
|
|
429
|
+
classifier_result = await self._execute_agent(classifier, context)
|
|
430
|
+
results.append(classifier_result)
|
|
431
|
+
total_duration += classifier_result.duration_seconds
|
|
432
|
+
|
|
433
|
+
if not classifier_result.success:
|
|
434
|
+
logger.error("Classifier failed, defaulting to first specialist")
|
|
435
|
+
selected_specialist = specialists[0]
|
|
436
|
+
else:
|
|
437
|
+
# Phase 2: Route to specialist based on classification
|
|
438
|
+
# Simplified: select based on confidence score
|
|
439
|
+
if classifier_result.confidence > 0.8:
|
|
440
|
+
# High confidence → simple task → cheap specialist
|
|
441
|
+
selected_specialist = min(
|
|
442
|
+
specialists,
|
|
443
|
+
key=lambda s: {
|
|
444
|
+
"CHEAP": 0,
|
|
445
|
+
"CAPABLE": 1,
|
|
446
|
+
"PREMIUM": 2,
|
|
447
|
+
}.get(s.tier_preference, 1),
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
# Low confidence → complex task → premium specialist
|
|
451
|
+
selected_specialist = max(
|
|
452
|
+
specialists,
|
|
453
|
+
key=lambda s: {
|
|
454
|
+
"CHEAP": 0,
|
|
455
|
+
"CAPABLE": 1,
|
|
456
|
+
"PREMIUM": 2,
|
|
457
|
+
}.get(s.tier_preference, 1),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
logger.info(f"Routed to specialist: {selected_specialist.id}")
|
|
461
|
+
|
|
462
|
+
# Phase 3: Execute selected specialist
|
|
463
|
+
specialist_context = context.copy()
|
|
464
|
+
specialist_context["classification"] = classifier_result.output
|
|
465
|
+
specialist_result = await self._execute_agent(selected_specialist, specialist_context)
|
|
466
|
+
results.append(specialist_result)
|
|
467
|
+
total_duration += specialist_result.duration_seconds
|
|
468
|
+
|
|
469
|
+
return StrategyResult(
|
|
470
|
+
success=all(r.success for r in results),
|
|
471
|
+
outputs=results,
|
|
472
|
+
aggregated_output={
|
|
473
|
+
"classification": classifier_result.output,
|
|
474
|
+
"selected_specialist": selected_specialist.id,
|
|
475
|
+
"specialist_output": specialist_result.output,
|
|
476
|
+
},
|
|
477
|
+
total_duration=total_duration,
|
|
478
|
+
errors=[r.error for r in results if not r.success],
|
|
479
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Data classes for execution strategy results.
|
|
2
|
+
|
|
3
|
+
This module contains the core data structures used across all execution strategies:
|
|
4
|
+
- AgentResult: Result from individual agent execution
|
|
5
|
+
- StrategyResult: Aggregated result from strategy execution
|
|
6
|
+
|
|
7
|
+
Extracted from execution_strategies.py for improved maintainability.
|
|
8
|
+
|
|
9
|
+
Copyright 2025 Smart-AI-Memory
|
|
10
|
+
Licensed under Fair Source License 0.9
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AgentResult:
|
|
21
|
+
"""Result from agent execution.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
agent_id: ID of agent that produced result
|
|
25
|
+
success: Whether execution succeeded
|
|
26
|
+
output: Agent output data
|
|
27
|
+
confidence: Confidence score (0-1)
|
|
28
|
+
duration_seconds: Execution time
|
|
29
|
+
error: Error message if failed
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
agent_id: str
|
|
33
|
+
success: bool
|
|
34
|
+
output: dict[str, Any]
|
|
35
|
+
confidence: float = 0.0
|
|
36
|
+
duration_seconds: float = 0.0
|
|
37
|
+
error: str = ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class StrategyResult:
|
|
42
|
+
"""Aggregated result from strategy execution.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
success: Whether overall execution succeeded
|
|
46
|
+
outputs: List of individual agent results
|
|
47
|
+
aggregated_output: Combined/synthesized output
|
|
48
|
+
total_duration: Total execution time
|
|
49
|
+
errors: List of errors encountered
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
success: bool
|
|
53
|
+
outputs: list[AgentResult]
|
|
54
|
+
aggregated_output: dict[str, Any]
|
|
55
|
+
total_duration: float = 0.0
|
|
56
|
+
errors: list[str] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
def __post_init__(self):
|
|
59
|
+
"""Initialize errors list if None."""
|
|
60
|
+
if not self.errors:
|
|
61
|
+
self.errors = []
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = ["AgentResult", "StrategyResult"]
|