agno 2.0.0rc2__py3-none-any.whl → 2.0.2__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 (51) hide show
  1. agno/agent/agent.py +78 -135
  2. agno/knowledge/knowledge.py +1 -1
  3. agno/knowledge/reranker/__init__.py +3 -0
  4. agno/media.py +269 -268
  5. agno/models/base.py +13 -48
  6. agno/models/google/gemini.py +11 -10
  7. agno/models/message.py +4 -4
  8. agno/models/ollama/chat.py +1 -1
  9. agno/models/openai/chat.py +33 -14
  10. agno/models/response.py +5 -5
  11. agno/os/app.py +8 -5
  12. agno/run/agent.py +28 -28
  13. agno/run/base.py +9 -19
  14. agno/run/team.py +24 -24
  15. agno/run/workflow.py +16 -16
  16. agno/team/team.py +72 -154
  17. agno/tools/brightdata.py +3 -3
  18. agno/tools/cartesia.py +3 -5
  19. agno/tools/dalle.py +7 -4
  20. agno/tools/desi_vocal.py +2 -2
  21. agno/tools/e2b.py +6 -6
  22. agno/tools/eleven_labs.py +3 -3
  23. agno/tools/fal.py +4 -4
  24. agno/tools/function.py +7 -7
  25. agno/tools/giphy.py +2 -2
  26. agno/tools/lumalab.py +3 -3
  27. agno/tools/models/azure_openai.py +2 -2
  28. agno/tools/models/gemini.py +3 -3
  29. agno/tools/models/groq.py +3 -5
  30. agno/tools/models/nebius.py +2 -2
  31. agno/tools/models_labs.py +5 -5
  32. agno/tools/openai.py +4 -9
  33. agno/tools/opencv.py +3 -3
  34. agno/tools/replicate.py +7 -7
  35. agno/utils/events.py +5 -5
  36. agno/utils/gemini.py +1 -1
  37. agno/utils/mcp.py +3 -3
  38. agno/utils/models/aws_claude.py +1 -1
  39. agno/utils/models/cohere.py +1 -1
  40. agno/utils/models/watsonx.py +1 -1
  41. agno/utils/openai.py +1 -1
  42. agno/vectordb/lancedb/lance_db.py +82 -25
  43. agno/workflow/step.py +7 -7
  44. agno/workflow/types.py +13 -13
  45. agno/workflow/workflow.py +28 -28
  46. {agno-2.0.0rc2.dist-info → agno-2.0.2.dist-info}/METADATA +140 -1
  47. {agno-2.0.0rc2.dist-info → agno-2.0.2.dist-info}/RECORD +50 -50
  48. agno-2.0.2.dist-info/licenses/LICENSE +201 -0
  49. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  50. {agno-2.0.0rc2.dist-info → agno-2.0.2.dist-info}/WHEEL +0 -0
  51. {agno-2.0.0rc2.dist-info → agno-2.0.2.dist-info}/top_level.txt +0 -0
agno/models/base.py CHANGED
@@ -21,7 +21,7 @@ from uuid import uuid4
21
21
  from pydantic import BaseModel
22
22
 
23
23
  from agno.exceptions import AgentRunException
24
- from agno.media import Audio, AudioArtifact, AudioResponse, Image, ImageArtifact, Video, VideoArtifact
24
+ from agno.media import Audio, Image, Video
25
25
  from agno.models.message import Citations, Message
26
26
  from agno.models.metrics import Metrics
27
27
  from agno.models.response import ModelResponse, ModelResponseEvent, ToolExecution
@@ -43,9 +43,9 @@ class MessageData:
43
43
  response_citations: Optional[Citations] = None
44
44
  response_tool_calls: List[Dict[str, Any]] = field(default_factory=list)
45
45
 
46
- response_audio: Optional[AudioResponse] = None
47
- response_image: Optional[ImageArtifact] = None
48
- response_video: Optional[VideoArtifact] = None
46
+ response_audio: Optional[Audio] = None
47
+ response_image: Optional[Image] = None
48
+ response_video: Optional[Video] = None
49
49
 
50
50
  # Data from the provider that we might need on subsequent messages
51
51
  response_provider_data: Optional[Dict[str, Any]] = None
@@ -502,9 +502,7 @@ class Model(ABC):
502
502
  if assistant_message.citations is not None:
