agno 2.0.0a1__py3-none-any.whl → 2.0.0rc1__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 +390 -33
- 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 +0 -6
- 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/integrations/discord/client.py +5 -1
- agno/knowledge/embedder/aws_bedrock.py +2 -2
- agno/models/anthropic/claude.py +2 -49
- agno/models/message.py +7 -6
- agno/os/app.py +158 -62
- 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 +255 -0
- agno/os/router.py +33 -7
- agno/os/routers/evals/evals.py +9 -5
- agno/os/routers/knowledge/knowledge.py +30 -7
- agno/os/routers/memory/memory.py +17 -8
- agno/os/routers/metrics/metrics.py +4 -2
- agno/os/routers/session/session.py +8 -3
- agno/os/settings.py +0 -1
- agno/run/agent.py +87 -2
- agno/run/cancel.py +0 -2
- agno/team/team.py +2 -2
- agno/tools/function.py +65 -7
- agno/tools/linear.py +1 -1
- agno/utils/gemini.py +31 -1
- agno/utils/models/claude.py +49 -0
- agno/utils/streamlit.py +454 -0
- agno/workflow/workflow.py +8 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/METADATA +1 -1
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/RECORD +50 -48
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/WHEEL +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/top_level.txt +0 -0
agno/tools/function.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from functools import partial
|
|
3
3
|
from importlib.metadata import version
|
|
4
|
-
from typing import Any, Callable, Dict, List, Literal, Optional, Type, TypeVar, get_type_hints
|
|
4
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Type, TypeVar, get_type_hints
|
|
5
5
|
|
|
6
6
|
from docstring_parser import parse
|
|
7
7
|
from packaging.version import Version
|
|
8
8
|
from pydantic import BaseModel, Field, validate_call
|
|
9
9
|
|
|
10
10
|
from agno.exceptions import AgentRunException
|
|
11
|
-
from agno.media import AudioArtifact, ImageArtifact, VideoArtifact
|
|
11
|
+
from agno.media import Audio, AudioArtifact, File, Image, ImageArtifact, Video, VideoArtifact
|
|
12
12
|
from agno.utils.log import log_debug, log_error, log_exception, log_warning
|
|
13
13
|
|
|
14
14
|
T = TypeVar("T")
|
|
@@ -125,6 +125,12 @@ class Function(BaseModel):
|
|
|
125
125
|
# The session state that the function is associated with
|
|
126
126
|
_session_state: Optional[Dict[str, Any]] = None
|
|
127
127
|
|
|
128
|
+
# Media context that the function is associated with
|
|
129
|
+
_images: Optional[Sequence[Image]] = None
|
|
130
|
+
_videos: Optional[Sequence[Video]] = None
|
|
131
|
+
_audios: Optional[Sequence[Audio]] = None
|
|
132
|
+
_files: Optional[Sequence[File]] = None
|
|
133
|
+
|
|
128
134
|
def to_dict(self) -> Dict[str, Any]:
|
|
129
135
|
return self.model_dump(
|
|
130
136
|
exclude_none=True,
|
|
@@ -150,13 +156,23 @@ class Function(BaseModel):
|
|
|
150
156
|
del type_hints["team"]
|
|
151
157
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
152
158
|
del type_hints["session_state"]
|
|
159
|
+
# Remove media parameters from type hints as they are injected automatically
|
|
160
|
+
if "images" in sig.parameters and "images" in type_hints:
|
|
161
|
+
del type_hints["images"]
|
|
162
|
+
if "videos" in sig.parameters and "videos" in type_hints:
|
|
163
|
+
del type_hints["videos"]
|
|
164
|
+
if "audios" in sig.parameters and "audios" in type_hints:
|
|
165
|
+
del type_hints["audios"]
|
|
166
|
+
if "files" in sig.parameters and "files" in type_hints:
|
|
167
|
+
del type_hints["files"]
|
|
153
168
|
# log_info(f"Type hints for {function_name}: {type_hints}")
|
|
154
169
|
|
|
155
170
|
# Filter out return type and only process parameters
|
|
156
171
|
param_type_hints = {
|
|
157
172
|
name: type_hints.get(name)
|
|
158
173
|
for name in sig.parameters
|
|
159
|
-
if name != "return"
|
|
174
|
+
if name != "return"
|
|
175
|
+
and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
# Parse docstring for parameters
|
|
@@ -183,14 +199,17 @@ class Function(BaseModel):
|
|
|
183
199
|
# See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
|
|
184
200
|
if strict:
|
|
185
201
|
parameters["required"] = [
|
|
186
|
-
name
|
|
202
|
+
name
|
|
203
|
+
for name in parameters["properties"]
|
|
204
|
+
if name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
|
|
187
205
|
]
|
|
188
206
|
else:
|
|
189
207
|
# Mark a field as required if it has no default value (this would include optional fields)
|
|
190
208
|
parameters["required"] = [
|
|
191
209
|
name
|
|
192
210
|
for name, param in sig.parameters.items()
|
|
193
|
-
if param.default == param.empty
|
|
211
|
+
if param.default == param.empty
|
|
212
|
+
and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
|
|
194
213
|
]
|
|
195
214
|
|
|
196
215
|
# log_debug(f"JSON schema for {function_name}: {parameters}")
|
|
@@ -241,10 +260,28 @@ class Function(BaseModel):
|
|
|
241
260
|
del type_hints["team"]
|
|
242
261
|
if "session_state" in sig.parameters and "session_state" in type_hints:
|
|
243
262
|
del type_hints["session_state"]
|
|
263
|
+
if "images" in sig.parameters and "images" in type_hints:
|
|
264
|
+
del type_hints["images"]
|
|
265
|
+
if "videos" in sig.parameters and "videos" in type_hints:
|
|
266
|
+
del type_hints["videos"]
|
|
267
|
+
if "audios" in sig.parameters and "audios" in type_hints:
|
|
268
|
+
del type_hints["audios"]
|
|
269
|
+
if "files" in sig.parameters and "files" in type_hints:
|
|
270
|
+
del type_hints["files"]
|
|
244
271
|
# log_info(f"Type hints for {self.name}: {type_hints}")
|
|
245
272
|
|
|
246
273
|
# Filter out return type and only process parameters
|
|
247
|
-
excluded_params = [
|
|
274
|
+
excluded_params = [
|
|
275
|
+
"return",
|
|
276
|
+
"agent",
|
|
277
|
+
"team",
|
|
278
|
+
"session_state",
|
|
279
|
+
"self",
|
|
280
|
+
"images",
|
|
281
|
+
"videos",
|
|
282
|
+
"audios",
|
|
283
|
+
"files",
|
|
284
|
+
]
|
|
248
285
|
if self.requires_user_input and self.user_input_fields:
|
|
249
286
|
if len(self.user_input_fields) == 0:
|
|
250
287
|
excluded_params.extend(list(type_hints.keys()))
|
|
@@ -357,7 +394,9 @@ class Function(BaseModel):
|
|
|
357
394
|
def process_schema_for_strict(self):
|
|
358
395
|
self.parameters["additionalProperties"] = False
|
|
359
396
|
self.parameters["required"] = [
|
|
360
|
-
name
|
|
397
|
+
name
|
|
398
|
+
for name in self.parameters["properties"]
|
|
399
|
+
if name not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self"]
|
|
361
400
|
]
|
|
362
401
|
|
|
363
402
|
def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
|
|
@@ -372,6 +411,14 @@ class Function(BaseModel):
|
|
|
372
411
|
del copy_entrypoint_args["team"]
|
|
373
412
|
if "session_state" in copy_entrypoint_args:
|
|
374
413
|
del copy_entrypoint_args["session_state"]
|
|
414
|
+
if "images" in copy_entrypoint_args:
|
|
415
|
+
del copy_entrypoint_args["images"]
|
|
416
|
+
if "videos" in copy_entrypoint_args:
|
|
417
|
+
del copy_entrypoint_args["videos"]
|
|
418
|
+
if "audios" in copy_entrypoint_args:
|
|
419
|
+
del copy_entrypoint_args["audios"]
|
|
420
|
+
if "files" in copy_entrypoint_args:
|
|
421
|
+
del copy_entrypoint_args["files"]
|
|
375
422
|
args_str = str(copy_entrypoint_args)
|
|
376
423
|
|
|
377
424
|
kwargs_str = str(sorted((call_args or {}).items()))
|
|
@@ -554,6 +601,17 @@ class FunctionCall(BaseModel):
|
|
|
554
601
|
# Check if the entrypoint has an fc argument
|
|
555
602
|
if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
556
603
|
entrypoint_args["fc"] = self
|
|
604
|
+
|
|
605
|
+
# Check if the entrypoint has media arguments
|
|
606
|
+
if "images" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
607
|
+
entrypoint_args["images"] = self.function._images
|
|
608
|
+
if "videos" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
609
|
+
entrypoint_args["videos"] = self.function._videos
|
|
610
|
+
if "audios" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
611
|
+
entrypoint_args["audios"] = self.function._audios
|
|
612
|
+
if "files" in signature(self.function.entrypoint).parameters: # type: ignore
|
|
613
|
+
entrypoint_args["files"] = self.function._files
|
|
614
|
+
|
|
557
615
|
return entrypoint_args
|
|
558
616
|
|
|
559
617
|
def _build_hook_args(self, hook: Callable, name: str, func: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
|
agno/tools/linear.py
CHANGED
|
@@ -202,7 +202,7 @@ class LinearTools(Toolkit):
|
|
|
202
202
|
"""
|
|
203
203
|
|
|
204
204
|
query = """
|
|
205
|
-
mutation IssueCreate ($title: String!, $description: String!, $teamId: String!, $projectId: String
|
|
205
|
+
mutation IssueCreate ($title: String!, $description: String!, $teamId: String!, $projectId: String, $assigneeId: String){
|
|
206
206
|
issueCreate(
|
|
207
207
|
input: { title: $title, description: $description, teamId: $teamId, projectId: $projectId, assigneeId: $assigneeId}
|
|
208
208
|
) {
|
agno/utils/gemini.py
CHANGED
|
@@ -175,7 +175,37 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
175
175
|
|
|
176
176
|
elif schema_type == "array" and "items" in schema_dict:
|
|
177
177
|
items = convert_schema(schema_dict["items"], root_schema)
|
|
178
|
-
|
|
178
|
+
min_items = schema_dict.get("minItems")
|
|
179
|
+
max_items = schema_dict.get("maxItems")
|
|
180
|
+
return Schema(
|
|
181
|
+
type=Type.ARRAY,
|
|
182
|
+
description=description,
|
|
183
|
+
items=items,
|
|
184
|
+
min_items=min_items,
|
|
185
|
+
max_items=max_items,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif schema_type == "string":
|
|
189
|
+
schema_kwargs = {
|
|
190
|
+
"type": Type.STRING,
|
|
191
|
+
"description": description,
|
|
192
|
+
"default": default,
|
|
193
|
+
}
|
|
194
|
+
if "format" in schema_dict:
|
|
195
|
+
schema_kwargs["format"] = schema_dict["format"]
|
|
196
|
+
return Schema(**schema_kwargs)
|
|
197
|
+
|
|
198
|
+
elif schema_type in ("integer", "number"):
|
|
199
|
+
schema_kwargs = {
|
|
200
|
+
"type": schema_type.upper(),
|
|
201
|
+
"description": description,
|
|
202
|
+
"default": default,
|
|
203
|
+
}
|
|
204
|
+
if "maximum" in schema_dict:
|
|
205
|
+
schema_kwargs["maximum"] = schema_dict["maximum"]
|
|
206
|
+
if "minimum" in schema_dict:
|
|
207
|
+
schema_kwargs["minimum"] = schema_dict["minimum"]
|
|
208
|
+
return Schema(**schema_kwargs)
|
|
179
209
|
|
|
180
210
|
elif schema_type == "" and "anyOf" in schema_dict:
|
|
181
211
|
any_of = []
|
agno/utils/models/claude.py
CHANGED
|
@@ -286,3 +286,52 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
286
286
|
|
|
287
287
|
chat_messages.append({"role": ROLE_MAP[message.role], "content": content}) # type: ignore
|
|
288
288
|
return chat_messages, " ".join(system_messages)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
292
|
+
"""
|
|
293
|
+
Transforms function definitions into a format accepted by the Anthropic API.
|
|
294
|
+
"""
|
|
295
|
+
if not tools:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
parsed_tools: List[Dict[str, Any]] = []
|
|
299
|
+
for tool_def in tools:
|
|
300
|
+
if tool_def.get("type", "") != "function":
|
|
301
|
+
parsed_tools.append(tool_def)
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
func_def = tool_def.get("function", {})
|
|
305
|
+
parameters: Dict[str, Any] = func_def.get("parameters", {})
|
|
306
|
+
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
307
|
+
required: List[str] = parameters.get("required", [])
|
|
308
|
+
required_params: List[str] = required
|
|
309
|
+
|
|
310
|
+
if not required_params:
|
|
311
|
+
for param_name, param_info in properties.items():
|
|
312
|
+
param_type = param_info.get("type", "")
|
|
313
|
+
param_type_list: List[str] = [param_type] if isinstance(param_type, str) else param_type or []
|
|
314
|
+
|
|
315
|
+
if "null" not in param_type_list:
|
|
316
|
+
required_params.append(param_name)
|
|
317
|
+
|
|
318
|
+
input_properties: Dict[str, Any] = {}
|
|
319
|
+
for param_name, param_info in properties.items():
|
|
320
|
+
# Preserve the complete schema structure for complex types
|
|
321
|
+
input_properties[param_name] = param_info.copy()
|
|
322
|
+
|
|
323
|
+
# Ensure description is present (default to empty if missing)
|
|
324
|
+
if "description" not in input_properties[param_name]:
|
|
325
|
+
input_properties[param_name]["description"] = ""
|
|
326
|
+
|
|
327
|
+
tool = {
|
|
328
|
+
"name": func_def.get("name") or "",
|
|
329
|
+
"description": func_def.get("description") or "",
|
|
330
|
+
"input_schema": {
|
|
331
|
+
"type": parameters.get("type", "object"),
|
|
332
|
+
"properties": input_properties,
|
|
333
|
+
"required": required_params,
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
parsed_tools.append(tool)
|
|
337
|
+
return parsed_tools
|