agno 2.2.0__py3-none-any.whl → 2.2.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 (71) hide show
  1. agno/agent/agent.py +751 -575
  2. agno/culture/manager.py +22 -24
  3. agno/db/async_postgres/__init__.py +1 -1
  4. agno/db/dynamo/dynamo.py +0 -2
  5. agno/db/firestore/firestore.py +0 -2
  6. agno/db/gcs_json/gcs_json_db.py +0 -4
  7. agno/db/gcs_json/utils.py +0 -24
  8. agno/db/in_memory/in_memory_db.py +0 -3
  9. agno/db/json/json_db.py +4 -10
  10. agno/db/json/utils.py +0 -24
  11. agno/db/mongo/mongo.py +0 -2
  12. agno/db/mysql/mysql.py +0 -3
  13. agno/db/postgres/__init__.py +1 -1
  14. agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
  15. agno/db/postgres/postgres.py +7 -10
  16. agno/db/postgres/utils.py +106 -2
  17. agno/db/redis/redis.py +0 -2
  18. agno/db/singlestore/singlestore.py +0 -3
  19. agno/db/sqlite/__init__.py +2 -1
  20. agno/db/sqlite/async_sqlite.py +2269 -0
  21. agno/db/sqlite/sqlite.py +0 -2
  22. agno/db/sqlite/utils.py +96 -0
  23. agno/db/surrealdb/surrealdb.py +0 -6
  24. agno/knowledge/knowledge.py +14 -3
  25. agno/knowledge/reader/pptx_reader.py +101 -0
  26. agno/knowledge/reader/reader_factory.py +30 -0
  27. agno/knowledge/reader/tavily_reader.py +194 -0
  28. agno/knowledge/types.py +1 -0
  29. agno/memory/manager.py +28 -25
  30. agno/models/anthropic/claude.py +63 -6
  31. agno/models/base.py +255 -36
  32. agno/models/response.py +69 -0
  33. agno/os/router.py +7 -5
  34. agno/os/routers/memory/memory.py +2 -1
  35. agno/os/routers/memory/schemas.py +5 -2
  36. agno/os/schema.py +26 -20
  37. agno/os/utils.py +9 -2
  38. agno/run/agent.py +28 -30
  39. agno/run/base.py +17 -1
  40. agno/run/team.py +28 -29
  41. agno/run/workflow.py +32 -17
  42. agno/session/agent.py +3 -0
  43. agno/session/summary.py +4 -1
  44. agno/session/team.py +1 -1
  45. agno/team/team.py +620 -374
  46. agno/tools/dalle.py +2 -4
  47. agno/tools/eleven_labs.py +23 -25
  48. agno/tools/function.py +40 -0
  49. agno/tools/mcp/__init__.py +10 -0
  50. agno/tools/mcp/mcp.py +324 -0
  51. agno/tools/mcp/multi_mcp.py +347 -0
  52. agno/tools/mcp/params.py +24 -0
  53. agno/tools/slack.py +18 -3
  54. agno/tools/tavily.py +146 -0
  55. agno/utils/agent.py +366 -1
  56. agno/utils/mcp.py +92 -2
  57. agno/utils/media.py +166 -1
  58. agno/utils/message.py +60 -0
  59. agno/utils/print_response/workflow.py +17 -1
  60. agno/utils/team.py +89 -1
  61. agno/workflow/step.py +0 -1
  62. agno/workflow/types.py +10 -15
  63. agno/workflow/workflow.py +86 -1
  64. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/METADATA +31 -25
  65. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/RECORD +68 -64
  66. agno/db/async_postgres/schemas.py +0 -139
  67. agno/db/async_postgres/utils.py +0 -347
  68. agno/tools/mcp.py +0 -679
  69. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/WHEEL +0 -0
  70. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/licenses/LICENSE +0 -0
  71. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/top_level.txt +0 -0
agno/memory/manager.py CHANGED
@@ -89,8 +89,6 @@ class MemoryManager:
89
89
  self.add_memories = add_memories
90
90
  self.clear_memories = clear_memories
91
91
  self.debug_mode = debug_mode
92
- self._tools_for_model: Optional[List[Dict[str, Any]]] = None
93
- self._functions_for_model: Optional[Dict[str, Function]] = None
94
92
 
95
93
  def get_model(self) -> Model:
96
94
  if self.model is None:
