agno 2.3.16__py3-none-any.whl → 2.3.18__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/__init__.py +2 -0
- agno/agent/agent.py +4 -53
- agno/agent/remote.py +351 -0
- agno/client/__init__.py +3 -0
- agno/client/os.py +2669 -0
- agno/db/base.py +20 -0
- agno/db/mongo/async_mongo.py +11 -0
- agno/db/mongo/mongo.py +10 -0
- agno/db/mysql/async_mysql.py +9 -0
- agno/db/mysql/mysql.py +9 -0
- agno/db/postgres/async_postgres.py +9 -0
- agno/db/postgres/postgres.py +9 -0
- agno/db/postgres/utils.py +3 -2
- agno/db/sqlite/async_sqlite.py +9 -0
- agno/db/sqlite/sqlite.py +11 -1
- agno/exceptions.py +23 -0
- agno/knowledge/chunking/semantic.py +123 -46
- agno/knowledge/reader/csv_reader.py +1 -1
- agno/knowledge/reader/field_labeled_csv_reader.py +1 -1
- agno/knowledge/reader/json_reader.py +1 -1
- agno/models/google/gemini.py +5 -0
- agno/os/app.py +108 -25
- agno/os/auth.py +25 -1
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +13 -13
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +23 -16
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +6 -6
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +29 -6
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +651 -79
- agno/os/router.py +125 -18
- agno/os/routers/agents/router.py +65 -22
- agno/os/routers/agents/schema.py +16 -4
- agno/os/routers/database.py +5 -0
- agno/os/routers/evals/evals.py +93 -11
- agno/os/routers/evals/utils.py +6 -6
- agno/os/routers/knowledge/knowledge.py +104 -16
- agno/os/routers/memory/memory.py +124 -7
- agno/os/routers/metrics/metrics.py +21 -4
- agno/os/routers/session/session.py +141 -12
- agno/os/routers/teams/router.py +40 -14
- agno/os/routers/teams/schema.py +12 -4
- agno/os/routers/traces/traces.py +54 -4
- agno/os/routers/workflows/router.py +223 -117
- agno/os/routers/workflows/schema.py +65 -1
- agno/os/schema.py +38 -12
- agno/os/utils.py +87 -166
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +484 -0
- agno/run/workflow.py +1 -0
- agno/team/__init__.py +2 -0
- agno/team/remote.py +287 -0
- agno/team/team.py +25 -54
- agno/tracing/exporter.py +10 -6
- agno/tracing/setup.py +2 -1
- agno/utils/agent.py +58 -1
- agno/utils/http.py +68 -20
- agno/utils/os.py +0 -0
- agno/utils/remote.py +23 -0
- agno/vectordb/chroma/chromadb.py +452 -16
- agno/vectordb/pgvector/pgvector.py +7 -0
- agno/vectordb/redis/redisdb.py +1 -1
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +2 -2
- agno/workflow/remote.py +222 -0
- agno/workflow/types.py +0 -73
- agno/workflow/workflow.py +119 -68
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/METADATA +1 -1
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/RECORD +76 -66
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/WHEEL +0 -0
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.16.dist-info → agno-2.3.18.dist-info}/top_level.txt +0 -0
agno/team/remote.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Literal, Optional, Sequence, Tuple, Union, overload
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from agno.media import Audio, File, Image, Video
|
|
8
|
+
from agno.models.base import Model
|
|
9
|
+
from agno.models.message import Message
|
|
10
|
+
from agno.remote.base import BaseRemote, RemoteDb, RemoteKnowledge
|
|
11
|
+
from agno.run.agent import RunOutputEvent
|
|
12
|
+
from agno.run.team import TeamRunOutput, TeamRunOutputEvent
|
|
13
|
+
from agno.utils.agent import validate_input
|
|
14
|
+
from agno.utils.log import log_warning
|
|
15
|
+
from agno.utils.remote import serialize_input
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from agno.os.routers.teams.schema import TeamResponse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RemoteTeam(BaseRemote):
|
|
22
|
+
# Private cache for team config with TTL: (config, timestamp)
|
|
23
|
+
_cached_team_config: Optional[Tuple["TeamResponse", float]] = None
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
base_url: str,
|
|
28
|
+
team_id: str,
|
|
29
|
+
timeout: float = 300.0,
|
|
30
|
+
config_ttl: float = 300.0,
|
|
31
|
+
):
|
|
32
|
+
"""Initialize AgentOSRunner for local or remote execution.
|
|
33
|
+
|
|
34
|
+
For remote execution, provide base_url and team_id.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
base_url: Base URL for remote AgentOS instance (e.g., "http://localhost:7777")
|
|
38
|
+
team_id: ID of remote team
|
|
39
|
+
timeout: Request timeout in seconds (default: 300)
|
|
40
|
+
config_ttl: Time-to-live for cached config in seconds (default: 300)
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(base_url, timeout, config_ttl)
|
|
43
|
+
self.team_id = team_id
|
|
44
|
+
self._cached_team_config = None
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def id(self) -> str:
|
|
48
|
+
return self.team_id
|
|
49
|
+
|
|
50
|
+
async def get_team_config(self) -> "TeamResponse":
|
|
51
|
+
"""Get the team config from remote (always fetches fresh)."""
|
|
52
|
+
return await self.client.aget_team(self.team_id)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def _team_config(self) -> "TeamResponse":
|
|
56
|
+
"""Get the team config from remote, cached with TTL."""
|
|
57
|
+
from agno.os.routers.teams.schema import TeamResponse
|
|
58
|
+
|
|
59
|
+
current_time = time.time()
|
|
60
|
+
|
|
61
|
+
# Check if cache is valid
|
|
62
|
+
if self._cached_team_config is not None:
|
|
63
|
+
config, cached_at = self._cached_team_config
|
|
64
|
+
if current_time - cached_at < self.config_ttl:
|
|
65
|
+
return config
|
|
66
|
+
|
|
67
|
+
# Fetch fresh config
|
|
68
|
+
config: TeamResponse = self.client.get_team(self.team_id) # type: ignore
|
|
69
|
+
self._cached_team_config = (config, current_time)
|
|
70
|
+
return config
|
|
71
|
+
|
|
72
|
+
def refresh_config(self) -> "TeamResponse":
|
|
73
|
+
"""Force refresh the cached team config."""
|
|
74
|
+
from agno.os.routers.teams.schema import TeamResponse
|
|
75
|
+
|
|
76
|
+
config: TeamResponse = self.client.get_team(self.team_id) # type: ignore
|
|
77
|
+
self._cached_team_config = (config, time.time())
|
|
78
|
+
return config
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def name(self) -> Optional[str]:
|
|
82
|
+
if self._team_config is not None:
|
|
83
|
+
return self._team_config.name
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def description(self) -> Optional[str]:
|
|
88
|
+
if self._team_config is not None:
|
|
89
|
+
return self._team_config.description
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def role(self) -> Optional[str]:
|
|
94
|
+
if self._team_config is not None:
|
|
95
|
+
return self._team_config.role
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def tools(self) -> Optional[List[Dict[str, Any]]]:
|
|
100
|
+
if self._team_config is not None:
|
|
101
|
+
try:
|
|
102
|
+
return json.loads(self._team_config.tools["tools"]) if self._team_config.tools else None
|
|
103
|
+
except Exception as e:
|
|
104
|
+
log_warning(f"Failed to load tools for team {self.team_id}: {e}")
|
|
105
|
+
return None
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def db(self) -> Optional[RemoteDb]:
|
|
110
|
+
if self._team_config is not None and self._team_config.db_id is not None:
|
|
111
|
+
return RemoteDb.from_config(
|
|
112
|
+
db_id=self._team_config.db_id,
|
|
113
|
+
client=self.client,
|
|
114
|
+
config=self._config,
|
|
115
|
+
)
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def knowledge(self) -> Optional[RemoteKnowledge]:
|
|
120
|
+
"""Whether the team has knowledge enabled."""
|
|
121
|
+
if self._team_config is not None and self._team_config.knowledge is not None:
|
|
122
|
+
return RemoteKnowledge(
|
|
123
|
+
client=self.client,
|
|
124
|
+
contents_db=RemoteDb(
|
|
125
|
+
id=self._team_config.knowledge.get("db_id"), # type: ignore
|
|
126
|
+
client=self.client,
|
|
127
|
+
knowledge_table_name=self._team_config.knowledge.get("knowledge_table"),
|
|
128
|
+
)
|
|
129
|
+
if self._team_config.knowledge.get("db_id") is not None
|
|
130
|
+
else None,
|
|
131
|
+
)
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def model(self) -> Optional[Model]:
|
|
136
|
+
# We don't expose the remote team's models, since they can't be used by other services in AgentOS.
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def user_id(self) -> Optional[str]:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
@overload
|
|
144
|
+
async def arun(
|
|
145
|
+
self,
|
|
146
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
147
|
+
*,
|
|
148
|
+
stream: Literal[False] = False,
|
|
149
|
+
user_id: Optional[str] = None,
|
|
150
|
+
session_id: Optional[str] = None,
|
|
151
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
152
|
+
audio: Optional[Sequence[Audio]] = None,
|
|
153
|
+
images: Optional[Sequence[Image]] = None,
|
|
154
|
+
videos: Optional[Sequence[Video]] = None,
|
|
155
|
+
files: Optional[Sequence[File]] = None,
|
|
156
|
+
stream_events: Optional[bool] = None,
|
|
157
|
+
retries: Optional[int] = None,
|
|
158
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
159
|
+
add_history_to_context: Optional[bool] = None,
|
|
160
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
161
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
162
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
163
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
164
|
+
auth_token: Optional[str] = None,
|
|
165
|
+
**kwargs: Any,
|
|
166
|
+
) -> TeamRunOutput: ...
|
|
167
|
+
|
|
168
|
+
@overload
|
|
169
|
+
def arun(
|
|
170
|
+
self,
|
|
171
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
172
|
+
*,
|
|
173
|
+
stream: Literal[True] = True,
|
|
174
|
+
user_id: Optional[str] = None,
|
|
175
|
+
session_id: Optional[str] = None,
|
|
176
|
+
audio: Optional[Sequence[Audio]] = None,
|
|
177
|
+
images: Optional[Sequence[Image]] = None,
|
|
178
|
+
videos: Optional[Sequence[Video]] = None,
|
|
179
|
+
files: Optional[Sequence[File]] = None,
|
|
180
|
+
stream_events: Optional[bool] = None,
|
|
181
|
+
retries: Optional[int] = None,
|
|
182
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
183
|
+
add_history_to_context: Optional[bool] = None,
|
|
184
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
185
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
186
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
187
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
188
|
+
auth_token: Optional[str] = None,
|
|
189
|
+
**kwargs: Any,
|
|
190
|
+
) -> AsyncIterator[TeamRunOutputEvent]: ...
|
|
191
|
+
|
|
192
|
+
def arun( # type: ignore
|
|
193
|
+
self,
|
|
194
|
+
input: Union[str, List, Dict, Message, BaseModel, List[Message]],
|
|
195
|
+
*,
|
|
196
|
+
stream: Optional[bool] = None,
|
|
197
|
+
user_id: Optional[str] = None,
|
|
198
|
+
session_id: Optional[str] = None,
|
|
199
|
+
session_state: Optional[Dict[str, Any]] = None,
|
|
200
|
+
audio: Optional[Sequence[Audio]] = None,
|
|
201
|
+
images: Optional[Sequence[Image]] = None,
|
|
202
|
+
videos: Optional[Sequence[Video]] = None,
|
|
203
|
+
files: Optional[Sequence[File]] = None,
|
|
204
|
+
stream_events: Optional[bool] = None,
|
|
205
|
+
retries: Optional[int] = None,
|
|
206
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
207
|
+
add_history_to_context: Optional[bool] = None,
|
|
208
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
209
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
210
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
211
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
212
|
+
auth_token: Optional[str] = None,
|
|
213
|
+
**kwargs: Any,
|
|
214
|
+
) -> Union[
|
|
215
|
+
TeamRunOutput,
|
|
216
|
+
AsyncIterator[RunOutputEvent],
|
|
217
|
+
]:
|
|
218
|
+
validated_input = validate_input(input)
|
|
219
|
+
serialized_input = serialize_input(validated_input)
|
|
220
|
+
headers = self._get_auth_headers(auth_token)
|
|
221
|
+
|
|
222
|
+
if stream:
|
|
223
|
+
# Handle streaming response
|
|
224
|
+
return self.get_client().run_team_stream( # type: ignore
|
|
225
|
+
team_id=self.team_id,
|
|
226
|
+
message=serialized_input,
|
|
227
|
+
session_id=session_id,
|
|
228
|
+
user_id=user_id,
|
|
229
|
+
audio=audio,
|
|
230
|
+
images=images,
|
|
231
|
+
videos=videos,
|
|
232
|
+
files=files,
|
|
233
|
+
session_state=session_state,
|
|
234
|
+
stream_events=stream_events,
|
|
235
|
+
retries=retries,
|
|
236
|
+
knowledge_filters=knowledge_filters,
|
|
237
|
+
add_history_to_context=add_history_to_context,
|
|
238
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
239
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
240
|
+
dependencies=dependencies,
|
|
241
|
+
metadata=metadata,
|
|
242
|
+
headers=headers,
|
|
243
|
+
**kwargs,
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
return self.get_client().run_team( # type: ignore
|
|
247
|
+
team_id=self.team_id,
|
|
248
|
+
message=serialized_input,
|
|
249
|
+
session_id=session_id,
|
|
250
|
+
user_id=user_id,
|
|
251
|
+
audio=audio,
|
|
252
|
+
images=images,
|
|
253
|
+
videos=videos,
|
|
254
|
+
files=files,
|
|
255
|
+
session_state=session_state,
|
|
256
|
+
stream_events=stream_events,
|
|
257
|
+
retries=retries,
|
|
258
|
+
knowledge_filters=knowledge_filters,
|
|
259
|
+
add_history_to_context=add_history_to_context,
|
|
260
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
261
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
262
|
+
dependencies=dependencies,
|
|
263
|
+
metadata=metadata,
|
|
264
|
+
headers=headers,
|
|
265
|
+
**kwargs,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
async def cancel_run(self, run_id: str, auth_token: Optional[str] = None) -> bool:
|
|
269
|
+
"""Cancel a running team execution.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
run_id (str): The run_id to cancel.
|
|
273
|
+
auth_token: Optional JWT token for authentication.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
bool: True if the run was found and marked for cancellation, False otherwise.
|
|
277
|
+
"""
|
|
278
|
+
headers = self._get_auth_headers(auth_token)
|
|
279
|
+
try:
|
|
280
|
+
await self.get_client().cancel_team_run(
|
|
281
|
+
team_id=self.team_id,
|
|
282
|
+
run_id=run_id,
|
|
283
|
+
headers=headers,
|
|
284
|
+
)
|
|
285
|
+
return True
|
|
286
|
+
except Exception:
|
|
287
|
+
return False
|
agno/team/team.py
CHANGED
|
@@ -103,11 +103,12 @@ from agno.utils.agent import (
|
|
|
103
103
|
set_session_name_util,
|
|
104
104
|
store_media_util,
|
|
105
105
|
update_session_state_util,
|
|
106
|
+
validate_input,
|
|
106
107
|
validate_media_object_id,
|
|
107
108
|
wait_for_open_threads,
|
|
108
109
|
wait_for_thread_tasks_stream,
|
|
109
110
|
)
|
|
110
|
-
from agno.utils.common import is_typed_dict
|
|
111
|
+
from agno.utils.common import is_typed_dict
|
|
111
112
|
from agno.utils.events import (
|
|
112
113
|
add_team_error_event,
|
|
113
114
|
create_team_parser_model_response_completed_event,
|
|
@@ -794,56 +795,6 @@ class Team:
|
|
|
794
795
|
if telemetry_env is not None:
|
|
795
796
|
self.telemetry = telemetry_env.lower() == "true"
|
|
796
797
|
|
|
797
|
-
def _validate_input(
|
|
798
|
-
self, input: Union[str, List, Dict, Message, BaseModel]
|
|
799
|
-
) -> Union[str, List, Dict, Message, BaseModel]:
|
|
800
|
-
"""Parse and validate input against input_schema if provided, otherwise return input as-is"""
|
|
801
|
-
if self.input_schema is None:
|
|
802
|
-
return input # Return input unchanged if no schema is set
|
|
803
|
-
|
|
804
|
-
# Handle Message objects - extract content
|
|
805
|
-
if isinstance(input, Message):
|
|
806
|
-
input = input.content # type: ignore
|
|
807
|
-
|
|
808
|
-
# If input is a string, convert it to a dict
|
|
809
|
-
if isinstance(input, str):
|
|
810
|
-
import json
|
|
811
|
-
|
|
812
|
-
try:
|
|
813
|
-
input = json.loads(input)
|
|
814
|
-
except Exception as e:
|
|
815
|
-
raise ValueError(f"Failed to parse input. Is it a valid JSON string?: {e}")
|
|
816
|
-
|
|
817
|
-
# Case 1: Message is already a BaseModel instance
|
|
818
|
-
if isinstance(input, BaseModel):
|
|
819
|
-
if isinstance(input, self.input_schema):
|
|
820
|
-
try:
|
|
821
|
-
return input
|
|
822
|
-
except Exception as e:
|
|
823
|
-
raise ValueError(f"BaseModel validation failed: {str(e)}")
|
|
824
|
-
else:
|
|
825
|
-
# Different BaseModel types
|
|
826
|
-
raise ValueError(f"Expected {self.input_schema.__name__} but got {type(input).__name__}")
|
|
827
|
-
|
|
828
|
-
# Case 2: Message is a dict
|
|
829
|
-
elif isinstance(input, dict):
|
|
830
|
-
try:
|
|
831
|
-
# Check if the schema is a TypedDict
|
|
832
|
-
if is_typed_dict(self.input_schema):
|
|
833
|
-
validated_dict = validate_typed_dict(input, self.input_schema)
|
|
834
|
-
return validated_dict
|
|
835
|
-
else:
|
|
836
|
-
validated_model = self.input_schema(**input)
|
|
837
|
-
return validated_model
|
|
838
|
-
except Exception as e:
|
|
839
|
-
raise ValueError(f"Failed to parse dict into {self.input_schema.__name__}: {str(e)}")
|
|
840
|
-
|
|
841
|
-
# Case 3: Other types not supported for structured input
|
|
842
|
-
else:
|
|
843
|
-
raise ValueError(
|
|
844
|
-
f"Cannot validate {type(input)} against input_schema. Expected dict or {self.input_schema.__name__} instance."
|
|
845
|
-
)
|
|
846
|
-
|
|
847
798
|
def _initialize_member(self, member: Union["Team", Agent], debug_mode: Optional[bool] = None) -> None:
|
|
848
799
|
# Set debug mode for all members
|
|
849
800
|
if debug_mode:
|
|
@@ -2077,7 +2028,7 @@ class Team:
|
|
|
2077
2028
|
background_tasks: BackgroundTasks = background_tasks # type: ignore
|
|
2078
2029
|
|
|
2079
2030
|
# Validate input against input_schema if provided
|
|
2080
|
-
validated_input = self.
|
|
2031
|
+
validated_input = validate_input(input, self.input_schema)
|
|
2081
2032
|
|
|
2082
2033
|
# Normalise hook & guardails
|
|
2083
2034
|
if not self._hooks_normalised:
|
|
@@ -2275,6 +2226,22 @@ class Team:
|
|
|
2275
2226
|
return generator_wrapper(cancelled_run_error) # type: ignore
|
|
2276
2227
|
else:
|
|
2277
2228
|
return run_response
|
|
2229
|
+
except KeyboardInterrupt:
|
|
2230
|
+
# Handle KeyboardInterrupt - stop retries immediately
|
|
2231
|
+
run_response.status = RunStatus.cancelled
|
|
2232
|
+
run_response.content = "Operation cancelled by user"
|
|
2233
|
+
if stream:
|
|
2234
|
+
cancelled_run_error = handle_event(
|
|
2235
|
+
create_team_run_cancelled_event(
|
|
2236
|
+
from_run_response=run_response, reason="Operation cancelled by user"
|
|
2237
|
+
),
|
|
2238
|
+
run_response,
|
|
2239
|
+
events_to_skip=self.events_to_skip,
|
|
2240
|
+
store_events=self.store_events,
|
|
2241
|
+
)
|
|
2242
|
+
return generator_wrapper(cancelled_run_error) # type: ignore
|
|
2243
|
+
else:
|
|
2244
|
+
return run_response
|
|
2278
2245
|
except (InputCheckError, OutputCheckError) as e:
|
|
2279
2246
|
run_response.status = RunStatus.error
|
|
2280
2247
|
|
|
@@ -3139,7 +3106,7 @@ class Team:
|
|
|
3139
3106
|
background_tasks: BackgroundTasks = background_tasks # type: ignore
|
|
3140
3107
|
|
|
3141
3108
|
# Validate input against input_schema if provided
|
|
3142
|
-
validated_input = self.
|
|
3109
|
+
validated_input = validate_input(input, self.input_schema)
|
|
3143
3110
|
|
|
3144
3111
|
# Normalise hook & guardails
|
|
3145
3112
|
if not self._hooks_normalised:
|
|
@@ -5416,6 +5383,10 @@ class Team:
|
|
|
5416
5383
|
system_message_content += f"{indent * ' '} - {_tool.name}\n"
|
|
5417
5384
|
elif callable(_tool):
|
|
5418
5385
|
system_message_content += f"{indent * ' '} - {_tool.__name__}\n"
|
|
5386
|
+
elif isinstance(_tool, dict) and "name" in _tool and _tool.get("name") is not None:
|
|
5387
|
+
system_message_content += f"{indent * ' '} - {_tool['name']}\n"
|
|
5388
|
+
else:
|
|
5389
|
+
system_message_content += f"{indent * ' '} - {str(_tool)}\n"
|
|
5419
5390
|
|
|
5420
5391
|
return system_message_content
|
|
5421
5392
|
|
|
@@ -7297,7 +7268,7 @@ class Team:
|
|
|
7297
7268
|
|
|
7298
7269
|
# 7. Add member-level history for the member if enabled (because we won't load the session for the member, so history won't be loaded automatically)
|
|
7299
7270
|
history = None
|
|
7300
|
-
if member_agent.add_history_to_context:
|
|
7271
|
+
if hasattr(member_agent, "add_history_to_context") and member_agent.add_history_to_context:
|
|
7301
7272
|
history = self._get_history_for_member_agent(session, member_agent)
|
|
7302
7273
|
if history:
|
|
7303
7274
|
if isinstance(member_agent_task, str):
|
agno/tracing/exporter.py
CHANGED
|
@@ -10,6 +10,7 @@ from opentelemetry.sdk.trace import ReadableSpan # type: ignore
|
|
|
10
10
|
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult # type: ignore
|
|
11
11
|
|
|
12
12
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
13
|
+
from agno.remote.base import RemoteDb
|
|
13
14
|
from agno.tracing.schemas import Span, create_trace_from_spans
|
|
14
15
|
from agno.utils.log import logger
|
|
15
16
|
|
|
@@ -17,7 +18,7 @@ from agno.utils.log import logger
|
|
|
17
18
|
class DatabaseSpanExporter(SpanExporter):
|
|
18
19
|
"""Custom OpenTelemetry SpanExporter that writes to Agno database"""
|
|
19
20
|
|
|
20
|
-
def __init__(self, db: Union[BaseDb, AsyncBaseDb]):
|
|
21
|
+
def __init__(self, db: Union[BaseDb, AsyncBaseDb, RemoteDb]):
|
|
21
22
|
"""
|
|
22
23
|
Initialize the DatabaseSpanExporter.
|
|
23
24
|
|
|
@@ -71,7 +72,10 @@ class DatabaseSpanExporter(SpanExporter):
|
|
|
71
72
|
spans_by_trace[converted_span.trace_id].append(converted_span)
|
|
72
73
|
|
|
73
74
|
# Handle async DB
|
|
74
|
-
if isinstance(self.db,
|
|
75
|
+
if isinstance(self.db, RemoteDb):
|
|
76
|
+
# Skipping remote database because it handles its own tracing
|
|
77
|
+
pass
|
|
78
|
+
elif isinstance(self.db, AsyncBaseDb):
|
|
75
79
|
self._export_async(spans_by_trace)
|
|
76
80
|
else:
|
|
77
81
|
# Synchronous database
|
|
@@ -90,10 +94,10 @@ class DatabaseSpanExporter(SpanExporter):
|
|
|
90
94
|
# Create trace record (aggregate of all spans)
|
|
91
95
|
trace = create_trace_from_spans(spans)
|
|
92
96
|
if trace:
|
|
93
|
-
self.db.upsert_trace(trace)
|
|
97
|
+
self.db.upsert_trace(trace) # type: ignore
|
|
94
98
|
|
|
95
99
|
# Create span records
|
|
96
|
-
self.db.create_spans(spans)
|
|
100
|
+
self.db.create_spans(spans) # type: ignore
|
|
97
101
|
|
|
98
102
|
except Exception as e:
|
|
99
103
|
logger.error(f"Failed to export sync traces: {e}", exc_info=True)
|
|
@@ -124,12 +128,12 @@ class DatabaseSpanExporter(SpanExporter):
|
|
|
124
128
|
# Create trace record (aggregate of all spans)
|
|
125
129
|
trace = create_trace_from_spans(spans)
|
|
126
130
|
if trace:
|
|
127
|
-
create_trace_result = self.db.upsert_trace(trace)
|
|
131
|
+
create_trace_result = self.db.upsert_trace(trace) # type: ignore
|
|
128
132
|
if create_trace_result is not None:
|
|
129
133
|
await create_trace_result
|
|
130
134
|
|
|
131
135
|
# Create span records
|
|
132
|
-
create_spans_result = self.db.create_spans(spans)
|
|
136
|
+
create_spans_result = self.db.create_spans(spans) # type: ignore
|
|
133
137
|
if create_spans_result is not None:
|
|
134
138
|
await create_spans_result
|
|
135
139
|
|
agno/tracing/setup.py
CHANGED
|
@@ -5,6 +5,7 @@ Setup helper functions for configuring Agno tracing.
|
|
|
5
5
|
from typing import Union
|
|
6
6
|
|
|
7
7
|
from agno.db.base import AsyncBaseDb, BaseDb
|
|
8
|
+
from agno.remote.base import RemoteDb
|
|
8
9
|
from agno.tracing.exporter import DatabaseSpanExporter
|
|
9
10
|
from agno.utils.log import logger
|
|
10
11
|
|
|
@@ -20,7 +21,7 @@ except ImportError:
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def setup_tracing(
|
|
23
|
-
db: Union[BaseDb, AsyncBaseDb],
|
|
24
|
+
db: Union[BaseDb, AsyncBaseDb, RemoteDb],
|
|
24
25
|
batch_processing: bool = False,
|
|
25
26
|
max_queue_size: int = 2048,
|
|
26
27
|
max_export_batch_size: int = 512,
|
agno/utils/agent.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from asyncio import Future, Task
|
|
2
|
-
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional, Sequence, Union
|
|
2
|
+
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional, Sequence, Type, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
3
5
|
|
|
4
6
|
from agno.media import Audio, File, Image, Video
|
|
5
7
|
from agno.models.message import Message
|
|
@@ -10,6 +12,7 @@ from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
|
|
|
10
12
|
from agno.run.team import RunOutputEvent as TeamRunOutputEvent
|
|
11
13
|
from agno.run.team import TeamRunOutput
|
|
12
14
|
from agno.session import AgentSession, TeamSession, WorkflowSession
|
|
15
|
+
from agno.utils.common import is_typed_dict, validate_typed_dict
|
|
13
16
|
from agno.utils.events import (
|
|
14
17
|
create_memory_update_completed_event,
|
|
15
18
|
create_memory_update_started_event,
|
|
@@ -936,3 +939,57 @@ async def aexecute_system_message(
|
|
|
936
939
|
return await system_message(**system_message_args)
|
|
937
940
|
else:
|
|
938
941
|
return system_message(**system_message_args)
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def validate_input(
|
|
945
|
+
input: Union[str, List, Dict, Message, BaseModel], input_schema: Optional[Type[BaseModel]] = None
|
|
946
|
+
) -> Union[str, List, Dict, Message, BaseModel]:
|
|
947
|
+
"""Parse and validate input against input_schema if provided, otherwise return input as-is"""
|
|
948
|
+
if input_schema is None:
|
|
949
|
+
return input # Return input unchanged if no schema is set
|
|
950
|
+
|
|
951
|
+
if input is None:
|
|
952
|
+
raise ValueError("Input required when input_schema is set")
|
|
953
|
+
|
|
954
|
+
# Handle Message objects - extract content
|
|
955
|
+
if isinstance(input, Message):
|
|
956
|
+
input = input.content # type: ignore
|
|
957
|
+
|
|
958
|
+
# If input is a string, convert it to a dict
|
|
959
|
+
if isinstance(input, str):
|
|
960
|
+
import json
|
|
961
|
+
|
|
962
|
+
try:
|
|
963
|
+
input = json.loads(input)
|
|
964
|
+
except Exception as e:
|
|
965
|
+
raise ValueError(f"Failed to parse input. Is it a valid JSON string?: {e}")
|
|
966
|
+
|
|
967
|
+
# Case 1: Message is already a BaseModel instance
|
|
968
|
+
if isinstance(input, BaseModel):
|
|
969
|
+
if isinstance(input, input_schema):
|
|
970
|
+
try:
|
|
971
|
+
return input
|
|
972
|
+
except Exception as e:
|
|
973
|
+
raise ValueError(f"BaseModel validation failed: {str(e)}")
|
|
974
|
+
else:
|
|
975
|
+
# Different BaseModel types
|
|
976
|
+
raise ValueError(f"Expected {input_schema.__name__} but got {type(input).__name__}")
|
|
977
|
+
|
|
978
|
+
# Case 2: Message is a dict
|
|
979
|
+
elif isinstance(input, dict):
|
|
980
|
+
try:
|
|
981
|
+
# Check if the schema is a TypedDict
|
|
982
|
+
if is_typed_dict(input_schema):
|
|
983
|
+
validated_dict = validate_typed_dict(input, input_schema)
|
|
984
|
+
return validated_dict
|
|
985
|
+
else:
|
|
986
|
+
validated_model = input_schema(**input)
|
|
987
|
+
return validated_model
|
|
988
|
+
except Exception as e:
|
|
989
|
+
raise ValueError(f"Failed to parse dict into {input_schema.__name__}: {str(e)}")
|
|
990
|
+
|
|
991
|
+
# Case 3: Other types not supported for structured input
|
|
992
|
+
else:
|
|
993
|
+
raise ValueError(
|
|
994
|
+
f"Cannot validate {type(input)} against input_schema. Expected dict or {input_schema.__name__} instance."
|
|
995
|
+
)
|