agno 2.0.0a1__py3-none-any.whl → 2.0.0rc2__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.
- agno/agent/agent.py +416 -41
- agno/api/agent.py +2 -2
- agno/api/evals.py +2 -2
- agno/api/os.py +1 -1
- agno/api/settings.py +2 -2
- agno/api/team.py +2 -2
- agno/db/dynamo/dynamo.py +0 -6
- agno/db/firestore/firestore.py +0 -6
- agno/db/in_memory/in_memory_db.py +0 -6
- agno/db/json/json_db.py +0 -6
- agno/db/mongo/mongo.py +8 -9
- agno/db/mysql/utils.py +0 -1
- agno/db/postgres/postgres.py +0 -10
- agno/db/postgres/utils.py +0 -1
- agno/db/redis/redis.py +0 -4
- agno/db/singlestore/singlestore.py +0 -10
- agno/db/singlestore/utils.py +0 -1
- agno/db/sqlite/sqlite.py +0 -4
- agno/db/sqlite/utils.py +0 -1
- agno/eval/accuracy.py +12 -5
- agno/integrations/discord/client.py +5 -1
- agno/knowledge/chunking/strategy.py +14 -14
- agno/knowledge/embedder/aws_bedrock.py +2 -2
- agno/knowledge/knowledge.py +156 -120
- agno/knowledge/reader/arxiv_reader.py +5 -5
- agno/knowledge/reader/csv_reader.py +6 -77
- agno/knowledge/reader/docx_reader.py +5 -5
- agno/knowledge/reader/firecrawl_reader.py +5 -5
- agno/knowledge/reader/json_reader.py +5 -5
- agno/knowledge/reader/markdown_reader.py +31 -9
- agno/knowledge/reader/pdf_reader.py +10 -123
- agno/knowledge/reader/reader_factory.py +65 -72
- agno/knowledge/reader/s3_reader.py +44 -114
- agno/knowledge/reader/text_reader.py +5 -5
- agno/knowledge/reader/url_reader.py +75 -31
- agno/knowledge/reader/web_search_reader.py +6 -29
- agno/knowledge/reader/website_reader.py +5 -5
- agno/knowledge/reader/wikipedia_reader.py +5 -5
- agno/knowledge/reader/youtube_reader.py +6 -6
- agno/knowledge/utils.py +10 -10
- agno/models/anthropic/claude.py +2 -49
- agno/models/aws/bedrock.py +3 -7
- agno/models/base.py +37 -6
- agno/models/message.py +7 -6
- agno/os/app.py +168 -64
- agno/os/interfaces/agui/agui.py +1 -1
- agno/os/interfaces/agui/utils.py +16 -9
- agno/os/interfaces/slack/slack.py +2 -3
- agno/os/interfaces/whatsapp/whatsapp.py +2 -3
- agno/os/mcp.py +235 -0
- agno/os/router.py +576 -19
- agno/os/routers/evals/evals.py +201 -12
- agno/os/routers/knowledge/knowledge.py +455 -18
- agno/os/routers/memory/memory.py +260 -29
- agno/os/routers/metrics/metrics.py +127 -7
- agno/os/routers/session/session.py +398 -25
- agno/os/schema.py +55 -2
- agno/os/settings.py +0 -1
- agno/run/agent.py +96 -2
- agno/run/cancel.py +0 -2
- agno/run/team.py +93 -2
- agno/run/workflow.py +25 -12
- agno/team/team.py +863 -1053
- agno/tools/function.py +65 -7
- agno/tools/linear.py +1 -1
- agno/tools/mcp.py +1 -2
- agno/utils/gemini.py +31 -1
- agno/utils/log.py +52 -2
- agno/utils/mcp.py +55 -3
- agno/utils/models/claude.py +41 -0
- agno/utils/print_response/team.py +177 -73
- agno/utils/streamlit.py +481 -0
- agno/workflow/workflow.py +17 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/METADATA +1 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/RECORD +78 -77
- agno/knowledge/reader/gcs_reader.py +0 -67
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/WHEEL +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/top_level.txt +0 -0
agno/run/agent.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from dataclasses import asdict, dataclass, field
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from time import time
|
|
4
|
-
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
|
|
8
|
+
from agno.media import AudioArtifact, AudioResponse, File, ImageArtifact, VideoArtifact
|
|
9
9
|
from agno.models.message import Citations, Message
|
|
10
10
|
from agno.models.metrics import Metrics
|
|
11
11
|
from agno.models.response import ToolExecution
|
|
@@ -43,6 +43,8 @@ class RunEvent(str, Enum):
|
|
|
43
43
|
output_model_response_started = "OutputModelResponseStarted"
|
|
44
44
|
output_model_response_completed = "OutputModelResponseCompleted"
|
|
45
45
|
|
|
46
|
+
custom_event = "CustomEvent"
|
|
47
|
+
|
|
46
48
|
|
|
47
49
|
@dataclass
|
|
48
50
|
class BaseAgentRunEvent(BaseRunOutputEvent):
|
|
@@ -226,6 +228,11 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
|
|
|
226
228
|
event: str = RunEvent.output_model_response_completed.value
|
|
227
229
|
|
|
228
230
|
|
|
231
|
+
@dataclass
|
|
232
|
+
class CustomEvent(BaseAgentRunEvent):
|
|
233
|
+
event: str = RunEvent.custom_event.value
|
|
234
|
+
|
|
235
|
+
|
|
229
236
|
RunOutputEvent = Union[
|
|
230
237
|
RunStartedEvent,
|
|
231
238
|
RunContentEvent,
|
|
@@ -246,6 +253,7 @@ RunOutputEvent = Union[
|
|
|
246
253
|
ParserModelResponseCompletedEvent,
|
|
247
254
|
OutputModelResponseStartedEvent,
|
|
248
255
|
OutputModelResponseCompletedEvent,
|
|
256
|
+
CustomEvent,
|
|
249
257
|
]
|
|
250
258
|
|
|
251
259
|
|
|
@@ -270,6 +278,7 @@ RUN_EVENT_TYPE_REGISTRY = {
|
|
|
270
278
|
RunEvent.parser_model_response_completed.value: ParserModelResponseCompletedEvent,
|
|
271
279
|
RunEvent.output_model_response_started.value: OutputModelResponseStartedEvent,
|
|
272
280
|
RunEvent.output_model_response_completed.value: OutputModelResponseCompletedEvent,
|
|
281
|
+
RunEvent.custom_event.value: CustomEvent,
|
|
273
282
|
}
|
|
274
283
|
|
|
275
284
|
|
|
@@ -281,6 +290,78 @@ def run_output_event_from_dict(data: dict) -> BaseRunOutputEvent:
|
|
|
281
290
|
return cls.from_dict(data) # type: ignore
|
|
282
291
|
|
|
283
292
|
|
|
293
|
+
@dataclass
|
|
294
|
+
class RunInput:
|
|
295
|
+
"""Container for the raw input data passed to Agent.run().
|
|
296
|
+
|
|
297
|
+
This captures the original input exactly as provided by the user,
|
|
298
|
+
separate from the processed messages that go to the model.
|
|
299
|
+
|
|
300
|
+
Attributes:
|
|
301
|
+
input_content: The literal input message/content passed to run()
|
|
302
|
+
images: Images directly passed to run()
|
|
303
|
+
videos: Videos directly passed to run()
|
|
304
|
+
audios: Audio files directly passed to run()
|
|
305
|
+
files: Files directly passed to run()
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
input_content: Optional[Union[str, List, Dict, Message, BaseModel, List[Message]]] = None
|
|
309
|
+
images: Optional[Sequence[ImageArtifact]] = None
|
|
310
|
+
videos: Optional[Sequence[VideoArtifact]] = None
|
|
311
|
+
audios: Optional[Sequence[AudioArtifact]] = None
|
|
312
|
+
files: Optional[Sequence[File]] = None
|
|
313
|
+
|
|
314
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
315
|
+
"""Convert to dictionary representation"""
|
|
316
|
+
result: Dict[str, Any] = {}
|
|
317
|
+
|
|
318
|
+
if self.input_content is not None:
|
|
319
|
+
if isinstance(self.input_content, (str)):
|
|
320
|
+
result["input_content"] = self.input_content
|
|
321
|
+
elif isinstance(self.input_content, BaseModel):
|
|
322
|
+
result["input_content"] = self.input_content.model_dump(exclude_none=True)
|
|
323
|
+
elif isinstance(self.input_content, Message):
|
|
324
|
+
result["input_content"] = self.input_content.to_dict()
|
|
325
|
+
elif (
|
|
326
|
+
isinstance(self.input_content, list)
|
|
327
|
+
and self.input_content
|
|
328
|
+
and isinstance(self.input_content[0], Message)
|
|
329
|
+
):
|
|
330
|
+
result["input_content"] = [m.to_dict() for m in self.input_content]
|
|
331
|
+
else:
|
|
332
|
+
result["input_content"] = self.input_content
|
|
333
|
+
|
|
334
|
+
if self.images:
|
|
335
|
+
result["images"] = [img.to_dict() for img in self.images]
|
|
336
|
+
if self.videos:
|
|
337
|
+
result["videos"] = [vid.to_dict() for vid in self.videos]
|
|
338
|
+
if self.audios:
|
|
339
|
+
result["audios"] = [aud.to_dict() for aud in self.audios]
|
|
340
|
+
|
|
341
|
+
return result
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
|
|
345
|
+
"""Create RunInput from dictionary"""
|
|
346
|
+
images = None
|
|
347
|
+
if data.get("images"):
|
|
348
|
+
images = [ImageArtifact.model_validate(img_data) for img_data in data["images"]]
|
|
349
|
+
|
|
350
|
+
videos = None
|
|
351
|
+
if data.get("videos"):
|
|
352
|
+
videos = [VideoArtifact.model_validate(vid_data) for vid_data in data["videos"]]
|
|
353
|
+
|
|
354
|
+
audios = None
|
|
355
|
+
if data.get("audios"):
|
|
356
|
+
audios = [AudioArtifact.model_validate(aud_data) for aud_data in data["audios"]]
|
|
357
|
+
|
|
358
|
+
files = None
|
|
359
|
+
if data.get("files"):
|
|
360
|
+
files = [File.model_validate(file_data) for file_data in data["files"]]
|
|
361
|
+
|
|
362
|
+
return cls(input_content=data.get("input_content"), images=images, videos=videos, audios=audios, files=files)
|
|
363
|
+
|
|
364
|
+
|
|
284
365
|
@dataclass
|
|
285
366
|
class RunOutput:
|
|
286
367
|
"""Response returned by Agent.run() or Workflow.run() functions"""
|
|
@@ -313,6 +394,9 @@ class RunOutput:
|
|
|
313
394
|
audio: Optional[List[AudioArtifact]] = None # Audio attached to the response
|
|
314
395
|
response_audio: Optional[AudioResponse] = None # Model audio response
|
|
315
396
|
|
|
397
|
+
# Input media and messages from user
|
|
398
|
+
input: Optional[RunInput] = None
|
|
399
|
+
|
|
316
400
|
citations: Optional[Citations] = None
|
|
317
401
|
references: Optional[List[MessageReferences]] = None
|
|
318
402
|
|
|
@@ -363,6 +447,7 @@ class RunOutput:
|
|
|
363
447
|
"videos",
|
|
364
448
|
"audio",
|
|
365
449
|
"response_audio",
|
|
450
|
+
"input",
|
|
366
451
|
"citations",
|
|
367
452
|
"events",
|
|
368
453
|
"additional_input",
|
|
@@ -446,6 +531,9 @@ class RunOutput:
|
|
|
446
531
|
else:
|
|
447
532
|
_dict["tools"].append(tool)
|
|
448
533
|
|
|
534
|
+
if self.input is not None:
|
|
535
|
+
_dict["input"] = self.input.to_dict()
|
|
536
|
+
|
|
449
537
|
return _dict
|
|
450
538
|
|
|
451
539
|
def to_json(self) -> str:
|
|
@@ -488,6 +576,11 @@ class RunOutput:
|
|
|
488
576
|
response_audio = data.pop("response_audio", None)
|
|
489
577
|
response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
|
|
490
578
|
|
|
579
|
+
input_data = data.pop("input", None)
|
|
580
|
+
input_obj = None
|
|
581
|
+
if input_data:
|
|
582
|
+
input_obj = RunInput.from_dict(input_data)
|
|
583
|
+
|
|
491
584
|
metrics = data.pop("metrics", None)
|
|
492
585
|
if metrics:
|
|
493
586
|
metrics = Metrics(**metrics)
|
|
@@ -518,6 +611,7 @@ class RunOutput:
|
|
|
518
611
|
audio=audio,
|
|
519
612
|
videos=videos,
|
|
520
613
|
response_audio=response_audio,
|
|
614
|
+
input=input_obj,
|
|
521
615
|
events=events,
|
|
522
616
|
additional_input=additional_input,
|
|
523
617
|
reasoning_steps=reasoning_steps,
|
agno/run/cancel.py
CHANGED
|
@@ -18,7 +18,6 @@ class RunCancellationManager:
|
|
|
18
18
|
"""Register a new run as not cancelled."""
|
|
19
19
|
with self._lock:
|
|
20
20
|
self._cancelled_runs[run_id] = False
|
|
21
|
-
logger.debug(f"Registered run {run_id} for cancellation tracking")
|
|
22
21
|
|
|
23
22
|
def cancel_run(self, run_id: str) -> bool:
|
|
24
23
|
"""Cancel a run by marking it as cancelled.
|
|
@@ -45,7 +44,6 @@ class RunCancellationManager:
|
|
|
45
44
|
with self._lock:
|
|
46
45
|
if run_id in self._cancelled_runs:
|
|
47
46
|
del self._cancelled_runs[run_id]
|
|
48
|
-
logger.debug(f"Cleaned up cancellation tracking for run {run_id}")
|
|
49
47
|
|
|
50
48
|
def raise_if_cancelled(self, run_id: str) -> None:
|
|
51
49
|
"""Check if a run should be cancelled and raise exception if so."""
|
agno/run/team.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from dataclasses import asdict, dataclass, field
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from time import time
|
|
4
|
-
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
|
|
8
|
+
from agno.media import AudioArtifact, AudioResponse, File, ImageArtifact, VideoArtifact
|
|
9
9
|
from agno.models.message import Citations, Message
|
|
10
10
|
from agno.models.metrics import Metrics
|
|
11
11
|
from agno.models.response import ToolExecution
|
|
@@ -40,6 +40,8 @@ class TeamRunEvent(str, Enum):
|
|
|
40
40
|
output_model_response_started = "TeamOutputModelResponseStarted"
|
|
41
41
|
output_model_response_completed = "TeamOutputModelResponseCompleted"
|
|
42
42
|
|
|
43
|
+
custom_event = "CustomEvent"
|
|
44
|
+
|
|
43
45
|
|
|
44
46
|
@dataclass
|
|
45
47
|
class BaseTeamRunEvent(BaseRunOutputEvent):
|
|
@@ -213,6 +215,11 @@ class OutputModelResponseCompletedEvent(BaseTeamRunEvent):
|
|
|
213
215
|
event: str = TeamRunEvent.output_model_response_completed.value
|
|
214
216
|
|
|
215
217
|
|
|
218
|
+
@dataclass
|
|
219
|
+
class CustomEvent(BaseTeamRunEvent):
|
|
220
|
+
event: str = TeamRunEvent.custom_event.value
|
|
221
|
+
|
|
222
|
+
|
|
216
223
|
TeamRunOutputEvent = Union[
|
|
217
224
|
RunStartedEvent,
|
|
218
225
|
RunContentEvent,
|
|
@@ -231,6 +238,7 @@ TeamRunOutputEvent = Union[
|
|
|
231
238
|
ParserModelResponseCompletedEvent,
|
|
232
239
|
OutputModelResponseStartedEvent,
|
|
233
240
|
OutputModelResponseCompletedEvent,
|
|
241
|
+
CustomEvent,
|
|
234
242
|
]
|
|
235
243
|
|
|
236
244
|
# Map event string to dataclass for team events
|
|
@@ -252,6 +260,7 @@ TEAM_RUN_EVENT_TYPE_REGISTRY = {
|
|
|
252
260
|
TeamRunEvent.parser_model_response_completed.value: ParserModelResponseCompletedEvent,
|
|
253
261
|
TeamRunEvent.output_model_response_started.value: OutputModelResponseStartedEvent,
|
|
254
262
|
TeamRunEvent.output_model_response_completed.value: OutputModelResponseCompletedEvent,
|
|
263
|
+
TeamRunEvent.custom_event.value: CustomEvent,
|
|
255
264
|
}
|
|
256
265
|
|
|
257
266
|
|
|
@@ -266,6 +275,76 @@ def team_run_output_event_from_dict(data: dict) -> BaseTeamRunEvent:
|
|
|
266
275
|
return event_class.from_dict(data) # type: ignore
|
|
267
276
|
|
|
268
277
|
|
|
278
|
+
@dataclass
|
|
279
|
+
class TeamRunInput:
|
|
280
|
+
"""Container for the raw input data passed to Agent.run().
|
|
281
|
+
This captures the original input exactly as provided by the user,
|
|
282
|
+
separate from the processed messages that go to the model.
|
|
283
|
+
Attributes:
|
|
284
|
+
input_content: The literal input message/content passed to run()
|
|
285
|
+
images: Images directly passed to run()
|
|
286
|
+
videos: Videos directly passed to run()
|
|
287
|
+
audios: Audio files directly passed to run()
|
|
288
|
+
files: Files directly passed to run()
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
input_content: Optional[Union[str, List, Dict, Message, BaseModel, List[Message]]] = None
|
|
292
|
+
images: Optional[Sequence[ImageArtifact]] = None
|
|
293
|
+
videos: Optional[Sequence[VideoArtifact]] = None
|
|
294
|
+
audios: Optional[Sequence[AudioArtifact]] = None
|
|
295
|
+
files: Optional[Sequence[File]] = None
|
|
296
|
+
|
|
297
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
298
|
+
"""Convert to dictionary representation"""
|
|
299
|
+
result: Dict[str, Any] = {}
|
|
300
|
+
|
|
301
|
+
if self.input_content is not None:
|
|
302
|
+
if isinstance(self.input_content, (str)):
|
|
303
|
+
result["input_content"] = self.input_content
|
|
304
|
+
elif isinstance(self.input_content, BaseModel):
|
|
305
|
+
result["input_content"] = self.input_content.model_dump(exclude_none=True)
|
|
306
|
+
elif isinstance(self.input_content, Message):
|
|
307
|
+
result["input_content"] = self.input_content.to_dict()
|
|
308
|
+
elif (
|
|
309
|
+
isinstance(self.input_content, list)
|
|
310
|
+
and self.input_content
|
|
311
|
+
and isinstance(self.input_content[0], Message)
|
|
312
|
+
):
|
|
313
|
+
result["input_content"] = [m.to_dict() for m in self.input_content]
|
|
314
|
+
else:
|
|
315
|
+
result["input_content"] = self.input_content
|
|
316
|
+
|
|
317
|
+
if self.images:
|
|
318
|
+
result["images"] = [img.to_dict() for img in self.images]
|
|
319
|
+
if self.videos:
|
|
320
|
+
result["videos"] = [vid.to_dict() for vid in self.videos]
|
|
321
|
+
if self.audios:
|
|
322
|
+
result["audios"] = [aud.to_dict() for aud in self.audios]
|
|
323
|
+
|
|
324
|
+
return result
|
|
325
|
+
|
|
326
|
+
@classmethod
|
|
327
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TeamRunInput":
|
|
328
|
+
"""Create TeamRunInput from dictionary"""
|
|
329
|
+
images = None
|
|
330
|
+
if data.get("images"):
|
|
331
|
+
images = [ImageArtifact.model_validate(img_data) for img_data in data["images"]]
|
|
332
|
+
|
|
333
|
+
videos = None
|
|
334
|
+
if data.get("videos"):
|
|
335
|
+
videos = [VideoArtifact.model_validate(vid_data) for vid_data in data["videos"]]
|
|
336
|
+
|
|
337
|
+
audios = None
|
|
338
|
+
if data.get("audios"):
|
|
339
|
+
audios = [AudioArtifact.model_validate(aud_data) for aud_data in data["audios"]]
|
|
340
|
+
|
|
341
|
+
files = None
|
|
342
|
+
if data.get("files"):
|
|
343
|
+
files = [File.model_validate(file_data) for file_data in data["files"]]
|
|
344
|
+
|
|
345
|
+
return cls(input_content=data.get("input_content"), images=images, videos=videos, audios=audios, files=files)
|
|
346
|
+
|
|
347
|
+
|
|
269
348
|
@dataclass
|
|
270
349
|
class TeamRunOutput:
|
|
271
350
|
"""Response returned by Team.run() functions"""
|
|
@@ -293,6 +372,9 @@ class TeamRunOutput:
|
|
|
293
372
|
|
|
294
373
|
response_audio: Optional[AudioResponse] = None # Model audio response
|
|
295
374
|
|
|
375
|
+
# Input media and messages from user
|
|
376
|
+
input: Optional[TeamRunInput] = None
|
|
377
|
+
|
|
296
378
|
reasoning_content: Optional[str] = None
|
|
297
379
|
|
|
298
380
|
citations: Optional[Citations] = None
|
|
@@ -401,6 +483,9 @@ class TeamRunOutput:
|
|
|
401
483
|
else:
|
|
402
484
|
_dict["tools"].append(tool)
|
|
403
485
|
|
|
486
|
+
if self.input is not None:
|
|
487
|
+
_dict["input"] = self.input.to_dict()
|
|
488
|
+
|
|
404
489
|
return _dict
|
|
405
490
|
|
|
406
491
|
def to_json(self) -> str:
|
|
@@ -468,6 +553,11 @@ class TeamRunOutput:
|
|
|
468
553
|
response_audio = data.pop("response_audio", None)
|
|
469
554
|
response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
|
|
470
555
|
|
|
556
|
+
input_data = data.pop("input", None)
|
|
557
|
+
input_obj = None
|
|
558
|
+
if input_data:
|
|
559
|
+
input_obj = TeamRunInput.from_dict(input_data)
|
|
560
|
+
|
|
471
561
|
metrics = data.pop("metrics", None)
|
|
472
562
|
if metrics:
|
|
473
563
|
metrics = Metrics(**metrics)
|
|
@@ -487,6 +577,7 @@ class TeamRunOutput:
|
|
|
487
577
|
videos=videos,
|
|
488
578
|
audio=audio,
|
|
489
579
|
response_audio=response_audio,
|
|
580
|
+
input=input_obj,
|
|
490
581
|
citations=citations,
|
|
491
582
|
tools=tools,
|
|
492
583
|
events=events,
|
agno/run/workflow.py
CHANGED
|
@@ -13,6 +13,9 @@ from agno.utils.log import log_error
|
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from agno.workflow.types import StepOutput, WorkflowMetrics
|
|
16
|
+
else:
|
|
17
|
+
StepOutput = Any
|
|
18
|
+
WorkflowMetrics = Any
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class WorkflowRunEvent(str, Enum):
|
|
@@ -46,6 +49,8 @@ class WorkflowRunEvent(str, Enum):
|
|
|
46
49
|
|
|
47
50
|
step_output = "StepOutput"
|
|
48
51
|
|
|
52
|
+
custom_event = "CustomEvent"
|
|
53
|
+
|
|
49
54
|
|
|
50
55
|
@dataclass
|
|
51
56
|
class BaseWorkflowRunOutputEvent:
|
|
@@ -132,7 +137,7 @@ class WorkflowCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
132
137
|
content_type: str = "str"
|
|
133
138
|
|
|
134
139
|
# Store actual step execution results as StepOutput objects
|
|
135
|
-
step_results: List[
|
|
140
|
+
step_results: List[StepOutput] = field(default_factory=list)
|
|
136
141
|
metadata: Optional[Dict[str, Any]] = None
|
|
137
142
|
|
|
138
143
|
|
|
@@ -183,7 +188,7 @@ class StepCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
183
188
|
response_audio: Optional[AudioResponse] = None
|
|
184
189
|
|
|
185
190
|
# Store actual step execution results as StepOutput objects
|
|
186
|
-
step_response: Optional[
|
|
191
|
+
step_response: Optional[StepOutput] = None
|
|
187
192
|
|
|
188
193
|
|
|
189
194
|
@dataclass
|
|
@@ -226,7 +231,7 @@ class LoopIterationCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
226
231
|
step_index: Optional[Union[int, tuple]] = None
|
|
227
232
|
iteration: int = 0
|
|
228
233
|
max_iterations: Optional[int] = None
|
|
229
|
-
iteration_results: List[
|
|
234
|
+
iteration_results: List[StepOutput] = field(default_factory=list)
|
|
230
235
|
should_continue: bool = True
|
|
231
236
|
|
|
232
237
|
|
|
@@ -239,7 +244,7 @@ class LoopExecutionCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
239
244
|
step_index: Optional[Union[int, tuple]] = None
|
|
240
245
|
total_iterations: int = 0
|
|
241
246
|
max_iterations: Optional[int] = None
|
|
242
|
-
all_results: List[List[
|
|
247
|
+
all_results: List[List[StepOutput]] = field(default_factory=list)
|
|
243
248
|
|
|
244
249
|
|
|
245
250
|
@dataclass
|
|
@@ -262,7 +267,7 @@ class ParallelExecutionCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
262
267
|
parallel_step_count: Optional[int] = None
|
|
263
268
|
|
|
264
269
|
# Results from all parallel steps
|
|
265
|
-
step_results: List[
|
|
270
|
+
step_results: List[StepOutput] = field(default_factory=list)
|
|
266
271
|
|
|
267
272
|
|
|
268
273
|
@dataclass
|
|
@@ -286,7 +291,7 @@ class ConditionExecutionCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
286
291
|
executed_steps: Optional[int] = None
|
|
287
292
|
|
|
288
293
|
# Results from executed steps
|
|
289
|
-
step_results: List[
|
|
294
|
+
step_results: List[StepOutput] = field(default_factory=list)
|
|
290
295
|
|
|
291
296
|
|
|
292
297
|
@dataclass
|
|
@@ -312,7 +317,7 @@ class RouterExecutionCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
312
317
|
executed_steps: Optional[int] = None
|
|
313
318
|
|
|
314
319
|
# Results from executed steps
|
|
315
|
-
step_results: List[
|
|
320
|
+
step_results: List[StepOutput] = field(default_factory=list)
|
|
316
321
|
|
|
317
322
|
|
|
318
323
|
@dataclass
|
|
@@ -336,7 +341,7 @@ class StepsExecutionCompletedEvent(BaseWorkflowRunOutputEvent):
|
|
|
336
341
|
executed_steps: Optional[int] = None
|
|
337
342
|
|
|
338
343
|
# Results from executed steps
|
|
339
|
-
step_results: List[
|
|
344
|
+
step_results: List[StepOutput] = field(default_factory=list)
|
|
340
345
|
|
|
341
346
|
|
|
342
347
|
@dataclass
|
|
@@ -348,7 +353,7 @@ class StepOutputEvent(BaseWorkflowRunOutputEvent):
|
|
|
348
353
|
step_index: Optional[Union[int, tuple]] = None
|
|
349
354
|
|
|
350
355
|
# Store actual step execution result as StepOutput object
|
|
351
|
-
step_output: Optional[
|
|
356
|
+
step_output: Optional[StepOutput] = None
|
|
352
357
|
|
|
353
358
|
# Properties for backward compatibility
|
|
354
359
|
@property
|
|
@@ -380,6 +385,13 @@ class StepOutputEvent(BaseWorkflowRunOutputEvent):
|
|
|
380
385
|
return self.step_output.stop if self.step_output else False
|
|
381
386
|
|
|
382
387
|
|
|
388
|
+
@dataclass
|
|
389
|
+
class CustomEvent(BaseWorkflowRunOutputEvent):
|
|
390
|
+
"""Event sent when a custom event is produced"""
|
|
391
|
+
|
|
392
|
+
event: str = WorkflowRunEvent.custom_event.value
|
|
393
|
+
|
|
394
|
+
|
|
383
395
|
# Union type for all workflow run response events
|
|
384
396
|
WorkflowRunOutputEvent = Union[
|
|
385
397
|
WorkflowStartedEvent,
|
|
@@ -402,6 +414,7 @@ WorkflowRunOutputEvent = Union[
|
|
|
402
414
|
StepsExecutionStartedEvent,
|
|
403
415
|
StepsExecutionCompletedEvent,
|
|
404
416
|
StepOutputEvent,
|
|
417
|
+
CustomEvent,
|
|
405
418
|
]
|
|
406
419
|
|
|
407
420
|
|
|
@@ -426,7 +439,7 @@ class WorkflowRunOutput:
|
|
|
426
439
|
response_audio: Optional[AudioResponse] = None
|
|
427
440
|
|
|
428
441
|
# Store actual step execution results as StepOutput objects
|
|
429
|
-
step_results: List[Union[
|
|
442
|
+
step_results: List[Union[StepOutput, List[StepOutput]]] = field(default_factory=list)
|
|
430
443
|
|
|
431
444
|
# Store agent/team responses separately with parent_run_id references
|
|
432
445
|
step_executor_runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
|
|
@@ -435,7 +448,7 @@ class WorkflowRunOutput:
|
|
|
435
448
|
events: Optional[List[WorkflowRunOutputEvent]] = None
|
|
436
449
|
|
|
437
450
|
# Workflow metrics aggregated from all steps
|
|
438
|
-
metrics: Optional[
|
|
451
|
+
metrics: Optional[WorkflowMetrics] = None
|
|
439
452
|
|
|
440
453
|
metadata: Optional[Dict[str, Any]] = None
|
|
441
454
|
created_at: int = field(default_factory=lambda: int(time()))
|
|
@@ -521,7 +534,7 @@ class WorkflowRunOutput:
|
|
|
521
534
|
workflow_metrics = WorkflowMetrics.from_dict(workflow_metrics_dict)
|
|
522
535
|
|
|
523
536
|
step_results = data.pop("step_results", [])
|
|
524
|
-
parsed_step_results: List[Union[
|
|
537
|
+
parsed_step_results: List[Union[StepOutput, List[StepOutput]]] = []
|
|
525
538
|
if step_results:
|
|
526
539
|
for step_output_dict in step_results:
|
|
527
540
|
# Reconstruct StepOutput from dict
|