@@ -710,23 +708,26 @@ class MemoryManager:
710
708
  return sorted_memories_list
711
709
 
712
710
  # --Memory Manager Functions--
713
- def determine_tools_for_model(self, tools: List[Callable]) -> None:
711
+ def determine_tools_for_model(self, tools: List[Callable]) -> List[Union[Function, dict]]:
714
712
  # Have to reset each time, because of different user IDs
715
- self._tools_for_model = []
716
- self._functions_for_model = {}
713
+ _function_names = []
714
+ _functions: List[Union[Function, dict]] = []
717
715
 
718
716
  for tool in tools:
719
717
  try:
720
718
  function_name = tool.__name__
721
- if function_name not in self._functions_for_model:
722
- func = Function.from_callable(tool, strict=True) # type: ignore
723
- func.strict = True
724
- self._functions_for_model[func.name] = func
725
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
726
- log_debug(f"Added function {func.name}")
719
+ if function_name in _function_names:
720
+ continue
721
+ _function_names.append(function_name)
722
+ func = Function.from_callable(tool, strict=True) # type: ignore
723
+ func.strict = True
724
+ _functions.append(func)
725
+ log_debug(f"Added function {func.name}")
727
726
  except Exception as e:
728
727
  log_warning(f"Could not add function {tool}: {e}")
729
728
 
729
+ return _functions
730
+
730
731
  def get_system_message(
731
732
  self,
732
733
  existing_memories: Optional[List[Dict[str, Any]]] = None,
@@ -833,7 +834,7 @@ class MemoryManager:
833
834
 
834
835
  model_copy = deepcopy(self.model)
835
836
  # Update the Model (set defaults, add logit etc.)
836
- self.determine_tools_for_model(
837
+ _tools = self.determine_tools_for_model(
837
838
  self._get_db_tools(
838
839
  user_id,
839
840
  db,
@@ -862,8 +863,7 @@ class MemoryManager:
862
863
  # Generate a response from the Model (includes running function calls)
863
864
  response = model_copy.response(
864
865
  messages=messages_for_model,
865
- tools=self._tools_for_model,
866
- functions=self._functions_for_model,
866
+ tools=_tools,
867
867
  )
868
868
 
869
869
  if response.tool_calls is not None and len(response.tool_calls) > 0:
@@ -897,7 +897,7 @@ class MemoryManager:
897
897
  model_copy = deepcopy(self.model)
898
898
  # Update the Model (set defaults, add logit etc.)
899
899
  if isinstance(db, AsyncBaseDb):
900
- self.determine_tools_for_model(
900
+ _tools = self.determine_tools_for_model(
901
901
  await self._aget_db_tools(
902
902
  user_id,
903
903
  db,
@@ -911,7 +911,7 @@ class MemoryManager:
911
911
  ),
912
912
  )
913
913
  else:
914
- self.determine_tools_for_model(
914
+ _tools = self.determine_tools_for_model(
915
915
  self._get_db_tools(
916
916
  user_id,
917
917
  db,
@@ -940,8 +940,7 @@ class MemoryManager:
940
940
  # Generate a response from the Model (includes running function calls)
941
941
  response = await model_copy.aresponse(
942
942
  messages=messages_for_model,
943
- tools=self._tools_for_model,
944
- functions=self._functions_for_model,
943
+ tools=_tools,
945
944
  )
946
945
 
947
946
  if response.tool_calls is not None and len(response.tool_calls) > 0:
@@ -969,7 +968,7 @@ class MemoryManager:
969
968
 
970
969
  model_copy = deepcopy(self.model)
971
970
  # Update the Model (set defaults, add logit etc.)
972
- self.determine_tools_for_model(
971
+ _tools = self.determine_tools_for_model(
973
972
  self._get_db_tools(
974
973
  user_id,
975
974
  db,
@@ -997,8 +996,7 @@ class MemoryManager:
997
996
  # Generate a response from the Model (includes running function calls)
998
997
  response = model_copy.response(
999
998
  messages=messages_for_model,
1000
- tools=self._tools_for_model,
1001
- functions=self._functions_for_model,
999
+ tools=_tools,
1002
1000
  )
1003
1001
 
1004
1002
  if response.tool_calls is not None and len(response.tool_calls) > 0:
@@ -1027,7 +1025,7 @@ class MemoryManager:
1027
1025
  model_copy = deepcopy(self.model)
1028
1026
  # Update the Model (set defaults, add logit etc.)
1029
1027
  if isinstance(db, AsyncBaseDb):
1030
- self.determine_tools_for_model(
1028
+ _tools = self.determine_tools_for_model(
1031
1029
  await self._aget_db_tools(
1032
1030
  user_id,
1033
1031
  db,
@@ -1039,7 +1037,7 @@ class MemoryManager:
1039
1037
  ),
1040
1038
  )
1041
1039
  else:
1042
- self.determine_tools_for_model(
1040
+ _tools = self.determine_tools_for_model(
1043
1041
  self._get_db_tools(
1044
1042
  user_id,
1045
1043
  db,
@@ -1067,8 +1065,7 @@ class MemoryManager:
1067
1065
  # Generate a response from the Model (includes running function calls)
1068
1066
  response = await model_copy.aresponse(
1069
1067
  messages=messages_for_model,
1070
- tools=self._tools_for_model,
1071
- functions=self._functions_for_model,
1068
+ tools=_tools,
1072
1069
  )
1073
1070
 
1074
1071
  if response.tool_calls is not None and len(response.tool_calls) > 0:
@@ -1132,6 +1129,9 @@ class MemoryManager:
1132
1129
  """
1133
1130
  from agno.db.base import UserMemory
1134
1131
 
1132
+ if memory == "":
1133
+ return "Can't update memory with empty string. Use the delete memory function if available."
1134
+
1135
1135
  try:
1136
1136
  db.upsert_user_memory(
1137
1137
  UserMemory(
@@ -1251,6 +1251,9 @@ class MemoryManager:
1251
1251
  """
1252
1252
  from agno.db.base import UserMemory
1253
1253
 
1254
+ if memory == "":
1255
+ return "Can't update memory with empty string. Use the delete memory function if available."
1256
+
1254
1257
  try:
1255
1258
  if isinstance(db, AsyncBaseDb):
1256
1259
  await db.upsert_user_memory(
@@ -86,6 +86,12 @@ class Claude(Model):
86
86
  request_params: Optional[Dict[str, Any]] = None
87
87
  mcp_servers: Optional[List[MCPServerConfiguration]] = None
88
88
 
89
+ # Skills configuration
90
+ skills: Optional[List[Dict[str, str]]] = (
91
+ None # e.g., [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
92
+ )
93
+ betas: Optional[List[str]] = None # Enables specific experimental or newly released features.
94
+
89
95
  # Client parameters
90
96
  api_key: Optional[str] = None
91
97
  default_headers: Optional[Dict[str, Any]] = None
@@ -101,6 +107,9 @@ class Claude(Model):
101
107
  # Validate thinking support immediately at model creation
102
108
  if self.thinking:
103
109
  self._validate_thinking_support()
110
+ # Set up skills configuration if skills are enabled
111
+ if self.skills:
112
+ self._setup_skills_configuration()
104
113
 
105
114
  def _get_client_params(self) -> Dict[str, Any]:
106
115
  client_params: Dict[str, Any] = {}
@@ -159,6 +168,26 @@ class Claude(Model):
159
168
  f"For more information, see: https://docs.anthropic.com/en/docs/about-claude/models/overview"
160
169
  )
161
170
 
171
+ def _setup_skills_configuration(self) -> None:
172
+ """
173
+ Set up configuration for Claude Agent Skills.
174
+ Automatically configures betas array with required values.
175
+
176
+ Skills enable document creation capabilities (PowerPoint, Excel, Word, PDF).
177
+ For more information, see: https://docs.claude.com/en/docs/agents-and-tools/agent-skills/quickstart
178
+ """
179
+ # Required betas for skills
180
+ required_betas = ["code-execution-2025-08-25", "skills-2025-10-02"]
181
+
182
+ # Initialize or merge betas
183
+ if self.betas is None:
184
+ self.betas = required_betas
185
+ else:
186
+ # Add required betas if not present
187
+ for beta in required_betas:
188
+ if beta not in self.betas:
189
+ self.betas.append(beta)
190
+
162
191
  def get_request_params(self) -> Dict[str, Any]:
163
192
  """
164
193
  Generate keyword arguments for API requests.
@@ -184,6 +213,9 @@ class Claude(Model):
184
213
  _request_params["mcp_servers"] = [
185
214
  {k: v for k, v in asdict(server).items() if v is not None} for server in self.mcp_servers
186
215
  ]
216
+ if self.skills:
217
+ _request_params["betas"] = self.betas
218
+ _request_params["container"] = {"skills": self.skills}
187
219
  if self.request_params:
188
220
  _request_params.update(self.request_params)
189
221
 
@@ -213,6 +245,15 @@ class Claude(Model):
213
245
  else:
214
246
  request_kwargs["system"] = [{"text": system_message, "type": "text"}]
215
247
 
248
+ # Add code execution tool if skills are enabled
249
+ if self.skills:
250
+ code_execution_tool = {"type": "code_execution_20250825", "name": "code_execution"}
251
+ if tools:
252
+ # Add code_execution to existing tools, code execution is needed for generating and processing files
253
+ tools = tools + [code_execution_tool]
254
+ else:
255
+ tools = [code_execution_tool]
256
+
216
257
  if tools:
217
258
  request_kwargs["tools"] = format_tools_for_model(tools)
218
259
 
@@ -239,12 +280,12 @@ class Claude(Model):
239
280
  chat_messages, system_message = format_messages(messages)
240
281
  request_kwargs = self._prepare_request_kwargs(system_message, tools)
241
282
 
242
- if self.mcp_servers is not None:
283
+ if self.mcp_servers is not None or self.skills is not None:
243
284
  assistant_message.metrics.start_timer()
244
285
  provider_response = self.get_client().beta.messages.create(
245
286
  model=self.id,
246
287
  messages=chat_messages, # type: ignore
247
- **self.get_request_params(),
288
+ **request_kwargs,
248
289
  )
249
290
  else:
250
291
  assistant_message.metrics.start_timer()
@@ -306,7 +347,7 @@ class Claude(Model):
306
347
  if run_response and run_response.metrics:
307
348
  run_response.metrics.set_time_to_first_token()
308
349
 
309
- if self.mcp_servers is not None:
350
+ if self.mcp_servers is not None or self.skills is not None:
310
351
  assistant_message.metrics.start_timer()
311
352
  with self.get_client().beta.messages.stream(
312
353
  model=self.id,
@@ -361,12 +402,12 @@ class Claude(Model):
361
402
  chat_messages, system_message = format_messages(messages)
362
403
  request_kwargs = self._prepare_request_kwargs(system_message, tools)
363
404
 
364
- if self.mcp_servers is not None:
405
+ if self.mcp_servers is not None or self.skills is not None:
365
406
  assistant_message.metrics.start_timer()
366
407
  provider_response = await self.get_async_client().beta.messages.create(
367
408
  model=self.id,
368
409
  messages=chat_messages, # type: ignore
369
- **self.get_request_params(),
410
+ **request_kwargs,
370
411
  )
371
412
  else:
372
413
  assistant_message.metrics.start_timer()
@@ -425,7 +466,7 @@ class Claude(Model):
425
466
  chat_messages, system_message = format_messages(messages)
426
467
  request_kwargs = self._prepare_request_kwargs(system_message, tools)
427
468
 
428
- if self.mcp_servers is not None:
469
+ if self.mcp_servers is not None or self.skills is not None:
429
470
  assistant_message.metrics.start_timer()
430
471
  async with self.get_async_client().beta.messages.stream(
431
472
  model=self.id,
@@ -542,6 +583,22 @@ class Claude(Model):
542
583
  if response.usage is not None:
543
584
  model_response.response_usage = self._get_metrics(response.usage)
544
585
 
586
+ # Extract file IDs if skills are enabled
587
+ if self.skills and response.content:
588
+ file_ids: List[str] = []
589
+ for block in response.content:
590
+ if block.type == "bash_code_execution_tool_result":
591
+ if hasattr(block, "content") and hasattr(block.content, "content"):
592
+ if isinstance(block.content.content, list):
593
+ for output_block in block.content.content:
594
+ if hasattr(output_block, "file_id"):
595
+ file_ids.append(output_block.file_id)
596
+
597
+ if file_ids:
598
+ if model_response.provider_data is None:
599
+ model_response.provider_data = {}
600
+ model_response.provider_data["file_ids"] = file_ids
601
+
545
602
  return model_response
546
603
 
547
604
  def _parse_provider_response_delta(