puffinflow 2.dev0__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.
Files changed (55) hide show
  1. puffinflow/__init__.py +132 -0
  2. puffinflow/core/__init__.py +110 -0
  3. puffinflow/core/agent/__init__.py +320 -0
  4. puffinflow/core/agent/base.py +1635 -0
  5. puffinflow/core/agent/checkpoint.py +50 -0
  6. puffinflow/core/agent/context.py +521 -0
  7. puffinflow/core/agent/decorators/__init__.py +90 -0
  8. puffinflow/core/agent/decorators/builder.py +454 -0
  9. puffinflow/core/agent/decorators/flexible.py +714 -0
  10. puffinflow/core/agent/decorators/inspection.py +144 -0
  11. puffinflow/core/agent/dependencies.py +57 -0
  12. puffinflow/core/agent/scheduling/__init__.py +21 -0
  13. puffinflow/core/agent/scheduling/builder.py +160 -0
  14. puffinflow/core/agent/scheduling/exceptions.py +35 -0
  15. puffinflow/core/agent/scheduling/inputs.py +137 -0
  16. puffinflow/core/agent/scheduling/parser.py +209 -0
  17. puffinflow/core/agent/scheduling/scheduler.py +413 -0
  18. puffinflow/core/agent/state.py +141 -0
  19. puffinflow/core/config.py +62 -0
  20. puffinflow/core/coordination/__init__.py +137 -0
  21. puffinflow/core/coordination/agent_group.py +359 -0
  22. puffinflow/core/coordination/agent_pool.py +629 -0
  23. puffinflow/core/coordination/agent_team.py +577 -0
  24. puffinflow/core/coordination/coordinator.py +720 -0
  25. puffinflow/core/coordination/deadlock.py +1759 -0
  26. puffinflow/core/coordination/fluent_api.py +421 -0
  27. puffinflow/core/coordination/primitives.py +478 -0
  28. puffinflow/core/coordination/rate_limiter.py +520 -0
  29. puffinflow/core/observability/__init__.py +47 -0
  30. puffinflow/core/observability/agent.py +139 -0
  31. puffinflow/core/observability/alerting.py +73 -0
  32. puffinflow/core/observability/config.py +127 -0
  33. puffinflow/core/observability/context.py +88 -0
  34. puffinflow/core/observability/core.py +147 -0
  35. puffinflow/core/observability/decorators.py +105 -0
  36. puffinflow/core/observability/events.py +71 -0
  37. puffinflow/core/observability/interfaces.py +196 -0
  38. puffinflow/core/observability/metrics.py +137 -0
  39. puffinflow/core/observability/tracing.py +209 -0
  40. puffinflow/core/reliability/__init__.py +27 -0
  41. puffinflow/core/reliability/bulkhead.py +96 -0
  42. puffinflow/core/reliability/circuit_breaker.py +149 -0
  43. puffinflow/core/reliability/leak_detector.py +122 -0
  44. puffinflow/core/resources/__init__.py +77 -0
  45. puffinflow/core/resources/allocation.py +790 -0
  46. puffinflow/core/resources/pool.py +645 -0
  47. puffinflow/core/resources/quotas.py +567 -0
  48. puffinflow/core/resources/requirements.py +217 -0
  49. puffinflow/version.py +21 -0
  50. puffinflow-2.dev0.dist-info/METADATA +334 -0
  51. puffinflow-2.dev0.dist-info/RECORD +55 -0
  52. puffinflow-2.dev0.dist-info/WHEEL +5 -0
  53. puffinflow-2.dev0.dist-info/entry_points.txt +3 -0
  54. puffinflow-2.dev0.dist-info/licenses/LICENSE +21 -0
  55. puffinflow-2.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,421 @@
