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,347 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict, List, Optional, Union
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from agno.media import AudioArtifact, ImageArtifact, VideoArtifact
7
+ from agno.run.response import RunResponse
8
+ from agno.run.team import TeamRunResponse
9
+
10
+
11
+ @dataclass
12
+ class WorkflowExecutionInput:
13
+ """Input data for a step execution"""
14
+
15
+ message: Optional[Union[str, Dict[str, Any], List[Any], BaseModel]] = None
16
+
17
+ additional_data: Optional[Dict[str, Any]] = None
18
+
19
+ # Media inputs
20
+ images: Optional[List[ImageArtifact]] = None
21
+ videos: Optional[List[VideoArtifact]] = None
22
+ audio: Optional[List[AudioArtifact]] = None
23
+
24
+ def get_message_as_string(self) -> Optional[str]:
25
+ """Convert message to string representation"""
26
+ if self.message is None:
27
+ return None
28
+
29
+ if isinstance(self.message, str):
30
+ return self.message
31
+ elif isinstance(self.message, BaseModel):
32
+ return self.message.model_dump_json(indent=2, exclude_none=True)
33
+ elif isinstance(self.message, (dict, list)):
34
+ import json
35
+
36
+ return json.dumps(self.message, indent=2, default=str)
37
+ else:
38
+ return str(self.message)
39
+
40
+ def to_dict(self) -> Dict[str, Any]:
41
+ """Convert to dictionary"""
42
+ message_dict: Optional[Union[str, Dict[str, Any], List[Any]]] = None
43
+ if self.message is not None:
44
+ if isinstance(self.message, BaseModel):
45
+ message_dict = self.message.model_dump(exclude_none=True)
46
+ elif isinstance(self.message, (dict, list)):
47
+ message_dict = self.message
48
+ else:
49
+ message_dict = str(self.message)
50
+
51
+ return {
52
+ "message": message_dict,
53
+ "additional_data": self.additional_data,
54
+ "images": [img.to_dict() for img in self.images] if self.images else None,
55
+ "videos": [vid.to_dict() for vid in self.videos] if self.videos else None,
56
+ "audio": [aud.to_dict() for aud in self.audio] if self.audio else None,
57
+ }
58
+
59
+
60
+ @dataclass
61
+ class StepInput:
62
+ """Input data for a step execution"""
63
+
64
+ message: Optional[Union[str, Dict[str, Any], List[Any], BaseModel]] = None
65
+
66
+ previous_step_content: Optional[Any] = None
67
+ previous_step_outputs: Optional[Dict[str, "StepOutput"]] = None
68
+
69
+ additional_data: Optional[Dict[str, Any]] = None
70
+
71
+ # Media inputs
72
+ images: Optional[List[ImageArtifact]] = None
73
+ videos: Optional[List[VideoArtifact]] = None
74
+ audio: Optional[List[AudioArtifact]] = None
75
+
76
+ def get_message_as_string(self) -> Optional[str]:
77
+ """Convert message to string representation"""
78
+ if self.message is None:
79
+ return None
80
+
81
+ if isinstance(self.message, str):
82
+ return self.message
83
+ elif isinstance(self.message, BaseModel):
84
+ return self.message.model_dump_json(indent=2, exclude_none=True)
85
+ elif isinstance(self.message, (dict, list)):
86
+ import json
87
+
88
+ return json.dumps(self.message, indent=2, default=str)
89
+ else:
90
+ return str(self.message)
91
+
92
+ def get_step_output(self, step_name: str) -> Optional["StepOutput"]:
93
+ """Get output from a specific previous step by name"""
94
+ if not self.previous_step_outputs:
95
+ return None
96
+ return self.previous_step_outputs.get(step_name)
97
+
98
+ def get_step_content(self, step_name: str) -> Optional[Union[str, Dict[str, str]]]:
99
+ """Get content from a specific previous step by name
100
+
101
+ For parallel steps, if you ask for the parallel step name, returns a dict
102
+ with {step_name: content} for each sub-step.
103
+ """
104
+ step_output = self.get_step_output(step_name)
105
+ if not step_output:
106
+ return None
107
+
108
+ # If this is a parallel step with sub-outputs, return structured dict
109
+ if step_output.parallel_step_outputs:
110
+ return {
111
+ sub_step_name: sub_output.content # type: ignore[misc]
112
+ for sub_step_name, sub_output in step_output.parallel_step_outputs.items()
113
+ if sub_output.content
114
+ }
115
+
116
+ # Regular step, return content directly
117
+ return step_output.content # type: ignore[return-value]
118
+
119
+ def get_all_previous_content(self) -> str:
120
+ """Get concatenated content from all previous steps"""
121
+ if not self.previous_step_outputs:
122
+ return ""
123
+
124
+ content_parts = []
125
+ for step_name, output in self.previous_step_outputs.items():
126
+ if output.content:
127
+ content_parts.append(f"=== {step_name} ===\n{output.content}")
128
+
129
+ return "\n\n".join(content_parts)
130
+
131
+ def get_last_step_content(self) -> Optional[str]:
132
+ """Get content from the most recent step (for backward compatibility)"""
133
+ if not self.previous_step_outputs:
134
+ return None
135
+
136
+ last_output = list(self.previous_step_outputs.values())[-1] if self.previous_step_outputs else None
137
+ return last_output.content if last_output else None # type: ignore[return-value]
138
+
139
+ def to_dict(self) -> Dict[str, Any]:
140
+ """Convert to dictionary"""
141
+ # Handle the unified message field
142
+ message_dict: Optional[Union[str, Dict[str, Any], List[Any]]] = None
143
+ if self.message is not None:
144
+ if isinstance(self.message, BaseModel):
145
+ message_dict = self.message.model_dump(exclude_none=True)
146
+ elif isinstance(self.message, (dict, list)):
147
+ message_dict = self.message
148
+ else:
149
+ message_dict = str(self.message)
150
+
151
+ previous_step_content_str: Optional[str] = None
152
+ # Handle previous_step_content (keep existing logic)
153
+ if isinstance(self.previous_step_content, BaseModel):
154
+ previous_step_content_str = self.previous_step_content.model_dump_json(indent=2, exclude_none=True)
155
+ elif isinstance(self.previous_step_content, dict):
156
+ import json
157
+
158
+ previous_step_content_str = json.dumps(self.previous_step_content, indent=2, default=str)
159
+ elif self.previous_step_content:
160
+ previous_step_content_str = str(self.previous_step_content)
161
+
162
+ # Convert previous_step_outputs to serializable format (keep existing logic)
163
+ previous_steps_dict = {}
164
+ if self.previous_step_outputs:
165
+ for step_name, output in self.previous_step_outputs.items():
166
+ previous_steps_dict[step_name] = output.to_dict()
167
+
168
+ return {
169
+ "message": message_dict,
170
+ "previous_step_outputs": previous_steps_dict,
171
+ "previous_step_content": previous_step_content_str,
172
+ "additional_data": self.additional_data,
173
+ "images": [img.to_dict() for img in self.images] if self.images else None,
174
+ "videos": [vid.to_dict() for vid in self.videos] if self.videos else None,
175
+ "audio": [aud.to_dict() for aud in self.audio] if self.audio else None,
176
+ }
177
+
178
+
179
+ @dataclass
180
+ class StepOutput:
181
+ """Output data from a step execution"""
182
+
183
+ step_name: Optional[str] = None
184
+ step_id: Optional[str] = None
185
+ executor_type: Optional[str] = None
186
+ executor_name: Optional[str] = None
187
+
188
+ # Primary output
189
+ content: Optional[Union[str, Dict[str, Any], List[Any], BaseModel, Any]] = None
190
+
191
+ # For parallel steps: store individual step outputs
192
+ parallel_step_outputs: Optional[Dict[str, "StepOutput"]] = None
193
+
194
+ # Execution response
195
+ response: Optional[Union[RunResponse, TeamRunResponse]] = None
196
+
197
+ # Media outputs
198
+ images: Optional[List[ImageArtifact]] = None
199
+ videos: Optional[List[VideoArtifact]] = None
200
+ audio: Optional[List[AudioArtifact]] = None
201
+
202
+ # Metrics for this step execution
203
+ metrics: Optional[Dict[str, Any]] = None
204
+
205
+ success: bool = True
206
+ error: Optional[str] = None
207
+
208
+ stop: bool = False
209
+
210
+ def to_dict(self) -> Dict[str, Any]:
211
+ """Convert to dictionary"""
212
+ # Handle the unified content field
213
+ content_dict: Optional[Union[str, Dict[str, Any], List[Any]]] = None
214
+ if self.content is not None:
215
+ if isinstance(self.content, BaseModel):
216
+ content_dict = self.content.model_dump(exclude_none=True)
217
+ elif isinstance(self.content, (dict, list)):
218
+ content_dict = self.content
219
+ else:
220
+ content_dict = str(self.content)
221
+
222
+ return {
223
+ "content": content_dict,
224
+ "response": self.response.to_dict() if self.response else None,
225
+ "images": [img.to_dict() for img in self.images] if self.images else None,
226
+ "videos": [vid.to_dict() for vid in self.videos] if self.videos else None,
227
+ "audio": [aud.to_dict() for aud in self.audio] if self.audio else None,
228
+ "metrics": self.metrics,
229
+ "success": self.success,
230
+ "error": self.error,
231
+ "stop": self.stop,
232
+ }
233
+
234
+ @classmethod
235
+ def from_dict(cls, data: Dict[str, Any]) -> "StepOutput":
236
+ """Create StepOutput from dictionary"""
237
+ from agno.run.response import RunResponse
238
+ from agno.run.team import TeamRunResponse
239
+
240
+ # Reconstruct response if present
241
+ response_data = data.get("response")
242
+ response: Optional[Union[RunResponse, TeamRunResponse]] = None
243
+ if response_data:
244
+ # Determine if it's RunResponse or TeamRunResponse based on structure
245
+ if "team_id" in response_data or "team_name" in response_data:
246
+ response = TeamRunResponse.from_dict(response_data)
247
+ else:
248
+ response = RunResponse.from_dict(response_data)
249
+
250
+ # Reconstruct media artifacts
251
+ images = data.get("images")
252
+ if images:
253
+ images = [ImageArtifact.model_validate(img) for img in images]
254
+
255
+ videos = data.get("videos")
256
+ if videos:
257
+ videos = [VideoArtifact.model_validate(vid) for vid in videos]
258
+
259
+ audio = data.get("audio")
260
+ if audio:
261
+ audio = [AudioArtifact.model_validate(aud) for aud in audio]
262
+
263
+ return cls(
264
+ content=data.get("content"),
265
+ response=response,
266
+ images=images,
267
+ videos=videos,
268
+ audio=audio,
269
+ metrics=data.get("metrics"),
270
+ success=data.get("success", True),
271
+ error=data.get("error"),
272
+ stop=data.get("stop", False),
273
+ )
274
+
275
+
276
+ @dataclass
277
+ class StepMetrics:
278
+ """Metrics for a single step execution"""
279
+
280
+ step_name: str
281
+ executor_type: str # "agent", "team", etc.
282
+ executor_name: str
283
+
284
+ # For regular steps: actual metrics data
285
+ metrics: Optional[Dict[str, Any]] = None
286
+
287
+ # For parallel steps: nested step metrics
288
+ parallel_steps: Optional[Dict[str, "StepMetrics"]] = None
289
+
290
+ def to_dict(self) -> Dict[str, Any]:
291
+ """Convert to dictionary - only include relevant fields"""
292
+ result = {
293
+ "step_name": self.step_name,
294
+ "executor_type": self.executor_type,
295
+ "executor_name": self.executor_name,
296
+ }
297
+
298
+ # Only include the relevant field based on executor type
299
+ if self.executor_type == "parallel" and self.parallel_steps:
300
+ result["parallel_steps"] = {name: step.to_dict() for name, step in self.parallel_steps.items()} # type: ignore[assignment]
301
+ elif self.executor_type != "parallel":
302
+ # For non-parallel steps, include metrics (even if None)
303
+ result["metrics"] = self.metrics # type: ignore[assignment]
304
+
305
+ return result
306
+
307
+ @classmethod
308
+ def from_dict(cls, data: Dict[str, Any]) -> "StepMetrics":
309
+ """Create StepMetrics from dictionary"""
310
+
311
+ # Parse nested parallel steps if they exist
312
+ parallel_steps = None
313
+ if "parallel_steps" in data and data["parallel_steps"] is not None:
314
+ parallel_steps = {name: cls.from_dict(step_data) for name, step_data in data["parallel_steps"].items()}
315
+
316
+ return cls(
317
+ step_name=data["step_name"],
318
+ executor_type=data["executor_type"],
319
+ executor_name=data["executor_name"],
320
+ metrics=data.get("metrics") if data.get("executor_type") != "parallel" else None,
321
+ parallel_steps=parallel_steps,
322
+ )
323
+
324
+
325
+ @dataclass
326
+ class WorkflowMetrics:
327
+ """Complete metrics for a workflow execution"""
328
+
329
+ total_steps: int
330
+ steps: Dict[str, StepMetrics]
331
+
332
+ def to_dict(self) -> Dict[str, Any]:
333
+ """Convert to dictionary"""
334
+ return {
335
+ "total_steps": self.total_steps,
336
+ "steps": {name: step.to_dict() for name, step in self.steps.items()},
337
+ }
338
+
339
+ @classmethod
340
+ def from_dict(cls, data: Dict[str, Any]) -> "WorkflowMetrics":
341
+ """Create WorkflowMetrics from dictionary"""
342
+ steps = {name: StepMetrics.from_dict(step_data) for name, step_data in data["steps"].items()}
343
+
344
+ return cls(
345
+ total_steps=data["total_steps"],
346
+ steps=steps,
347
+ )