strands-swarms 0.1.1__py3-none-any.whl → 0.1.2__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.
strands_swarms/events.py DELETED
@@ -1,563 +0,0 @@
1
- """Event types for DynamicSwarm execution.
2
-
3
- This module extends strands' hook system with events specific to dynamic
4
- swarm planning and execution. Events follow the same patterns as
5
- strands.hooks.events for consistency.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import re
11
- from dataclasses import dataclass, field
12
- from typing import Any, Callable
13
-
14
- from strands.hooks.registry import BaseHookEvent, HookProvider, HookRegistry
15
-
16
- # =============================================================================
17
- # Planning Events
18
- # =============================================================================
19
-
20
-
21
- @dataclass
22
- class SwarmStartedEvent(BaseHookEvent):
23
- """Event triggered when a dynamic swarm begins execution.
24
-
25
- Attributes:
26
- query: The user's input query.
27
- available_tools: List of tool names available to spawned agents.
28
- available_models: List of model names available to spawned agents.
29
- """
30
-
31
- query: str
32
- available_tools: list[str] = field(default_factory=list)
33
- available_models: list[str] = field(default_factory=list)
34
-
35
-
36
- @dataclass
37
- class PlanningStartedEvent(BaseHookEvent):
38
- """Event triggered when the planning phase begins."""
39
-
40
- pass
41
-
42
-
43
- @dataclass
44
- class AgentSpawnedEvent(BaseHookEvent):
45
- """Event triggered when a new agent is dynamically spawned.
46
-
47
- Attributes:
48
- name: The agent's unique identifier.
49
- role: The agent's role description.
50
- instructions: Additional instructions for the agent.
51
- tools: List of tool names assigned to this agent.
52
- model: Model name assigned to this agent.
53
- color: ANSI color code assigned to this agent.
54
- """
55
-
56
- name: str
57
- role: str
58
- instructions: str | None = None
59
- tools: list[str] = field(default_factory=list)
60
- model: str | None = None
61
- color: str | None = None # ANSI color code for consistent display
62
-
63
-
64
- @dataclass
65
- class TaskCreatedEvent(BaseHookEvent):
66
- """Event triggered when a new task is created.
67
-
68
- Attributes:
69
- name: The task's unique identifier.
70
- agent: Name of the agent assigned to this task.
71
- description: Description of what the task does.
72
- depends_on: List of task names this task depends on.
73
- """
74
-
75
- name: str
76
- agent: str
77
- description: str | None = None
78
- depends_on: list[str] = field(default_factory=list)
79
-
80
-
81
- @dataclass
82
- class AgentInfo:
83
- """Info about a spawned agent for summary display."""
84
- name: str
85
- role: str
86
- tools: list[str]
87
- model: str | None
88
- color: str | None = None
89
-
90
-
91
- @dataclass
92
- class TaskInfo:
93
- """Info about a created task for summary display."""
94
- name: str
95
- agent: str
96
- description: str | None
97
- depends_on: list[str]
98
-
99
-
100
- @dataclass
101
- class PlanningCompletedEvent(BaseHookEvent):
102
- """Event triggered when planning phase completes.
103
-
104
- Attributes:
105
- entry_task: The entry point task, if specified.
106
- agents: List of agent info for summary display.
107
- tasks: List of task info for summary display.
108
- """
109
-
110
- entry_task: str | None = None
111
- agents: list[AgentInfo] = field(default_factory=list)
112
- tasks: list[TaskInfo] = field(default_factory=list)
113
-
114
-
115
- # =============================================================================
116
- # Execution Events
117
- # =============================================================================
118
-
119
-
120
- @dataclass
121
- class ExecutionStartedEvent(BaseHookEvent):
122
- """Event triggered when the execution phase begins.
123
-
124
- Attributes:
125
- tasks: List of task names to execute.
126
- """
127
-
128
- tasks: list[str] = field(default_factory=list)
129
-
130
-
131
- @dataclass
132
- class TaskStartedEvent(BaseHookEvent):
133
- """Event triggered when a task begins executing.
134
-
135
- Attributes:
136
- name: The task name.
137
- agent_role: Role of the agent executing this task.
138
- """
139
-
140
- name: str
141
- agent_role: str | None = None
142
-
143
-
144
- @dataclass
145
- class TaskCompletedEvent(BaseHookEvent):
146
- """Event triggered when a task completes successfully.
147
-
148
- Attributes:
149
- name: The task name.
150
- """
151
-
152
- name: str
153
-
154
- @property
155
- def should_reverse_callbacks(self) -> bool:
156
- """Cleanup events should reverse callback order."""
157
- return True
158
-
159
-
160
- @dataclass
161
- class TaskFailedEvent(BaseHookEvent):
162
- """Event triggered when a task fails.
163
-
164
- Attributes:
165
- name: The task name.
166
- error: Error message or description.
167
- """
168
-
169
- name: str
170
- error: str | None = None
171
-
172
- @property
173
- def should_reverse_callbacks(self) -> bool:
174
- """Cleanup events should reverse callback order."""
175
- return True
176
-
177
-
178
- @dataclass
179
- class ExecutionCompletedEvent(BaseHookEvent):
180
- """Event triggered when execution phase completes.
181
-
182
- Attributes:
183
- status: Final execution status.
184
- agent_count: Number of agents used.
185
- task_count: Number of tasks completed.
186
- """
187
-
188
- status: str
189
- agent_count: int = 0
190
- task_count: int = 0
191
-
192
- @property
193
- def should_reverse_callbacks(self) -> bool:
194
- """Cleanup events should reverse callback order."""
195
- return True
196
-
197
-
198
- @dataclass
199
- class SwarmCompletedEvent(BaseHookEvent):
200
- """Event triggered when swarm completes successfully."""
201
-
202
- pass
203
-
204
- @property
205
- def should_reverse_callbacks(self) -> bool:
206
- """Cleanup events should reverse callback order."""
207
- return True
208
-
209
-
210
- @dataclass
211
- class SwarmFailedEvent(BaseHookEvent):
212
- """Event triggered when swarm fails.
213
-
214
- Attributes:
215
- error: Error message or description.
216
- """
217
-
218
- error: str
219
-
220
- @property
221
- def should_reverse_callbacks(self) -> bool:
222
- """Cleanup events should reverse callback order."""
223
- return True
224
-
225
-
226
- # =============================================================================
227
- # Default Hook Provider
228
- # =============================================================================
229
-
230
- # ANSI Color Constants (for consistent agent output coloring)
231
- AGENT_COLORS = [
232
- "\033[94m", # Blue
233
- "\033[92m", # Green
234
- "\033[93m", # Yellow
235
- "\033[95m", # Magenta
236
- "\033[96m", # Cyan
237
- "\033[91m", # Red
238
- ]
239
- RESET = "\033[0m"
240
- BOLD = "\033[1m"
241
- DIM = "\033[2m"
242
-
243
-
244
- # =============================================================================
245
- # Colored Callback Handler
246
- # =============================================================================
247
-
248
-
249
- # Patterns for internal LLM reasoning tags that should be filtered from output
250
- _INTERNAL_TAG_PATTERN = re.compile(
251
- r'<(/?)(?:thinking|result|task_quality_reflection|task_quality_score|'
252
- r'search_quality_reflection|search_quality_score|reflection|score)>',
253
- re.IGNORECASE
254
- )
255
-
256
-
257
- def _filter_internal_tags(text: str) -> str:
258
- """Filter out internal LLM reasoning tags from streamed text.
259
-
260
- These tags are generated by the LLM for internal reasoning but shouldn't
261
- be displayed to users in verbose output.
262
- """
263
- return _INTERNAL_TAG_PATTERN.sub('', text)
264
-
265
-
266
- def create_colored_callback_handler(color: str, agent_name: str) -> Callable[..., None]:
267
- """Create a callback handler that prints agent output with a specific color.
268
-
269
- This ensures all output from a specific agent (text, reasoning, tool calls)
270
- is displayed with the same color for easy visual tracking.
271
-
272
- Internal reasoning tags (like <thinking>, <result>, etc.) are automatically
273
- filtered out from the streamed output.
274
-
275
- Args:
276
- color: ANSI color code to use for this agent's output.
277
- agent_name: Name of the agent (for tool call headers).
278
-
279
- Returns:
280
- A callback handler function for the strands Agent.
281
- """
282
- # Track which tool calls we've already printed to avoid duplicates
283
- # (strands fires current_tool_use multiple times as input streams in)
284
- printed_tool_ids: set[str] = set()
285
-
286
- def handler(**kwargs: Any) -> None:
287
- nonlocal printed_tool_ids
288
-
289
- # Text generation events
290
- if "data" in kwargs:
291
- # Filter internal reasoning tags and stream text chunk with color
292
- filtered_text = _filter_internal_tags(kwargs['data'])
293
- if filtered_text:
294
- print(f"{color}{filtered_text}{RESET}", end="", flush=True)
295
-
296
- # Tool usage events - only print once per tool call
297
- elif "current_tool_use" in kwargs:
298
- tool = kwargs["current_tool_use"]
299
- tool_id = tool.get("toolUseId", "")
300
-
301
- # Skip if we've already printed this tool call
302
- if tool_id in printed_tool_ids:
303
- return
304
- printed_tool_ids.add(tool_id)
305
-
306
- tool_name = tool.get("name", "unknown")
307
- # Print tool header with color
308
- print(f"\n{color}{BOLD}Tool: {tool_name}{RESET}")
309
-
310
- # Tool result events - print the input when tool completes
311
- elif "tool_result" in kwargs:
312
- result = kwargs["tool_result"]
313
- tool_id = result.get("toolUseId", "")
314
- tool_input = result.get("input")
315
-
316
- if tool_input:
317
- if isinstance(tool_input, dict):
318
- for key, value in tool_input.items():
319
- # Truncate long values for readability
320
- str_value = str(value)
321
- if len(str_value) > 100:
322
- str_value = str_value[:100] + "..."
323
- print(f"{color} {key}: {str_value}{RESET}")
324
- else:
325
- str_input = str(tool_input)
326
- if len(str_input) > 200:
327
- str_input = str_input[:200] + "..."
328
- print(f"{color} Input: {str_input}{RESET}")
329
-
330
- # Reasoning/thinking events - also filter internal tags
331
- elif "reasoningText" in kwargs:
332
- filtered_text = _filter_internal_tags(kwargs['reasoningText'])
333
- if filtered_text:
334
- print(f"{color}{filtered_text}{RESET}", end="", flush=True)
335
- elif "reasoning" in kwargs:
336
- # Start of reasoning block
337
- pass # The reasoningText will contain the actual content
338
-
339
- return handler
340
-
341
-
342
- class PrintingHookProvider(HookProvider):
343
- """Hook provider that prints formatted output to console.
344
-
345
- This provides a nice CLI experience with emoji indicators and colors.
346
- Use with DynamicSwarm for live status updates.
347
-
348
- Example:
349
- swarm = DynamicSwarm(
350
- ...,
351
- hooks=[PrintingHookProvider()],
352
- )
353
- """
354
-
355
- # Use same colors as agent output for consistency
356
- COLORS = AGENT_COLORS
357
-
358
- def __init__(self, use_colors: bool = True) -> None:
359
- """Initialize the hook provider with state tracking.
360
-
361
- Args:
362
- use_colors: Whether to use ANSI colors for agent differentiation.
363
- """
364
- self._agents_header_printed = False
365
- self._tasks_header_printed = False
366
- self._use_colors = use_colors
367
- self._agent_colors: dict[str, str] = {} # Populated from AgentSpawnedEvent
368
- self._task_agents: dict[str, str] = {} # task_name -> agent_name
369
-
370
- def _get_agent_color(self, agent_name: str | None) -> str:
371
- """Get a consistent color for an agent."""
372
- if not self._use_colors or not agent_name:
373
- return ""
374
- return self._agent_colors.get(agent_name, "")
375
-
376
- def _colored(self, text: str, agent_name: str | None = None, bold: bool = False) -> str:
377
- """Apply color to text."""
378
- if not self._use_colors:
379
- return text
380
- color = self._get_agent_color(agent_name) if agent_name else ""
381
- bold_code = BOLD if bold else ""
382
- return f"{bold_code}{color}{text}{RESET}"
383
-
384
- def _get_task_agent(self, task_name: str) -> str | None:
385
- """Get the agent assigned to a task."""
386
- return self._task_agents.get(task_name)
387
-
388
- def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
389
- """Register all printing callbacks."""
390
- # Planning events
391
- registry.add_callback(SwarmStartedEvent, self._on_swarm_started)
392
- registry.add_callback(PlanningStartedEvent, self._on_planning_started)
393
- registry.add_callback(AgentSpawnedEvent, self._on_agent_spawned)
394
- registry.add_callback(TaskCreatedEvent, self._on_task_created)
395
- registry.add_callback(PlanningCompletedEvent, self._on_planning_completed)
396
-
397
- # Execution events
398
- registry.add_callback(ExecutionStartedEvent, self._on_execution_started)
399
- registry.add_callback(TaskStartedEvent, self._on_task_started)
400
- registry.add_callback(TaskCompletedEvent, self._on_task_completed)
401
- registry.add_callback(TaskFailedEvent, self._on_task_failed)
402
- registry.add_callback(ExecutionCompletedEvent, self._on_execution_completed)
403
-
404
- # Completion events
405
- registry.add_callback(SwarmCompletedEvent, self._on_swarm_completed)
406
- registry.add_callback(SwarmFailedEvent, self._on_swarm_failed)
407
-
408
- def _on_swarm_started(self, event: SwarmStartedEvent) -> None:
409
- # Reset state for new swarm execution
410
- self._agents_header_printed = False
411
- self._tasks_header_printed = False
412
- self._agent_colors.clear()
413
- self._task_agents.clear()
414
-
415
- print("\n" + "=" * 60)
416
- print("🚀 DYNAMIC SWARM STARTING")
417
- print("=" * 60)
418
- query = event.query
419
- print(f"\n📝 Query: {query[:200]}{'...' if len(query) > 200 else ''}")
420
- print(f"📦 Available tools: {event.available_tools or ['none']}")
421
- print(f"🧠 Available models: {event.available_models or ['default']}")
422
-
423
- def _on_planning_started(self, event: PlanningStartedEvent) -> None:
424
- print("\n" + "-" * 40)
425
- print("📐 PHASE 1: PLANNING")
426
- print("-" * 40)
427
-
428
- def _on_agent_spawned(self, event: AgentSpawnedEvent) -> None:
429
- # Store the color from the event (assigned by registry) for later use
430
- # Don't print here - we'll print a clean summary after planning completes
431
- if event.color:
432
- self._agent_colors[event.name] = event.color
433
-
434
- def _on_task_created(self, event: TaskCreatedEvent) -> None:
435
- # Track task-agent mapping for coloring during execution
436
- # Don't print here - we'll print a clean summary after planning completes
437
- self._task_agents[event.name] = event.agent
438
-
439
- def _on_planning_completed(self, event: PlanningCompletedEvent) -> None:
440
- # Print agents summary
441
- if event.agents:
442
- print("\n" + "·" * 40)
443
- print("🤖 AGENTS")
444
- print("·" * 40)
445
- for agent in event.agents:
446
- # Store color for later use
447
- if agent.color:
448
- self._agent_colors[agent.name] = agent.color
449
-
450
- # Color the entire agent block
451
- c = self._colored
452
- print(f"\n {c(f'[{agent.name}]', agent.name, bold=True)}")
453
- print(f" {c('Role:', agent.name, bold=True)} {c(agent.role, agent.name)}")
454
- print(f" {c('Tools:', agent.name, bold=True)} {c(str(agent.tools or ['none']), agent.name)}")
455
- print(f" {c('Model:', agent.name, bold=True)} {c(agent.model or 'default', agent.name)}")
456
-
457
- # Print tasks summary with dependency visualization
458
- if event.tasks:
459
- print("\n" + "·" * 40)
460
- print("📋 TASKS & DEPENDENCIES")
461
- print("·" * 40)
462
- for task in event.tasks:
463
- # Track task-agent mapping
464
- self._task_agents[task.name] = task.agent
465
-
466
- # Color the entire task block with the agent's color
467
- c = self._colored
468
- agent_name = task.agent
469
-
470
- print(f"\n {c(f'[{task.name}]', agent_name, bold=True)}")
471
- print(f" {c('Agent:', agent_name, bold=True)} {c(agent_name, agent_name)}")
472
- if task.description:
473
- print(f" {c('Description:', agent_name, bold=True)} {c(task.description, agent_name)}")
474
- if task.depends_on:
475
- deps_colored = [self._colored(d, self._task_agents.get(d), bold=True) for d in task.depends_on]
476
- print(f" {c('⏳ Waits for:', agent_name, bold=True)} {', '.join(deps_colored)}")
477
- else:
478
- print(f" {c('⚡ Can start immediately', agent_name)}")
479
-
480
- # Print execution summary
481
- print("\n" + "·" * 40)
482
- print("✅ PLAN READY")
483
- print("·" * 40)
484
- print(f" Entry: {event.entry_task or 'auto'}")
485
- print(f" Total: {len(event.agents)} agents, {len(event.tasks)} tasks")
486
-
487
- def _on_execution_started(self, event: ExecutionStartedEvent) -> None:
488
- print("\n" + "-" * 40)
489
- print("⚡ PHASE 2: EXECUTION")
490
- print("-" * 40)
491
- # Color each task name by its agent
492
- tasks_colored = [self._colored(t, self._get_task_agent(t), bold=True) for t in event.tasks]
493
- print(f"📋 Tasks to execute: [{', '.join(tasks_colored)}]")
494
-
495
- def _on_task_started(self, event: TaskStartedEvent) -> None:
496
- agent = self._get_task_agent(event.name)
497
- task_str = self._colored(event.name, agent, bold=True)
498
- print(f"\n▶️ Executing task: {task_str}")
499
- if event.agent_role:
500
- role_str = self._colored(event.agent_role, agent)
501
- print(f" Agent role: {role_str}")
502
-
503
- def _on_task_completed(self, event: TaskCompletedEvent) -> None:
504
- agent = self._get_task_agent(event.name)
505
- task_str = self._colored(event.name, agent)
506
- print(f" ✓ Completed: {task_str}")
507
-
508
- def _on_task_failed(self, event: TaskFailedEvent) -> None:
509
- task_str = self._colored(event.name, self._get_task_agent(event.name))
510
- print(f" ❌ Failed: {task_str}")
511
- if event.error:
512
- print(f" Error: {event.error}")
513
-
514
- def _on_execution_completed(self, event: ExecutionCompletedEvent) -> None:
515
- print("\n" + "-" * 40)
516
- print("🏁 EXECUTION COMPLETE")
517
- print("-" * 40)
518
- print(f" Status: {event.status}")
519
- print(f" Agents used: {event.agent_count}")
520
- print(f" Tasks completed: {event.task_count}")
521
-
522
- def _on_swarm_completed(self, event: SwarmCompletedEvent) -> None:
523
- print("\n" + "=" * 60)
524
- print("✅ SWARM COMPLETED SUCCESSFULLY")
525
- print("=" * 60)
526
-
527
- def _on_swarm_failed(self, event: SwarmFailedEvent) -> None:
528
- print("\n" + "=" * 60)
529
- print(f"❌ SWARM FAILED: {event.error}")
530
- print("=" * 60)
531
-
532
-
533
- # Re-export strands types for convenience
534
- __all__ = [
535
- # Events
536
- "SwarmStartedEvent",
537
- "PlanningStartedEvent",
538
- "AgentSpawnedEvent",
539
- "TaskCreatedEvent",
540
- "PlanningCompletedEvent",
541
- "ExecutionStartedEvent",
542
- "TaskStartedEvent",
543
- "TaskCompletedEvent",
544
- "TaskFailedEvent",
545
- "ExecutionCompletedEvent",
546
- "SwarmCompletedEvent",
547
- "SwarmFailedEvent",
548
- # Data types
549
- "AgentInfo",
550
- "TaskInfo",
551
- # Hook provider
552
- "PrintingHookProvider",
553
- # Callback handler factory
554
- "create_colored_callback_handler",
555
- # Color constants
556
- "AGENT_COLORS",
557
- "RESET",
558
- "BOLD",
559
- "DIM",
560
- # Re-exports from strands
561
- "HookProvider",
562
- "HookRegistry",
563
- ]
strands_swarms/py.typed DELETED
File without changes