agno 1.7.2__py3-none-any.whl → 1.7.4__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 (63) hide show
  1. agno/agent/agent.py +264 -155
  2. agno/api/schemas/agent.py +1 -0
  3. agno/api/schemas/team.py +1 -0
  4. agno/app/base.py +0 -22
  5. agno/app/discord/client.py +134 -56
  6. agno/app/fastapi/app.py +0 -11
  7. agno/app/playground/app.py +3 -24
  8. agno/app/playground/async_router.py +97 -28
  9. agno/app/playground/operator.py +25 -19
  10. agno/app/playground/schemas.py +1 -0
  11. agno/app/playground/sync_router.py +93 -26
  12. agno/document/reader/gcs/__init__.py +0 -0
  13. agno/document/reader/gcs/pdf_reader.py +44 -0
  14. agno/embedder/langdb.py +9 -5
  15. agno/knowledge/document.py +199 -8
  16. agno/knowledge/gcs/__init__.py +0 -0
  17. agno/knowledge/gcs/base.py +39 -0
  18. agno/knowledge/gcs/pdf.py +21 -0
  19. agno/models/langdb/langdb.py +8 -5
  20. agno/run/base.py +2 -0
  21. agno/run/response.py +4 -4
  22. agno/run/team.py +6 -6
  23. agno/run/v2/__init__.py +0 -0
  24. agno/run/v2/workflow.py +563 -0
  25. agno/storage/base.py +4 -4
  26. agno/storage/dynamodb.py +74 -10
  27. agno/storage/firestore.py +6 -1
  28. agno/storage/gcs_json.py +8 -2
  29. agno/storage/json.py +20 -5
  30. agno/storage/mongodb.py +14 -5
  31. agno/storage/mysql.py +56 -17
  32. agno/storage/postgres.py +55 -13
  33. agno/storage/redis.py +25 -5
  34. agno/storage/session/__init__.py +3 -1
  35. agno/storage/session/agent.py +3 -0
  36. agno/storage/session/team.py +3 -0
  37. agno/storage/session/v2/__init__.py +5 -0
  38. agno/storage/session/v2/workflow.py +89 -0
  39. agno/storage/singlestore.py +74 -12
  40. agno/storage/sqlite.py +64 -18
  41. agno/storage/yaml.py +26 -6
  42. agno/team/team.py +198 -243
  43. agno/tools/scrapegraph.py +8 -10
  44. agno/utils/log.py +12 -0
  45. agno/utils/message.py +5 -1
  46. agno/utils/openai.py +20 -5
  47. agno/utils/pprint.py +32 -8
  48. agno/workflow/v2/__init__.py +21 -0
  49. agno/workflow/v2/condition.py +554 -0
  50. agno/workflow/v2/loop.py +602 -0
  51. agno/workflow/v2/parallel.py +659 -0
  52. agno/workflow/v2/router.py +521 -0
  53. agno/workflow/v2/step.py +861 -0
  54. agno/workflow/v2/steps.py +465 -0
  55. agno/workflow/v2/types.py +347 -0
  56. agno/workflow/v2/workflow.py +3134 -0
  57. agno/workflow/workflow.py +15 -147
  58. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/METADATA +1 -1
  59. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/RECORD +63 -45
  60. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/WHEEL +0 -0
  61. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/entry_points.txt +0 -0
  62. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/licenses/LICENSE +0 -0
  63. {agno-1.7.2.dist-info → agno-1.7.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,465 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterator, List, Optional, Union
3
+
4
+ from agno.run.response import RunResponseEvent
5
+ from agno.run.team import TeamRunResponseEvent
6
+ from agno.run.v2.workflow import (
7
+ StepsExecutionCompletedEvent,
8
+ StepsExecutionStartedEvent,
9
+ WorkflowRunResponse,
10
+ WorkflowRunResponseEvent,
11
+ )
12
+ from agno.utils.log import log_debug, logger
13
+ from agno.workflow.v2.step import Step, StepInput, StepOutput
14
+
15
+ WorkflowSteps = List[
16
+ Union[
17
+ Callable[
18
+ [StepInput], Union[StepOutput, Awaitable[StepOutput], Iterator[StepOutput], AsyncIterator[StepOutput]]
19
+ ],
20
+ Step,
21
+ "Steps", # type: ignore # noqa: F821
22
+ "Loop", # type: ignore # noqa: F821
23
+ "Parallel", # type: ignore # noqa: F821
24
+ "Condition", # type: ignore # noqa: F821
25
+ "Router", # type: ignore # noqa: F821
26
+ ]
27
+ ]
28
+
29
+
30
+ @dataclass
31
+ class Steps:
32
+ """A pipeline of steps that execute in order"""
33
+
34
+ # Steps to execute
35
+ steps: WorkflowSteps
36
+
37
+ # Pipeline identification
38
+ name: Optional[str] = None
39
+ description: Optional[str] = None
40
+
41
+ def __init__(
42
+ self, name: Optional[str] = None, description: Optional[str] = None, steps: Optional[List[Any]] = None
43
+ ): # Change to List[Any]
44
+ self.name = name
45
+ self.description = description
46
+ self.steps = steps if steps else []
47
+
48
+ def _prepare_steps(self):
49
+ """Prepare the steps for execution - mirrors workflow logic"""
50
+ from agno.agent.agent import Agent
51
+ from agno.team.team import Team
52
+ from agno.workflow.v2.condition import Condition
53
+ from agno.workflow.v2.loop import Loop
54
+ from agno.workflow.v2.parallel import Parallel
55
+ from agno.workflow.v2.router import Router
56
+ from agno.workflow.v2.step import Step
57
+
58
+ prepared_steps: WorkflowSteps = []
59
+ for step in self.steps:
60
+ if callable(step) and hasattr(step, "__name__"):
61
+ prepared_steps.append(Step(name=step.__name__, description="User-defined callable step", executor=step))
62
+ elif isinstance(step, Agent):
63
+ prepared_steps.append(Step(name=step.name, description=step.description, agent=step))
64
+ elif isinstance(step, Team):
65
+ prepared_steps.append(Step(name=step.name, description=step.description, team=step))
66
+ elif isinstance(step, (Step, Steps, Loop, Parallel, Condition, Router)):
67
+ prepared_steps.append(step)
68
+ else:
69
+ raise ValueError(f"Invalid step type: {type(step).__name__}")
70
+
71
+ self.steps = prepared_steps
72
+
73
+ def _update_step_input_from_outputs(
74
+ self,
75
+ step_input: StepInput,
76
+ step_outputs: Union[StepOutput, List[StepOutput]],
77
+ steps_step_outputs: Optional[Dict[str, StepOutput]] = None,
78
+ ) -> StepInput:
79
+ """Helper to update step input from step outputs - mirrors Condition/Router logic"""
80
+ current_images = step_input.images or []
81
+ current_videos = step_input.videos or []
82
+ current_audio = step_input.audio or []
83
+
84
+ if isinstance(step_outputs, list):
85
+ step_images = sum([out.images or [] for out in step_outputs], [])
86
+ step_videos = sum([out.videos or [] for out in step_outputs], [])
87
+ step_audio = sum([out.audio or [] for out in step_outputs], [])
88
+ # Use the last output's content for chaining
89
+ previous_step_content = step_outputs[-1].content if step_outputs else None
90
+ else:
91
+ # Single output
92
+ step_images = step_outputs.images or []
93
+ step_videos = step_outputs.videos or []
94
+ step_audio = step_outputs.audio or []
95
+ previous_step_content = step_outputs.content
96
+
97
+ updated_previous_step_outputs = {}
98
+ if step_input.previous_step_outputs:
99
+ updated_previous_step_outputs.update(step_input.previous_step_outputs)
100
+ if steps_step_outputs:
101
+ updated_previous_step_outputs.update(steps_step_outputs)
102
+
103
+ return StepInput(
104
+ message=step_input.message,
105
+ previous_step_content=previous_step_content,
106
+ previous_step_outputs=updated_previous_step_outputs,
107
+ additional_data=step_input.additional_data,
108
+ images=current_images + step_images,
109
+ videos=current_videos + step_videos,
110
+ audio=current_audio + step_audio,
111
+ )
112
+
113
+ def execute(
114
+ self, step_input: StepInput, session_id: Optional[str] = None, user_id: Optional[str] = None
115
+ ) -> List[StepOutput]:
116
+ """Execute all steps in sequence and return the final result"""
117
+ log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
118
+
119
+ self._prepare_steps()
120
+
121
+ if not self.steps:
122
+ return [StepOutput(step_name=self.name or "Steps", content="No steps to execute")]
123
+
124
+ # Track outputs and pass data between steps - following Condition/Router pattern
125
+ all_results: List[StepOutput] = []
126
+ current_step_input = step_input
127
+ steps_step_outputs = {}
128
+
129
+ try:
130
+ for i, step in enumerate(self.steps):
131
+ step_name = getattr(step, "name", f"step_{i + 1}")
132
+ log_debug(f"Steps {self.name}: Executing step {i + 1}/{len(self.steps)} - {step_name}")
133
+
134
+ # Execute step
135
+ step_output = step.execute(current_step_input, session_id=session_id, user_id=user_id) # type: ignore
136
+
137
+ # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
138
+ if isinstance(step_output, list):
139
+ all_results.extend(step_output)
140
+ if step_output:
141
+ steps_step_outputs[step_name] = step_output[-1]
142
+
143
+ if any(output.stop for output in step_output):
144
+ logger.info(f"Early termination requested by step {step_name}")
145
+ break
146
+ else:
147
+ all_results.append(step_output)
148
+ steps_step_outputs[step_name] = step_output
149
+
150
+ if step_output.stop:
151
+ logger.info(f"Early termination requested by step {step_name}")
152
+ break
153
+ log_debug(f"Steps {self.name}: Step {step_name} completed successfully")
154
+
155
+ # Update input for next step with proper chaining
156
+ current_step_input = self._update_step_input_from_outputs(
157
+ current_step_input, step_output, steps_step_outputs
158
+ )
159
+
160
+ log_debug(f"Steps End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
161
+ return all_results
162
+
163
+ except Exception as e:
164
+ logger.error(f"Steps execution failed: {e}")
165
+ return [
166
+ StepOutput(
167
+ step_name=self.name or "Steps",
168
+ content=f"Steps execution failed: {str(e)}",
169
+ success=False,
170
+ error=str(e),
171
+ )
172
+ ]
173
+
174
+ def execute_stream(
175
+ self,
176
+ step_input: StepInput,
177
+ workflow_run_response: WorkflowRunResponse,
178
+ session_id: Optional[str] = None,
179
+ user_id: Optional[str] = None,
180
+ stream_intermediate_steps: bool = False,
181
+ step_index: Optional[Union[int, tuple]] = None,
182
+ ) -> Iterator[Union[WorkflowRunResponseEvent, TeamRunResponseEvent, RunResponseEvent, StepOutput]]:
183
+ """Execute all steps in sequence with streaming support"""
184
+ log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
185
+
186
+ self._prepare_steps()
187
+
188
+ if stream_intermediate_steps:
189
+ # Yield steps execution started event
190
+ yield StepsExecutionStartedEvent(
191
+ run_id=workflow_run_response.run_id or "",
192
+ workflow_name=workflow_run_response.workflow_name or "",
193
+ workflow_id=workflow_run_response.workflow_id or "",
194
+ session_id=workflow_run_response.session_id or "",
195
+ step_name=self.name,
196
+ step_index=step_index,
197
+ steps_count=len(self.steps),
198
+ )
199
+
200
+ if not self.steps:
201
+ yield StepOutput(step_name=self.name or "Steps", content="No steps to execute")
202
+ return
203
+
204
+ # Track outputs and pass data between steps - following Condition/Router pattern
205
+ all_results = []
206
+ current_step_input = step_input
207
+ steps_step_outputs = {}
208
+
209
+ try:
210
+ for i, step in enumerate(self.steps):
211
+ step_name = getattr(step, "name", f"step_{i + 1}")
212
+ log_debug(f"Steps {self.name}: Executing step {i + 1}/{len(self.steps)} - {step_name}")
213
+
214
+ step_outputs_for_step = []
215
+
216
+ if step_index is None or isinstance(step_index, int):
217
+ # Steps is a main step - child steps get x.1, x.2, x.3 format
218
+ child_step_index = (step_index if step_index is not None else 1, i) # Use i, not i+1
219
+ else:
220
+ # Steps is already a child step - child steps get parent.1, parent.2, parent.3
221
+ child_step_index = step_index + (i,) # Extend the tuple
222
+
223
+ # Stream step execution
224
+ for event in step.execute_stream( # type: ignore
225
+ current_step_input,
226
+ session_id=session_id,
227
+ user_id=user_id,
228
+ stream_intermediate_steps=stream_intermediate_steps,
229
+ workflow_run_response=workflow_run_response,
230
+ step_index=child_step_index,
231
+ ):
232
+ if isinstance(event, StepOutput):
233
+ step_outputs_for_step.append(event)
234
+ all_results.append(event)
235
+ else:
236
+ # Yield other events (streaming content, step events, etc.)
237
+ yield event
238
+
239
+ # Update step outputs tracking and prepare input for next step
240
+ if step_outputs_for_step:
241
+ if len(step_outputs_for_step) == 1:
242
+ steps_step_outputs[step_name] = step_outputs_for_step[0]
243
+
244
+ if step_outputs_for_step[0].stop:
245
+ logger.info(f"Early termination requested by step {step_name}")
246
+ break
247
+
248
+ current_step_input = self._update_step_input_from_outputs(
249
+ current_step_input, step_outputs_for_step[0], steps_step_outputs
250
+ )
251
+ else:
252
+ # Use last output
253
+ steps_step_outputs[step_name] = step_outputs_for_step[-1]
254
+
255
+ if any(output.stop for output in step_outputs_for_step):
256
+ logger.info(f"Early termination requested by step {step_name}")
257
+ break
258
+
259
+ current_step_input = self._update_step_input_from_outputs(
260
+ current_step_input, step_outputs_for_step, steps_step_outputs
261
+ )
262
+
263
+ log_debug(f"Steps End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
264
+
265
+ if stream_intermediate_steps:
266
+ # Yield steps execution completed event
267
+ yield StepsExecutionCompletedEvent(
268
+ run_id=workflow_run_response.run_id or "",
269
+ workflow_name=workflow_run_response.workflow_name or "",
270
+ workflow_id=workflow_run_response.workflow_id or "",
271
+ session_id=workflow_run_response.session_id or "",
272
+ step_name=self.name,
273
+ step_index=step_index,
274
+ steps_count=len(self.steps),
275
+ executed_steps=len(all_results),
276
+ step_results=all_results,
277
+ )
278
+
279
+ for result in all_results:
280
+ yield result
281
+
282
+ except Exception as e:
283
+ logger.error(f"Steps streaming failed: {e}")
284
+ error_result = StepOutput(
285
+ step_name=self.name or "Steps",
286
+ content=f"Steps execution failed: {str(e)}",
287
+ success=False,
288
+ error=str(e),
289
+ )
290
+ yield error_result
291
+
292
+ async def aexecute(
293
+ self, step_input: StepInput, session_id: Optional[str] = None, user_id: Optional[str] = None
294
+ ) -> List[StepOutput]:
295
+ """Execute all steps in sequence asynchronously and return the final result"""
296
+ log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
297
+
298
+ self._prepare_steps()
299
+
300
+ if not self.steps:
301
+ return [StepOutput(step_name=self.name or "Steps", content="No steps to execute")]
302
+
303
+ # Track outputs and pass data between steps - following Condition/Router pattern
304
+ all_results: List[StepOutput] = []
305
+ current_step_input = step_input
306
+ steps_step_outputs = {}
307
+
308
+ try:
309
+ for i, step in enumerate(self.steps):
310
+ step_name = getattr(step, "name", f"step_{i + 1}")
311
+ log_debug(f"Steps {self.name}: Executing async step {i + 1}/{len(self.steps)} - {step_name}")
312
+
313
+ # Execute step
314
+ step_output = await step.aexecute(current_step_input, session_id=session_id, user_id=user_id) # type: ignore
315
+
316
+ # Handle both single StepOutput and List[StepOutput] (from Loop/Condition/Router steps)
317
+ if isinstance(step_output, list):
318
+ all_results.extend(step_output)
319
+ if step_output:
320
+ steps_step_outputs[step_name] = step_output[-1]
321
+
322
+ if any(output.stop for output in step_output):
323
+ logger.info(f"Early termination requested by step {step_name}")
324
+ break
325
+ else:
326
+ all_results.append(step_output)
327
+ steps_step_outputs[step_name] = step_output
328
+
329
+ if step_output.stop:
330
+ logger.info(f"Early termination requested by step {step_name}")
331
+ break
332
+
333
+ # Update input for next step with proper chaining
334
+ current_step_input = self._update_step_input_from_outputs(
335
+ current_step_input, step_output, steps_step_outputs
336
+ )
337
+
338
+ log_debug(f"Steps End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
339
+ return all_results
340
+
341
+ except Exception as e:
342
+ logger.error(f"Async steps execution failed: {e}")
343
+ return [
344
+ StepOutput(
345
+ step_name=self.name or "Steps",
346
+ content=f"Steps execution failed: {str(e)}",
347
+ success=False,
348
+ error=str(e),
349
+ )
350
+ ]
351
+
352
+ async def aexecute_stream(
353
+ self,
354
+ step_input: StepInput,
355
+ workflow_run_response: WorkflowRunResponse,
356
+ session_id: Optional[str] = None,
357
+ user_id: Optional[str] = None,
358
+ stream_intermediate_steps: bool = False,
359
+ step_index: Optional[Union[int, tuple]] = None,
360
+ ) -> AsyncIterator[Union[WorkflowRunResponseEvent, TeamRunResponseEvent, RunResponseEvent, StepOutput]]:
361
+ """Execute all steps in sequence with async streaming support"""
362
+ log_debug(f"Steps Start: {self.name} ({len(self.steps)} steps)", center=True, symbol="-")
363
+
364
+ self._prepare_steps()
365
+
366
+ # Yield steps execution started event
367
+ yield StepsExecutionStartedEvent(
368
+ run_id=workflow_run_response.run_id or "",
369
+ workflow_name=workflow_run_response.workflow_name or "",
370
+ workflow_id=workflow_run_response.workflow_id or "",
371
+ session_id=workflow_run_response.session_id or "",
372
+ step_name=self.name,
373
+ step_index=step_index,
374
+ steps_count=len(self.steps),
375
+ )
376
+
377
+ if not self.steps:
378
+ yield StepOutput(step_name=self.name or "Steps", content="No steps to execute")
379
+ return
380
+
381
+ # Track outputs and pass data between steps - following Condition/Router pattern
382
+ all_results = []
383
+ current_step_input = step_input
384
+ steps_step_outputs = {}
385
+
386
+ try:
387
+ for i, step in enumerate(self.steps):
388
+ step_name = getattr(step, "name", f"step_{i + 1}")
389
+ log_debug(f"Steps {self.name}: Executing async step {i + 1}/{len(self.steps)} - {step_name}")
390
+
391
+ step_outputs_for_step = []
392
+
393
+ if step_index is None or isinstance(step_index, int):
394
+ # Steps is a main step - child steps get x.1, x.2, x.3 format
395
+ child_step_index = (step_index if step_index is not None else 1, i) # Use i, not i+1
396
+ else:
397
+ # Steps is already a child step - child steps get parent.1, parent.2, parent.3
398
+ child_step_index = step_index + (i,) # Extend the tuple
399
+
400
+ # Stream step execution
401
+ async for event in step.aexecute_stream( # type: ignore
402
+ current_step_input,
403
+ session_id=session_id,
404
+ user_id=user_id,
405
+ stream_intermediate_steps=stream_intermediate_steps,
406
+ workflow_run_response=workflow_run_response,
407
+ step_index=child_step_index,
408
+ ):
409
+ if isinstance(event, StepOutput):
410
+ step_outputs_for_step.append(event)
411
+ all_results.append(event)
412
+ else:
413
+ # Yield other events (streaming content, step events, etc.)
414
+ yield event
415
+
416
+ # Update step outputs tracking and prepare input for next step
417
+ if step_outputs_for_step:
418
+ if len(step_outputs_for_step) == 1:
419
+ steps_step_outputs[step_name] = step_outputs_for_step[0]
420
+
421
+ if step_outputs_for_step[0].stop:
422
+ logger.info(f"Early termination requested by step {step_name}")
423
+ break
424
+
425
+ current_step_input = self._update_step_input_from_outputs(
426
+ current_step_input, step_outputs_for_step[0], steps_step_outputs
427
+ )
428
+ else:
429
+ # Use last output
430
+ steps_step_outputs[step_name] = step_outputs_for_step[-1]
431
+
432
+ if any(output.stop for output in step_outputs_for_step):
433
+ logger.info(f"Early termination requested by step {step_name}")
434
+ break
435
+
436
+ current_step_input = self._update_step_input_from_outputs(
437
+ current_step_input, step_outputs_for_step, steps_step_outputs
438
+ )
439
+
440
+ log_debug(f"Steps End: {self.name} ({len(all_results)} results)", center=True, symbol="-")
441
+ # Yield steps execution completed event
442
+ yield StepsExecutionCompletedEvent(
443
+ run_id=workflow_run_response.run_id or "",
444
+ workflow_name=workflow_run_response.workflow_name or "",
445
+ workflow_id=workflow_run_response.workflow_id or "",
446
+ session_id=workflow_run_response.session_id or "",
447
+ step_name=self.name,
448
+ step_index=step_index,
449
+ steps_count=len(self.steps),
450
+ executed_steps=len(all_results),
451
+ step_results=all_results,
452
+ )
453
+
454
+ for result in all_results:
455
+ yield result
456
+
457
+ except Exception as e:
458
+ logger.error(f"Async steps streaming failed: {e}")
459
+ error_result = StepOutput(
460
+ step_name=self.name or "Steps",
461
+ content=f"Steps execution failed: {str(e)}",
462
+ success=False,
463
+ error=str(e),
464
+ )
465
+ yield error_result