1
+ """Fluent APIs for intuitive agent coordination."""
2
+
3
+ from collections import defaultdict
4
+ from typing import Any, Optional, Union
5
+
6
+ from ..agent.base import Agent, AgentResult
7
+ from .agent_group import AgentOrchestrator, ExecutionStrategy
8
+ from .agent_team import AgentTeam, TeamResult
9
+
10
+
11
+ class Agents:
12
+ """Fluent API for agent coordination."""
13
+
14
+ def __init__(self) -> None:
15
+ self._agents: list[Agent] = []
16
+ self._stages: list[dict] = []
17
+ self._conditions: list[dict] = []
18
+ self._current_stage_agents: list[Agent] = []
19
+ self._pipeline_mode = False
20
+ self._fan_out_config: Optional[dict] = None
21
+ self._aggregator: Optional[Agent] = None
22
+ self._variables: dict[str, Any] = {}
23
+
24
+ def add(self, agent: Agent) -> "Agents":
25
+ """Add a single agent."""
26
+ self._agents.append(agent)
27
+ self._current_stage_agents.append(agent)
28
+ return self
29
+
30
+ def add_many(self, agents: list[Agent]) -> "Agents":
31
+ """Add multiple agents."""
32
+ self._agents.extend(agents)
33
+ self._current_stage_agents.extend(agents)
34
+ return self
35
+
36
+ def add_parallel(self, agents: list[Agent]) -> "Agents":
37
+ """Add agents to run in parallel."""
38
+ self._stages.append({"type": "parallel", "agents": agents.copy()})
39
+ self._agents.extend(agents)
40
+ return self
41
+
42
+ def then_add(self, agent: Agent) -> "Agents":
43
+ """Add agent for sequential execution."""
44
+ if self._current_stage_agents:
45
+ self._stages.append(
46
+ {"type": "sequential", "agents": self._current_stage_agents.copy()}
47
+ )
48
+ self._current_stage_agents.clear()
49
+
50
+ self._current_stage_agents.append(agent)
51
+ self._agents.append(agent)
52
+ return self
53
+
54
+ def add_stage(self, name: str, agents: list[Agent]) -> "Agents":
55
+ """Add named stage."""
56
+ self._stages.append({"type": "stage", "name": name, "agents": agents.copy()})
57
+ self._agents.extend(agents)
58
+ return self
59
+
60
+ def set_variable_for_all(self, key: str, value: Any) -> "Agents":
61
+ """Set variable for all agents."""
62
+ self._variables[key] = value
63
+ for agent in self._agents:
64
+ agent.set_variable(key, value)
65
+ return self
66
+
67
+ def pipe_variable(self, output_key: str, input_key: str) -> "Agents":
68
+ """Pipe output from one stage to input of next."""
69
+ self._pipeline_mode = True
70
+ # This would be implemented in the execution phase
71
+ return self
72
+
73
+ def fan_out(self, source_stage: str, target_stage: str) -> "Agents":
74
+ """Configure fan-out from one stage to another."""
75
+ self._fan_out_config = {"source": source_stage, "target": target_stage}
76
+ return self
77
+
78
+ def aggregate_with(self, aggregator: Agent) -> "Agents":
79
+ """Set aggregator agent."""
80
+ self._aggregator = aggregator
81
+ return self
82
+
83
+ def if_output(
84
+ self, agent_name: str, key: str, expected_value: Any
85
+ ) -> "ConditionalAgents":
86
+ """Start conditional execution."""
87
+ return ConditionalAgents(self, agent_name, key, expected_value)
88
+
89
+ # Execution methods
90
+ async def run_parallel(self, timeout: Optional[float] = None) -> "FluentResult":
91
+ """Run all agents in parallel."""
92
+ team = AgentTeam("fluent_parallel")
93
+ team.add_agents(self._agents)
94
+
95
+ # Set variables
96
+ for key, value in self._variables.items():
97
+ team.set_global_variable(key, value)
98
+
99
+ result = await team.run_parallel(timeout)
100
+ return FluentResult(result)
101
+
102
+ async def run_sequential(self) -> "FluentResult":
103
+ """Run agents sequentially."""
104
+ team = AgentTeam("fluent_sequential")
105
+ team.add_agents(self._agents)
106
+
107
+ # Set variables
108
+ for key, value in self._variables.items():
109
+ team.set_global_variable(key, value)
110
+
111
+ result = await team.run_sequential()
112
+ return FluentResult(result)
113
+
114
+ async def run_with_coordination(self) -> "FluentResult":
115
+ """Run with advanced coordination."""
116
+ orchestrator = AgentOrchestrator("fluent_orchestration")
117
+ orchestrator.add_agents(self._agents)
118
+
119
+ # Add stages
120
+ for i, stage_config in enumerate(self._stages):
121
+ stage_name = stage_config.get("name", f"stage_{i}")
122
+ agents = stage_config["agents"]
123
+ stage_type = stage_config["type"]
124
+
125
+ strategy = ExecutionStrategy.PARALLEL
126
+ if stage_type == "sequential":
127
+ strategy = ExecutionStrategy.SEQUENTIAL
128
+
129
+ orchestrator.add_stage(stage_name, agents, strategy)
130
+
131
+ # Set variables
132
+ for key, value in self._variables.items():
133
+ orchestrator.set_global_variable(key, value)
134
+
135
+ result = await orchestrator.run()
136
+ return FluentResult(result)
137
+
138
+ async def collect_all(self) -> "FluentResult":
139
+ """Run and collect all results."""
140
+ return await self.run_parallel()
141
+
142
+ async def collect_outputs(self, key: str) -> list[Any]:
143
+ """Collect specific output from all agents."""
144
+ result = await self.run_parallel()
145
+ return result.get_all_outputs(key)
146
+
147
+ async def wait_for_all(self, timeout: Optional[float] = None) -> "FluentResult":
148
+ """Wait for all agents to complete."""
149
+ return await self.run_parallel(timeout)
150
+
151
+ async def get_best_by(self, metric: str, maximize: bool = True) -> AgentResult:
152
+ """Get agent with best metric."""
153
+ result = await self.run_parallel()
154
+ best_result = result.get_best_by(metric, maximize)
155
+ if best_result is None:
156
+ raise ValueError(f"No agent found with metric '{metric}'")
157
+ return best_result
158
+
159
+ async def aggregate_stage_results(self, stage_name: str) -> dict[str, Any]:
160
+ """Aggregate results from a specific stage."""
161
+ result = await self.run_with_coordination()
162
+
163
+ if hasattr(result, "stage_results"):
164
+ stage_result = result.stage_results.get(stage_name)
165
+ if stage_result:
166
+ return {
167
+ "outputs": stage_result.get_all_outputs("result"),
168
+ "metrics": {
169
+ "success_rate": stage_result.success_rate,
170
+ "agent_count": len(stage_result.agent_results),
171
+ },
172
+ }
173
+
174
+ return {}
175
+
176
+ async def get_final_result(self) -> dict[str, Any]:
177
+ """Get final aggregated result."""
178
+ if self._stages:
179
+ result = await self.run_with_coordination()
180
+ if hasattr(result, "get_final_result"):
181
+ final_result = result.get_final_result()
182
+ if isinstance(final_result, dict):
183
+ return final_result
184
+ else:
185
+ return {"result": final_result}
186
+
187
+ team_result = await self.run_parallel()
188
+ return {
189
+ "agent_results": team_result.agent_results,
190
+ "success_rate": team_result.success_rate,
191
+ "total_duration": team_result.total_duration,
192
+ }
193
+
194
+ async def get_aggregated_result(self) -> dict[str, Any]:
195
+ """Get result aggregated by aggregator agent."""
196
+ if self._aggregator:
197
+ # Run main agents first
198
+ main_result = await self.run_parallel()
199
+
200
+ # Collect outputs for aggregator
201
+ all_outputs = []
202
+ for agent_result in main_result.agent_results.values():
203
+ all_outputs.extend(agent_result.outputs.values())
204
+
205
+ # Run aggregator
206
+ self._aggregator.set_variable("input_data", all_outputs)
207
+ aggregator_result = await self._aggregator.run()
208
+
209
+ return {
210
+ "main_results": main_result.agent_results,
211
+ "aggregated_result": aggregator_result.outputs,
212
+ "total_duration": main_result.total_duration
213
+ + aggregator_result.execution_duration,
214
+ }
215
+
216
+ return await self.get_final_result()
217
+
218
+ # Pipeline methods
219
+ def pipeline(self, *agents: Agent) -> "PipelineAgents":
220
+ """Create pipeline of agents."""
221
+ return PipelineAgents(list(agents))
222
+
223
+ # Conditional execution
224
+ async def run_conditional(self) -> "FluentResult":
225
+ """Run with conditional logic."""
226
+ # Execute conditions and build execution plan
227
+ for _condition in self._conditions:
228
+ # Evaluate condition and add appropriate agents
229
+ pass
230
+
231
+ return await self.run_sequential()
232
+
233
+
234
+ class ConditionalAgents:
235
+ """Conditional execution builder."""
236
+
237
+ def __init__(self, parent: Agents, agent_name: str, key: str, expected_value: Any):
238
+ self.parent = parent
239
+ self.condition_agent = agent_name
240
+ self.condition_key = key
241
+ self.expected_value = expected_value
242
+ self.then_agents: list[Agent] = []
243
+ self.else_agents: list[Agent] = []
244
+ self._in_else = False
245
+
246
+ def then_add(self, agent: Agent) -> "ConditionalAgents":
247
+ """Add agent for 'then' branch."""
248
+ if not self._in_else:
249
+ self.then_agents.append(agent)
250
+ else:
251
+ self.else_agents.append(agent)
252
+ return self
253
+
254
+ def else_(self) -> "ConditionalAgents":
255
+ """Switch to 'else' branch."""
256
+ self._in_else = True
257
+ return self
258
+
259
+ def endif(self) -> Agents:
260
+ """End conditional and return to main builder."""
261
+ # Store conditional configuration
262
+ self.parent._conditions.append(
263
+ {
264
+ "agent": self.condition_agent,
265
+ "key": self.condition_key,
266
+ "expected": self.expected_value,
267
+ "then": self.then_agents,
268
+ "else": self.else_agents,
269
+ }
270
+ )
271
+ return self.parent
272
+
273
+
274
+ class PipelineAgents:
275
+ """Pipeline execution builder."""
276
+
277
+ def __init__(self, agents: list[Agent]):
278
+ self.agents = agents
279
+ self._pipe_configs: list[dict] = []
280
+
281
+ def pipe_output(
282
+ self, from_agent: str, output_key: str, to_agent: str, input_key: str
283
+ ) -> "PipelineAgents":
284
+ """Configure output piping between agents."""
285
+ self._pipe_configs.append(
286
+ {
287
+ "from": from_agent,
288
+ "output_key": output_key,
289
+ "to": to_agent,
290
+ "input_key": input_key,
291
+ }
292
+ )
293
+ return self
294
+
295
+ async def run(self) -> "FluentResult":
296
+ """Run pipeline."""
297
+ results: dict[str, Any] = {}
298
+
299
+ for i, agent in enumerate(self.agents):
300
+ # Set up inputs from previous agent if configured
301
+ if i > 0:
302
+ prev_agent = self.agents[i - 1]
303
+ prev_result = results.get(prev_agent.name)
304
+
305
+ if prev_result:
306
+ # Find pipe configuration
307
+ for pipe_config in self._pipe_configs:
308
+ if (
309
+ pipe_config["from"] == prev_agent.name
310
+ and pipe_config["to"] == agent.name
311
+ ):
312
+ output_value = prev_result.get_output(
313
+ pipe_config["output_key"]
314
+ )
315
+ if output_value is not None:
316
+ agent.set_variable(
317
+ pipe_config["input_key"], output_value
318
+ )
319
+
320
+ # Run agent
321
+ result = await agent.run()
322
+ results[agent.name] = result
323
+
324
+ # Create team result
325
+ team_result = TeamResult(
326
+ team_name="pipeline", status="completed", agent_results=results
327
+ )
328
+
329
+ return FluentResult(team_result)
330
+
331
+ def get_final_output(self) -> Any:
332
+ """Get output from last agent."""
333
+ # This would be implemented after execution
334
+ pass
335
+
336
+
337
+ class FluentResult:
338
+ """Enhanced result wrapper with fluent methods."""
339
+
340
+ def __init__(self, result: Union[TeamResult, Any]):
341
+ if isinstance(result, TeamResult):
342
+ self._team_result = result
343
+ else:
344
+ # Handle other result types
345
+ self._team_result = result
346
+
347
+ def __getattr__(self, name: str) -> Any:
348
+ """Delegate to underlying result."""
349
+ return getattr(self._team_result, name)
350
+
351
+ def get_all_outputs(self, key: str) -> list[Any]:
352
+ """Get specific output from all agents."""
353
+ if hasattr(self._team_result, "get_all_outputs"):
354
+ return self._team_result.get_all_outputs(key)
355
+ return []
356
+
357
+ def get_best_by(self, metric: str, maximize: bool = True) -> Optional[AgentResult]:
358
+ """Get best agent by metric."""
359
+ if hasattr(self._team_result, "get_best_by"):
360
+ return self._team_result.get_best_by(metric, maximize)
361
+ return None
362
+
363
+ def filter_successful(self) -> list[AgentResult]:
364
+ """Get only successful agent results."""
365
+ if hasattr(self._team_result, "agent_results"):
366
+ return [
367
+ result
368
+ for result in self._team_result.agent_results.values()
369
+ if result.is_success
370
+ ]
371
+ return []
372
+
373
+ def group_by_status(self) -> dict[str, list[AgentResult]]:
374
+ """Group results by status."""
375
+ groups = defaultdict(list)
376
+ if hasattr(self._team_result, "agent_results"):
377
+ for result in self._team_result.agent_results.values():
378
+ status = (
379
+ result.status.value
380
+ if hasattr(result.status, "value")
381
+ else str(result.status)
382
+ )
383
+ groups[status].append(result)
384
+ return dict(groups)
385
+
386
+
387
+ # Helper functions for one-liner patterns
388
+ async def run_parallel_agents(
389
+ *agents: Agent, timeout: Optional[float] = None
390
+ ) -> FluentResult:
391
+ """One-liner for parallel execution."""
392
+ return await Agents().add_many(list(agents)).run_parallel(timeout)
393
+
394
+
395
+ async def run_sequential_agents(*agents: Agent) -> FluentResult:
396
+ """One-liner for sequential execution."""
397
+ return await Agents().add_many(list(agents)).run_sequential()
398
+
399
+
400
+ async def collect_agent_outputs(agents: list[Agent], output_key: str) -> list[Any]:
401
+ """One-liner to collect specific output from agents."""
402
+ return await Agents().add_many(agents).collect_outputs(output_key)
403
+
404
+
405
+ async def get_best_agent(
406
+ agents: list[Agent], metric: str, maximize: bool = True
407
+ ) -> AgentResult:
408
+ """One-liner to get best performing agent."""
409
+ return await Agents().add_many(agents).get_best_by(metric, maximize)
410
+
411
+
412
+ # Pipeline helpers
413
+ def create_pipeline(*agents: Agent) -> PipelineAgents:
414
+ """Create agent pipeline."""
415
+ return PipelineAgents(list(agents))
416
+
417
+
418
+ def create_agent_team(name: str, agents: list[Agent]) -> AgentTeam:
419
+ """Create agent team."""
420
+ team = AgentTeam(name)
421
+ return team.add_agents(agents)