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.
Files changed (50) hide show
  1. agno/agent/agent.py +390 -33
  2. agno/api/agent.py +2 -2
  3. agno/api/evals.py +2 -2
  4. agno/api/os.py +1 -1
  5. agno/api/settings.py +2 -2
  6. agno/api/team.py +2 -2
  7. agno/db/dynamo/dynamo.py +0 -6
  8. agno/db/firestore/firestore.py +0 -6
  9. agno/db/in_memory/in_memory_db.py +0 -6
  10. agno/db/json/json_db.py +0 -6
  11. agno/db/mongo/mongo.py +0 -6
  12. agno/db/mysql/utils.py +0 -1
  13. agno/db/postgres/postgres.py +0 -10
  14. agno/db/postgres/utils.py +0 -1
  15. agno/db/redis/redis.py +0 -4
  16. agno/db/singlestore/singlestore.py +0 -10
  17. agno/db/singlestore/utils.py +0 -1
  18. agno/db/sqlite/sqlite.py +0 -4
  19. agno/db/sqlite/utils.py +0 -1
  20. agno/integrations/discord/client.py +5 -1
  21. agno/knowledge/embedder/aws_bedrock.py +2 -2
  22. agno/models/anthropic/claude.py +2 -49
  23. agno/models/message.py +7 -6
  24. agno/os/app.py +158 -62
  25. agno/os/interfaces/agui/agui.py +1 -1
  26. agno/os/interfaces/agui/utils.py +16 -9
  27. agno/os/interfaces/slack/slack.py +2 -3
  28. agno/os/interfaces/whatsapp/whatsapp.py +2 -3
  29. agno/os/mcp.py +255 -0
  30. agno/os/router.py +33 -7
  31. agno/os/routers/evals/evals.py +9 -5
  32. agno/os/routers/knowledge/knowledge.py +30 -7
  33. agno/os/routers/memory/memory.py +17 -8
  34. agno/os/routers/metrics/metrics.py +4 -2
  35. agno/os/routers/session/session.py +8 -3
  36. agno/os/settings.py +0 -1
  37. agno/run/agent.py +87 -2
  38. agno/run/cancel.py +0 -2
  39. agno/team/team.py +2 -2
  40. agno/tools/function.py +65 -7
  41. agno/tools/linear.py +1 -1
  42. agno/utils/gemini.py +31 -1
  43. agno/utils/models/claude.py +49 -0
  44. agno/utils/streamlit.py +454 -0
  45. agno/workflow/workflow.py +8 -1
  46. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/METADATA +1 -1
  47. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/RECORD +50 -48
  48. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/WHEEL +0 -0
  49. {agno-2.0.0a1.dist-info → agno-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  50. {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" and name not in ["agent", "team", "session_state", "self"]
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 for name in parameters["properties"] if name not in ["agent", "team", "session_state", "self"]
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 and name not in ["agent", "team", "session_state", "self"]
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 = ["return", "agent", "team", "session_state", "self"]
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 for name in self.parameters["properties"] if name not in ["agent", "team", "session_state", "self"]
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!, $assigneeId: 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
- return Schema(type=Type.ARRAY, description=description, items=items)
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 = []
@@ -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