503
503
  model_response.citations = assistant_message.citations
504
504
  if assistant_message.audio_output is not None:
505
- if isinstance(assistant_message.audio_output, AudioArtifact):
506
- model_response.audios = [assistant_message.audio_output]
507
- elif isinstance(assistant_message.audio_output, AudioResponse):
505
+ if isinstance(assistant_message.audio_output, Audio):
508
506
  model_response.audio = assistant_message.audio_output
509
507
  if assistant_message.image_output is not None:
510
508
  model_response.images = [assistant_message.image_output]
@@ -557,9 +555,7 @@ class Model(ABC):
557
555
  if assistant_message.citations is not None:
558
556
  model_response.citations = assistant_message.citations
559
557
  if assistant_message.audio_output is not None:
560
- if isinstance(assistant_message.audio_output, AudioArtifact):
561
- model_response.audios = [assistant_message.audio_output]
562
- elif isinstance(assistant_message.audio_output, AudioResponse):
558
+ if isinstance(assistant_message.audio_output, Audio):
563
559
  model_response.audio = assistant_message.audio_output
564
560
  if assistant_message.image_output is not None:
565
561
  model_response.images = [assistant_message.image_output]
@@ -993,13 +989,13 @@ class Model(ABC):
993
989
  stream_data.response_tool_calls.extend(model_response_delta.tool_calls)
994
990
  should_yield = True
995
991
 
996
- if model_response_delta.audio is not None and isinstance(model_response_delta.audio, AudioResponse):
992
+ if model_response_delta.audio is not None and isinstance(model_response_delta.audio, Audio):
997
993
  if stream_data.response_audio is None:
998
- stream_data.response_audio = AudioResponse(id=str(uuid4()), content="", transcript="")
994
+ stream_data.response_audio = Audio(id=str(uuid4()), content="", transcript="")
999
995
 
1000
996
  from typing import cast
1001
997
 
1002
- audio_response = cast(AudioResponse, model_response_delta.audio)
998
+ audio_response = cast(Audio, model_response_delta.audio)
1003
999
 
1004
1000
  # Update the stream data with audio information
1005
1001
  if audio_response.id is not None:
@@ -1104,41 +1100,10 @@ class Model(ABC):
1104
1100
  audios = None
1105
1101
 
1106
1102
  if success and function_execution_result:
1107
- # Convert ImageArtifacts to Images for message compatibility
1108
- if function_execution_result.images:
1109
- from agno.media import Image
1110
-
1111
- images = []
1112
- for img_artifact in function_execution_result.images:
1113
- if img_artifact.url:
1114
- images.append(Image(url=img_artifact.url))
1115
- elif img_artifact.content:
1116
- images.append(Image(content=img_artifact.content))
1117
-
1118
- # Convert VideoArtifacts to Videos for message compatibility
1119
- if function_execution_result.videos:
1120
- from agno.media import Video
1121
-
1122
- videos = []
1123
- for vid_artifact in function_execution_result.videos:
1124
- if vid_artifact.url:
1125
- videos.append(Video(url=vid_artifact.url))
1126
- elif vid_artifact.content:
1127
- videos.append(Video(content=vid_artifact.content))
1128
-
1129
- # Convert AudioArtifacts to Audio for message compatibility
1130
- if function_execution_result.audios:
1131
- from agno.media import Audio
1132
-
1133
- audios = []
1134
- for aud_artifact in function_execution_result.audios:
1135
- if aud_artifact.url:
1136
- audios.append(Audio(url=aud_artifact.url))
1137
- elif aud_artifact.base64_audio:
1138
- import base64
1139
-
1140
- audio_bytes = base64.b64decode(aud_artifact.base64_audio)
1141
- audios.append(Audio(content=audio_bytes))
1103
+ # With unified classes, no conversion needed - use directly
1104
+ images = function_execution_result.images
1105
+ videos = function_execution_result.videos
1106
+ audios = function_execution_result.audios
1142
1107
 
