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.
- puffinflow/__init__.py +132 -0
- puffinflow/core/__init__.py +110 -0
- puffinflow/core/agent/__init__.py +320 -0
- puffinflow/core/agent/base.py +1635 -0
- puffinflow/core/agent/checkpoint.py +50 -0
- puffinflow/core/agent/context.py +521 -0
- puffinflow/core/agent/decorators/__init__.py +90 -0
- puffinflow/core/agent/decorators/builder.py +454 -0
- puffinflow/core/agent/decorators/flexible.py +714 -0
- puffinflow/core/agent/decorators/inspection.py +144 -0
- puffinflow/core/agent/dependencies.py +57 -0
- puffinflow/core/agent/scheduling/__init__.py +21 -0
- puffinflow/core/agent/scheduling/builder.py +160 -0
- puffinflow/core/agent/scheduling/exceptions.py +35 -0
- puffinflow/core/agent/scheduling/inputs.py +137 -0
- puffinflow/core/agent/scheduling/parser.py +209 -0
- puffinflow/core/agent/scheduling/scheduler.py +413 -0
- puffinflow/core/agent/state.py +141 -0
- puffinflow/core/config.py +62 -0
- puffinflow/core/coordination/__init__.py +137 -0
- puffinflow/core/coordination/agent_group.py +359 -0
- puffinflow/core/coordination/agent_pool.py +629 -0
- puffinflow/core/coordination/agent_team.py +577 -0
- puffinflow/core/coordination/coordinator.py +720 -0
- puffinflow/core/coordination/deadlock.py +1759 -0
- puffinflow/core/coordination/fluent_api.py +421 -0
- puffinflow/core/coordination/primitives.py +478 -0
- puffinflow/core/coordination/rate_limiter.py +520 -0
- puffinflow/core/observability/__init__.py +47 -0
- puffinflow/core/observability/agent.py +139 -0
- puffinflow/core/observability/alerting.py +73 -0
- puffinflow/core/observability/config.py +127 -0
- puffinflow/core/observability/context.py +88 -0
- puffinflow/core/observability/core.py +147 -0
- puffinflow/core/observability/decorators.py +105 -0
- puffinflow/core/observability/events.py +71 -0
- puffinflow/core/observability/interfaces.py +196 -0
- puffinflow/core/observability/metrics.py +137 -0
- puffinflow/core/observability/tracing.py +209 -0
- puffinflow/core/reliability/__init__.py +27 -0
- puffinflow/core/reliability/bulkhead.py +96 -0
- puffinflow/core/reliability/circuit_breaker.py +149 -0
- puffinflow/core/reliability/leak_detector.py +122 -0
- puffinflow/core/resources/__init__.py +77 -0
- puffinflow/core/resources/allocation.py +790 -0
- puffinflow/core/resources/pool.py +645 -0
- puffinflow/core/resources/quotas.py +567 -0
- puffinflow/core/resources/requirements.py +217 -0
- puffinflow/version.py +21 -0
- puffinflow-2.dev0.dist-info/METADATA +334 -0
- puffinflow-2.dev0.dist-info/RECORD +55 -0
- puffinflow-2.dev0.dist-info/WHEEL +5 -0
- puffinflow-2.dev0.dist-info/entry_points.txt +3 -0
- puffinflow-2.dev0.dist-info/licenses/LICENSE +21 -0
- 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)
|