agno 2.0.0rc2__py3-none-any.whl → 2.0.1__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 +78 -135
- agno/knowledge/knowledge.py +1 -1
- agno/knowledge/reranker/__init__.py +9 -0
- agno/media.py +269 -268
- agno/models/base.py +13 -48
- agno/models/google/gemini.py +11 -10
- agno/models/message.py +4 -4
- agno/models/ollama/chat.py +1 -1
- agno/models/openai/chat.py +33 -14
- agno/models/response.py +5 -5
- agno/os/app.py +8 -5
- agno/run/agent.py +28 -28
- agno/run/base.py +9 -19
- agno/run/team.py +24 -24
- agno/run/workflow.py +16 -16
- agno/team/team.py +72 -154
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +7 -4
- agno/tools/desi_vocal.py +2 -2
- agno/tools/e2b.py +6 -6
- agno/tools/eleven_labs.py +3 -3
- agno/tools/fal.py +4 -4
- agno/tools/function.py +7 -7
- agno/tools/giphy.py +2 -2
- agno/tools/lumalab.py +3 -3
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +2 -2
- agno/tools/models_labs.py +5 -5
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/replicate.py +7 -7
- agno/utils/events.py +5 -5
- agno/utils/gemini.py +1 -1
- agno/utils/mcp.py +3 -3
- agno/utils/models/aws_claude.py +1 -1
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/vectordb/lancedb/lance_db.py +82 -25
- agno/workflow/step.py +7 -7
- agno/workflow/types.py +13 -13
- agno/workflow/workflow.py +28 -28
- {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/METADATA +140 -1
- {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/RECORD +50 -50
- agno-2.0.1.dist-info/licenses/LICENSE +201 -0
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.0.1.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.0.1.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,
|
|
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[
|
|
47
|
-
response_image: Optional[
|
|
48
|
-
response_video: Optional[
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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(
|
|
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
|
-
#
|
|
1108
|
-
|
|
1109
|
-
|
|
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,
|
agno/models/google/gemini.py
CHANGED
|
@@ -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,
|
|
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
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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[
|
|
75
|
-
image_output: Optional[
|
|
76
|
-
video_output: Optional[
|
|
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
|
agno/models/ollama/chat.py
CHANGED
|
@@ -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.
|
|
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):
|
agno/models/openai/chat.py
CHANGED
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
|
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[
|
|
90
|
+
audio: Optional[Audio] = None
|
|
91
91
|
|
|
92
92
|
# Unified media fields for LLM-generated and tool-generated media artifacts
|
|
93
|
-
images: Optional[List[
|
|
94
|
-
videos: Optional[List[
|
|
95
|
-
audios: Optional[List[
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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[
|
|
101
|
-
image: Optional[
|
|
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[
|
|
123
|
-
videos: Optional[List[
|
|
124
|
-
audio: Optional[List[
|
|
125
|
-
response_audio: Optional[
|
|
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[
|
|
207
|
-
videos: Optional[List[
|
|
208
|
-
audio: Optional[List[
|
|
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[
|
|
310
|
-
videos: Optional[Sequence[
|
|
311
|
-
audios: Optional[Sequence[
|
|
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 = [
|
|
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 = [
|
|
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 = [
|
|
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[
|
|
393
|
-
videos: Optional[List[
|
|
394
|
-
audio: Optional[List[
|
|
395
|
-
response_audio: Optional[
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 = [
|
|
568
|
+
images = [Image.model_validate(image) for image in images] if images else None
|
|
569
569
|
|
|
570
570
|
videos = data.pop("videos", [])
|
|
571
|
-
videos = [
|
|
571
|
+
videos = [Video.model_validate(video) for video in videos] if videos else None
|
|
572
572
|
|
|
573
573
|
audio = data.pop("audio", [])
|
|
574
|
-
audio = [
|
|
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 =
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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"] = [
|
|
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"] = [
|
|
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"] = [
|
|
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"] =
|
|
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:
|