1143
1108
  return Message(
1144
1109
  role=self.tool_message_role,
@@ -10,7 +10,7 @@ from uuid import uuid4
10
10
  from pydantic import BaseModel
11
11
 
12
12
  from agno.exceptions import ModelProviderError
13
- from agno.media import Audio, File, ImageArtifact, Video
13
+ from agno.media import Audio, File, Image, Video
14
14
  from agno.models.base import Model
15
15
  from agno.models.message import Citations, Message, UrlCitation
16
16
  from agno.models.metrics import Metrics
@@ -559,9 +559,14 @@ class Gemini(Model):
559
559
  return Part.from_bytes(mime_type=mime_type, data=audio.content)
560
560
 
561
561
  # Case 2: Audio is an url
562
- elif audio.url is not None and audio.audio_url_content is not None:
563
- mime_type = f"audio/{audio.format}" if audio.format else "audio/mp3"
564
- return Part.from_bytes(mime_type=mime_type, data=audio.audio_url_content)
562
+ elif audio.url is not None:
563
+ audio_bytes = audio.get_content_bytes() # type: ignore
564
+ if audio_bytes is not None:
565
+ mime_type = f"audio/{audio.format}" if audio.format else "audio/mp3"
566
+ return Part.from_bytes(mime_type=mime_type, data=audio_bytes)
567
+ else:
568
+ log_warning(f"Failed to download audio from {audio}")
569
+ return None
565
570
 
566
571
  # Case 3: Audio is a local file path
567
572
  elif audio.filepath is not None:
@@ -815,9 +820,7 @@ class Gemini(Model):
815
820
  if model_response.images is None:
816
821
  model_response.images = []
817
822
  model_response.images.append(
818
- ImageArtifact(
819
- id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type
820
- )
823
+ Image(id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type)
821
824
  )
822
825
 
823
826
  # Extract function call if present
@@ -929,9 +932,7 @@ class Gemini(Model):
929
932
  if model_response.images is None:
930
933
  model_response.images = []
931
934
  model_response.images.append(
932
- ImageArtifact(
933
- id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type
934
- )
935
+ Image(id=str(uuid4()), content=part.inline_data.data, mime_type=part.inline_data.mime_type)
935
936
  )
936
937
 
937
938
  # Extract function call if present
agno/models/message.py CHANGED
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Sequence, Union
4
4
 
5
5
  from pydantic import BaseModel, ConfigDict, Field
6
6
 
7
- from agno.media import Audio, AudioArtifact, AudioResponse, File, Image, ImageArtifact, Video, VideoArtifact
7
+ from agno.media import Audio, File, Image, Video
8
8
  from agno.models.metrics import Metrics
9
9
  from agno.utils.log import log_debug, log_error, log_info, log_warning
10
10
 
@@ -71,9 +71,9 @@ class Message(BaseModel):
71
71
  files: Optional[Sequence[File]] = None
72
72
 
73
73
  # Output from the models
74
- audio_output: Optional[Union[AudioResponse, AudioArtifact]] = None
75
- image_output: Optional[ImageArtifact] = None
76
- video_output: Optional[VideoArtifact] = None
74
+ audio_output: Optional[Audio] = None
75
+ image_output: Optional[Image] = None
76
+ video_output: Optional[Video] = None
77
77
 
78
78
  # The thinking content from the model
79
79
  redacted_reasoning_content: Optional[str] = None
@@ -149,7 +149,7 @@ class Ollama(Model):
149
149
  message_images = []
150
150
  for image in message.images:
151
151
  if image.url is not None:
152
- message_images.append(image.image_url_content)
152
+ message_images.append(image.get_content_bytes())
153
153
  if image.filepath is not None:
154
154
  message_images.append(image.filepath) # type: ignore
155
155
  if image.content is not None and isinstance(image.content, bytes):
@@ -2,12 +2,13 @@ from collections.abc import AsyncIterator
2
2
  from dataclasses import dataclass
3
3
  from os import getenv
4
4
  from typing import Any, Dict, Iterator, List, Literal, Optional, Type, Union
5
+ from uuid import uuid4
5
6
 
6
7
  import httpx
7
8
  from pydantic import BaseModel
8
9
 
9
10
  from agno.exceptions import ModelProviderError
10
- from agno.media import AudioResponse
11
+ from agno.media import Audio
11
12
  from agno.models.base import Model
12
13
  from agno.models.message import Message
13
14
  from agno.models.metrics import Metrics
@@ -729,14 +730,14 @@ class OpenAIChat(Model):
729
730
  # If the audio output modality is requested, we can extract an audio response
730
731
  try:
731
732
  if isinstance(response_message.audio, dict):
732
- model_response.audio = AudioResponse(
733
+ model_response.audio = Audio(
733
734
  id=response_message.audio.get("id"),
734
735
  content=response_message.audio.get("data"),
735
736
  expires_at=response_message.audio.get("expires_at"),
736
737
  transcript=response_message.audio.get("transcript"),
737
738
  )
738
739
  else:
739
- model_response.audio = AudioResponse(
740
+ model_response.audio = Audio(
740
741
  id=response_message.audio.id,
741
742
  content=response_message.audio.data,
742
743
  expires_at=response_message.audio.expires_at,
@@ -783,21 +784,39 @@ class OpenAIChat(Model):
783
784
  # Add audio if present
784
785
  if hasattr(choice_delta, "audio") and choice_delta.audio is not None:
785
786
  try:
787
+ audio_data = None
788
+ audio_id = None
789
+ audio_expires_at = None
790
+ audio_transcript = None
791
+
786
792
  if isinstance(choice_delta.audio, dict):
787
- model_response.audio = AudioResponse(
788
- id=choice_delta.audio.get("id"),
789
- content=choice_delta.audio.get("data"),
790
- expires_at=choice_delta.audio.get("expires_at"),
791
- transcript=choice_delta.audio.get("transcript"),
793
+ audio_data = choice_delta.audio.get("data")
794
+ audio_id = choice_delta.audio.get("id")
795
+ audio_expires_at = choice_delta.audio.get("expires_at")
796
+ audio_transcript = choice_delta.audio.get("transcript")
797
+ else:
798
+ audio_data = choice_delta.audio.data
799
+ audio_id = choice_delta.audio.id
800
+ audio_expires_at = choice_delta.audio.expires_at
801
+ audio_transcript = choice_delta.audio.transcript
802
+
803
+ # Only create Audio object if there's actual content
804
+ if audio_data is not None:
805
+ model_response.audio = Audio(
806
+ id=audio_id,
807
+ content=audio_data,
808
+ expires_at=audio_expires_at,
809
+ transcript=audio_transcript,
792
810
  sample_rate=24000,
793
811
  mime_type="pcm16",
794
812
  )
795
- else:
796
- model_response.audio = AudioResponse(
797
- id=choice_delta.audio.id,
798
- content=choice_delta.audio.data,
799
- expires_at=choice_delta.audio.expires_at,
800
- transcript=choice_delta.audio.transcript,
813
+ # If no content but there's transcript/metadata, create minimal Audio object
814
+ elif audio_transcript is not None or audio_id is not None:
815
+ model_response.audio = Audio(
816
+ id=audio_id or str(uuid4()),
817
+ content=b"",
818
+ expires_at=audio_expires_at,
819
+ transcript=audio_transcript,
801
820
  sample_rate=24000,
802
821
  mime_type="pcm16",
803
822
  )
agno/models/response.py CHANGED
@@ -3,7 +3,7 @@ from enum import Enum
3
3
  from time import time
4
4
  from typing import Any, Dict, List, Optional
5
5
 
6
- from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
6
+ from agno.media import Audio, Image, Video
7
7
  from agno.models.message import Citations
8
8
  from agno.models.metrics import Metrics
9
9
  from agno.tools.function import UserInputField
@@ -87,12 +87,12 @@ class ModelResponse:
87
87
 
88
88
  content: Optional[Any] = None
89
89
  parsed: Optional[Any] = None
90
- audio: Optional[AudioResponse] = None
90
+ audio: Optional[Audio] = None
91
91
 
92
92
  # Unified media fields for LLM-generated and tool-generated media artifacts
93
- images: Optional[List[ImageArtifact]] = None
94
- videos: Optional[List[VideoArtifact]] = None
95
- audios: Optional[List[AudioArtifact]] = None
93
+ images: Optional[List[Image]] = None
94
+ videos: Optional[List[Video]] = None
95
+ audios: Optional[List[Audio]] = None
96
96
 
97
97
  # Model tool calls
98
98
  tool_calls: List[Dict[str, Any]] = field(default_factory=list)
agno/os/app.py CHANGED
@@ -36,12 +36,11 @@ from agno.os.routers.session import get_session_router
36
36
  from agno.os.settings import AgnoAPISettings
37
37
  from agno.os.utils import generate_id
38
38
  from agno.team.team import Team
39
- from agno.tools.mcp import MCPTools, MultiMCPTools
40
39
  from agno.workflow.workflow import Workflow
41
40
 
42
41
 
43
42
  @asynccontextmanager
44
- async def mcp_lifespan(app, mcp_tools: List[Union[MCPTools, MultiMCPTools]]):
43
+ async def mcp_lifespan(app, mcp_tools):
45
44
  """Manage MCP connection lifecycle inside a FastAPI app"""
46
45
  # Startup logic: connect to all contextual MCP servers
47
46
  for tool in mcp_tools:
@@ -103,14 +102,16 @@ class AgentOS:
103
102
  self.lifespan = lifespan
104
103
 
105
104
  # List of all MCP tools used inside the AgentOS
106
- self.mcp_tools: List[Union[MCPTools, MultiMCPTools]] = []
105
+ self.mcp_tools = []
107
106
 
108
107
  if self.agents:
109
108
  for agent in self.agents:
110
109
  # Track all MCP tools to later handle their connection
111
110
  if agent.tools:
112
111
  for tool in agent.tools:
113
- if isinstance(tool, MCPTools) or isinstance(tool, MultiMCPTools):
112
+ # Checking if the tool is a MCPTools or MultiMCPTools instance
113
+ type_name = type(tool).__name__
114
+ if type_name in ("MCPTools", "MultiMCPTools"):
114
115
  self.mcp_tools.append(tool)
115
116
 
116
117
  agent.initialize_agent()
@@ -123,7 +124,9 @@ class AgentOS:
123
124
  # Track all MCP tools to later handle their connection
124
125
  if team.tools:
125
126
  for tool in team.tools:
126
- if isinstance(tool, MCPTools) or isinstance(tool, MultiMCPTools):
127
+ # Checking if the tool is a MCPTools or MultiMCPTools instance
128
+ type_name = type(tool).__name__
129
+ if type_name in ("MCPTools", "MultiMCPTools"):
127
130
  self.mcp_tools.append(tool)
128
131
 
129
132
  team.initialize_team()
agno/run/agent.py CHANGED
@@ -5,7 +5,7 @@ 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, File, ImageArtifact, VideoArtifact
8
+ from agno.media import Audio, File, Image, Video
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
@@ -97,8 +97,8 @@ class RunContentEvent(BaseAgentRunEvent):
97
97
  content_type: str = "str"
98
98
  reasoning_content: Optional[str] = None
99
99
  citations: Optional[Citations] = None
100
- response_audio: Optional[AudioResponse] = None # Model audio response
101
- image: Optional[ImageArtifact] = None # Image attached to the response
100
+ response_audio: Optional[Audio] = None # Model audio response
101
+ image: Optional[Image] = None # Image attached to the response
102
102
  references: Optional[List[MessageReferences]] = None
103
103
  additional_input: Optional[List[Message]] = None
104
104
  reasoning_steps: Optional[List[ReasoningStep]] = None
@@ -119,10 +119,10 @@ class RunCompletedEvent(BaseAgentRunEvent):
119
119
  content_type: str = "str"
120
120
  reasoning_content: Optional[str] = None
121
121
  citations: Optional[Citations] = None
122
- images: Optional[List[ImageArtifact]] = None # Images attached to the response
123
- videos: Optional[List[VideoArtifact]] = None # Videos attached to the response
124
- audio: Optional[List[AudioArtifact]] = None # Audio attached to the response
125
- response_audio: Optional[AudioResponse] = None # Model audio response
122
+ images: Optional[List[Image]] = None # Images attached to the response
123
+ videos: Optional[List[Video]] = None # Videos attached to the response
124
+ audio: Optional[List[Audio]] = None # Audio attached to the response
125
+ response_audio: Optional[Audio] = None # Model audio response
126
126
  references: Optional[List[MessageReferences]] = None
127
127
  additional_input: Optional[List[Message]] = None
128
128
  reasoning_steps: Optional[List[ReasoningStep]] = None
@@ -203,9 +203,9 @@ class ToolCallCompletedEvent(BaseAgentRunEvent):
203
203
  event: str = RunEvent.tool_call_completed.value
204
204
  tool: Optional[ToolExecution] = None
205
205
  content: Optional[Any] = None
206
- images: Optional[List[ImageArtifact]] = None # Images produced by the tool call
207
- videos: Optional[List[VideoArtifact]] = None # Videos produced by the tool call
208
- audio: Optional[List[AudioArtifact]] = None # Audio produced by the tool call
206
+ images: Optional[List[Image]] = None # Images produced by the tool call
207
+ videos: Optional[List[Video]] = None # Videos produced by the tool call
208
+ audio: Optional[List[Audio]] = None # Audio produced by the tool call
209
209
 
210
210
 
211
211
  @dataclass
@@ -306,9 +306,9 @@ class RunInput:
306
306
  """
307
307
 
308
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
309
+ images: Optional[Sequence[Image]] = None
310
+ videos: Optional[Sequence[Video]] = None
311
+ audios: Optional[Sequence[Audio]] = None
312
312
  files: Optional[Sequence[File]] = None
313
313
 
314
314
  def to_dict(self) -> Dict[str, Any]:
@@ -345,15 +345,15 @@ class RunInput:
345
345
  """Create RunInput from dictionary"""
346
346
  images = None
347
347
  if data.get("images"):
348
- images = [ImageArtifact.model_validate(img_data) for img_data in data["images"]]
348
+ images = [Image.model_validate(img_data) for img_data in data["images"]]
349
349
 
350
350
  videos = None
351
351
  if data.get("videos"):
352
- videos = [VideoArtifact.model_validate(vid_data) for vid_data in data["videos"]]
352
+ videos = [Video.model_validate(vid_data) for vid_data in data["videos"]]
353
353
 
354
354
  audios = None
355
355
  if data.get("audios"):
356
- audios = [AudioArtifact.model_validate(aud_data) for aud_data in data["audios"]]
356
+ audios = [Audio.model_validate(aud_data) for aud_data in data["audios"]]
357
357
 
358
358
  files = None
359
359
  if data.get("files"):
@@ -389,10 +389,10 @@ class RunOutput:
389
389
 
390
390
  tools: Optional[List[ToolExecution]] = None
391
391
 
392
- images: Optional[List[ImageArtifact]] = None # Images attached to the response
393
- videos: Optional[List[VideoArtifact]] = None # Videos attached to the response
394
- audio: Optional[List[AudioArtifact]] = None # Audio attached to the response
395
- response_audio: Optional[AudioResponse] = None # Model audio response
392
+ images: Optional[List[Image]] = None # Images attached to the response
393
+ videos: Optional[List[Video]] = None # Videos attached to the response
394
+ audio: Optional[List[Audio]] = None # Audio attached to the response
395
+ response_audio: Optional[Audio] = None # Model audio response
396
396
 
397
397
  # Input media and messages from user
398
398
  input: Optional[RunInput] = None
@@ -487,7 +487,7 @@ class RunOutput:
487
487
  if self.images is not None:
488
488
  _dict["images"] = []
489
489
  for img in self.images:
490
- if isinstance(img, ImageArtifact):
490
+ if isinstance(img, Image):
491
491
  _dict["images"].append(img.to_dict())
492
492
  else:
493
493
  _dict["images"].append(img)
@@ -495,7 +495,7 @@ class RunOutput:
495
495
  if self.videos is not None:
496
496
  _dict["videos"] = []
497
497
  for vid in self.videos:
498
- if isinstance(vid, VideoArtifact):
498
+ if isinstance(vid, Video):
499
499
  _dict["videos"].append(vid.to_dict())
500
500
  else:
501
501
  _dict["videos"].append(vid)
@@ -503,13 +503,13 @@ class RunOutput:
503
503
  if self.audio is not None:
504
504
  _dict["audio"] = []
505
505
  for aud in self.audio:
506
- if isinstance(aud, AudioArtifact):
506
+ if isinstance(aud, Audio):
507
507
  _dict["audio"].append(aud.to_dict())
508
508
  else:
509
509
  _dict["audio"].append(aud)
510
510
 
511
511
  if self.response_audio is not None:
512
- if isinstance(self.response_audio, AudioResponse):
512
+ if isinstance(self.response_audio, Audio):
513
513
  _dict["response_audio"] = self.response_audio.to_dict()
514
514
  else:
515
515
  _dict["response_audio"] = self.response_audio
@@ -565,16 +565,16 @@ class RunOutput:
565
565
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
566
566
 
567
567
  images = data.pop("images", [])
568
- images = [ImageArtifact.model_validate(image) for image in images] if images else None
568
+ images = [Image.model_validate(image) for image in images] if images else None
569
569
 
570
570
  videos = data.pop("videos", [])
571
- videos = [VideoArtifact.model_validate(video) for video in videos] if videos else None
571
+ videos = [Video.model_validate(video) for video in videos] if videos else None
572
572
 
573
573
  audio = data.pop("audio", [])
574
- audio = [AudioArtifact.model_validate(audio) for audio in audio] if audio else None
574
+ audio = [Audio.model_validate(audio) for audio in audio] if audio else None
575
575
 
576
576
  response_audio = data.pop("response_audio", None)
577
- response_audio = AudioResponse.model_validate(response_audio) if response_audio else None
577
+ response_audio = Audio.model_validate(response_audio) if response_audio else None
578
578
 
579
579
  input_data = data.pop("input", None)
580
580
  input_obj = None
agno/run/base.py CHANGED
@@ -4,7 +4,7 @@ from typing import Any, Dict
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
7
- from agno.media import AudioArtifact, AudioResponse, ImageArtifact, VideoArtifact
7
+ from agno.media import Audio, Image, Video
8
8
  from agno.models.message import Citations, Message, MessageReferences
9
9
  from agno.models.metrics import Metrics
10
10
  from agno.models.response import ToolExecution
@@ -60,21 +60,15 @@ class BaseRunOutputEvent:
60
60
  if hasattr(self, "images") and self.images is not None:
61
61
  _dict["images"] = []
62
62
  for img in self.images:
63
- if isinstance(img, ImageArtifact):
63
+ if isinstance(img, Image):
64
64
  _dict["images"].append(img.to_dict())
65
65
  else:
66
66
  _dict["images"].append(img)
67
67
 
68
- if hasattr(self, "image") and self.image is not None:
69
- if isinstance(self.image, ImageArtifact):
70
- _dict["image"] = self.image.to_dict()
71
- else:
72
- _dict["image"] = self.image
73
-
74
68
  if hasattr(self, "videos") and self.videos is not None:
75
69
  _dict["videos"] = []
76
70
  for vid in self.videos:
77
- if isinstance(vid, VideoArtifact):
71
+ if isinstance(vid, Video):
78
72
  _dict["videos"].append(vid.to_dict())
79
73
  else:
80
74
  _dict["videos"].append(vid)
@@ -82,13 +76,13 @@ class BaseRunOutputEvent:
82
76
  if hasattr(self, "audio") and self.audio is not None:
83
77
  _dict["audio"] = []
84
78
  for aud in self.audio:
85
- if isinstance(aud, AudioArtifact):
79
+ if isinstance(aud, Audio):
86
80
  _dict["audio"].append(aud.to_dict())
87
81
  else:
88
82
  _dict["audio"].append(aud)
89
83
 
90
84
  if hasattr(self, "response_audio") and self.response_audio is not None:
91
- if isinstance(self.response_audio, AudioResponse):
85
+ if isinstance(self.response_audio, Audio):
92
86
  _dict["response_audio"] = self.response_audio.to_dict()
93
87
  else:
94
88
  _dict["response_audio"] = self.response_audio
@@ -140,23 +134,19 @@ class BaseRunOutputEvent:
140
134
 
141
135
  images = data.pop("images", None)
142
136
  if images:
143
- data["images"] = [ImageArtifact.model_validate(image) for image in images]
144
-
145
- image = data.pop("image", None)
146
- if image:
147
- data["image"] = ImageArtifact.model_validate(image)
137
+ data["images"] = [Image.model_validate(image) for image in images]
148
138
 
149
139
  videos = data.pop("videos", None)
150
140
  if videos:
151
- data["videos"] = [VideoArtifact.model_validate(video) for video in videos]
141
+ data["videos"] = [Video.model_validate(video) for video in videos]
152
142
 
153
143
  audio = data.pop("audio", None)
154
144
  if audio:
155
- data["audio"] = [AudioArtifact.model_validate(audio) for audio in audio]
145
+ data["audio"] = [Audio.model_validate(audio) for audio in audio]
156
146
 
157
147
  response_audio = data.pop("response_audio", None)
158
148
  if response_audio:
159
- data["response_audio"] = AudioResponse.model_validate(response_audio)
149
+ data["response_audio"] = Audio.model_validate(response_audio)
160
150
 
161
151
  additional_input = data.pop("additional_input", None)
162
152
  if additional_input is not None: