agno 2.0.6__py3-none-any.whl → 2.0.8__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 (52) hide show
  1. agno/agent/agent.py +94 -48
  2. agno/db/migrations/v1_to_v2.py +140 -11
  3. agno/knowledge/chunking/semantic.py +33 -6
  4. agno/knowledge/embedder/sentence_transformer.py +3 -3
  5. agno/knowledge/knowledge.py +152 -31
  6. agno/knowledge/types.py +8 -0
  7. agno/media.py +2 -0
  8. agno/models/base.py +38 -9
  9. agno/models/cometapi/__init__.py +5 -0
  10. agno/models/cometapi/cometapi.py +57 -0
  11. agno/models/google/gemini.py +4 -8
  12. agno/models/llama_cpp/__init__.py +5 -0
  13. agno/models/llama_cpp/llama_cpp.py +22 -0
  14. agno/models/nexus/__init__.py +1 -1
  15. agno/models/nexus/nexus.py +2 -5
  16. agno/models/ollama/chat.py +24 -1
  17. agno/models/openai/chat.py +2 -7
  18. agno/models/openai/responses.py +21 -17
  19. agno/os/app.py +4 -10
  20. agno/os/interfaces/agui/agui.py +2 -2
  21. agno/os/interfaces/agui/utils.py +81 -18
  22. agno/os/interfaces/slack/slack.py +2 -2
  23. agno/os/interfaces/whatsapp/whatsapp.py +2 -2
  24. agno/os/router.py +3 -4
  25. agno/os/routers/evals/evals.py +1 -1
  26. agno/os/routers/memory/memory.py +1 -1
  27. agno/os/schema.py +3 -4
  28. agno/os/utils.py +55 -12
  29. agno/reasoning/default.py +3 -1
  30. agno/run/agent.py +4 -0
  31. agno/run/team.py +3 -1
  32. agno/session/agent.py +8 -5
  33. agno/session/team.py +14 -10
  34. agno/team/team.py +239 -115
  35. agno/tools/decorator.py +4 -2
  36. agno/tools/function.py +43 -4
  37. agno/tools/mcp.py +61 -38
  38. agno/tools/memori.py +1 -53
  39. agno/utils/events.py +7 -1
  40. agno/utils/gemini.py +147 -19
  41. agno/utils/models/claude.py +9 -0
  42. agno/utils/print_response/agent.py +16 -0
  43. agno/utils/print_response/team.py +16 -0
  44. agno/vectordb/base.py +2 -2
  45. agno/vectordb/langchaindb/langchaindb.py +5 -7
  46. agno/vectordb/llamaindex/llamaindexdb.py +25 -6
  47. agno/workflow/workflow.py +59 -15
  48. {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/METADATA +1 -1
  49. {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/RECORD +52 -48
  50. {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/WHEEL +0 -0
  51. {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/licenses/LICENSE +0 -0
  52. {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/top_level.txt +0 -0
agno/tools/decorator.py CHANGED
@@ -250,8 +250,10 @@ def tool(*args, **kwargs) -> Union[Function, Callable[[F], Function]]:
250
250
  if kwargs.get("stop_after_tool_call") is True:
251
251
  if "show_result" not in kwargs or kwargs.get("show_result") is None:
252
252
  tool_config["show_result"] = True
253
-
254
- return Function(**tool_config)
253
+ function = Function(**tool_config)
254
+ # Determine parameters for the function
255
+ function.process_entrypoint()
256
+ return function
255
257
 
256
258
  # Handle both @tool and @tool() cases
257
259
  if len(args) == 1 and callable(args[0]) and not kwargs:
agno/tools/function.py CHANGED
@@ -124,6 +124,8 @@ class Function(BaseModel):
124
124
  _team: Optional[Any] = None
125
125
  # The session state that the function is associated with
126
126
  _session_state: Optional[Dict[str, Any]] = None
127
+ # The dependencies that the function is associated with
128
+ _dependencies: Optional[Dict[str, Any]] = None
127
129
 
128
130
  # Media context that the function is associated with
129
131
  _images: Optional[Sequence[Image]] = None
@@ -165,6 +167,8 @@ class Function(BaseModel):
165
167
  del type_hints["audios"]
166
168
  if "files" in sig.parameters and "files" in type_hints:
167
169
  del type_hints["files"]
170
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
171
+ del type_hints["dependencies"]
168
172
  # log_info(f"Type hints for {function_name}: {type_hints}")
169
173
 
170
174
  # Filter out return type and only process parameters
@@ -172,7 +176,8 @@ class Function(BaseModel):
172
176
  name: type_hints.get(name)
173
177
  for name in sig.parameters
174
178
  if name != "return"
175
- and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
179
+ and name
180
+ not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files", "dependencies"]
176
181
  }
177
182
 
178
183
  # Parse docstring for parameters
@@ -201,7 +206,18 @@ class Function(BaseModel):
201
206
  parameters["required"] = [
202
207
  name
203
208
  for name in parameters["properties"]
204
- if name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
209
+ if name
210
+ not in [
211
+ "agent",
212
+ "team",
213
+ "session_state",
214
+ "self",
215
+ "images",
216
+ "videos",
217
+ "audios",
218
+ "files",
219
+ "dependencies",
220
+ ]
205
221
  ]
206
222
  else:
207
223
  # Mark a field as required if it has no default value (this would include optional fields)
@@ -209,7 +225,18 @@ class Function(BaseModel):
209
225
  name
210
226
  for name, param in sig.parameters.items()
211
227
  if param.default == param.empty
212
- and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
228
+ and name
229
+ not in [
230
+ "agent",
231
+ "team",
232
+ "session_state",
233
+ "self",
234
+ "images",
235
+ "videos",
236
+ "audios",
237
+ "files",
238
+ "dependencies",
239
+ ]
213
240
  ]
214
241
 
215
242
  # log_debug(f"JSON schema for {function_name}: {parameters}")
@@ -268,6 +295,8 @@ class Function(BaseModel):
268
295
  del type_hints["audios"]
269
296
  if "files" in sig.parameters and "files" in type_hints:
270
297
  del type_hints["files"]
298
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
299
+ del type_hints["dependencies"]
271
300
  # log_info(f"Type hints for {self.name}: {type_hints}")
272
301
 
273
302
  # Filter out return type and only process parameters
@@ -281,6 +310,7 @@ class Function(BaseModel):
281
310
  "videos",
282
311
  "audios",
283
312
  "files",
313
+ "dependencies",
284
314
  ]
285
315
  if self.requires_user_input and self.user_input_fields:
286
316
  if len(self.user_input_fields) == 0:
@@ -396,7 +426,8 @@ class Function(BaseModel):
396
426
  self.parameters["required"] = [
397
427
  name
398
428
  for name in self.parameters["properties"]
399
- if name not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self"]
429
+ if name
430
+ not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self", "dependencies"]
400
431
  ]
401
432
 
402
433
  def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
@@ -419,6 +450,8 @@ class Function(BaseModel):
419
450
  del copy_entrypoint_args["audios"]
420
451
  if "files" in copy_entrypoint_args:
421
452
  del copy_entrypoint_args["files"]
453
+ if "dependencies" in copy_entrypoint_args:
454
+ del copy_entrypoint_args["dependencies"]
422
455
  args_str = str(copy_entrypoint_args)
423
456
 
424
457
  kwargs_str = str(sorted((call_args or {}).items()))
@@ -599,6 +632,9 @@ class FunctionCall(BaseModel):
599
632
  # Check if the entrypoint has an session_state argument
600
633
  if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
601
634
  entrypoint_args["session_state"] = self.function._session_state
635
+ # Check if the entrypoint has an dependencies argument
636
+ if "dependencies" in signature(self.function.entrypoint).parameters: # type: ignore
637
+ entrypoint_args["dependencies"] = self.function._dependencies
602
638
  # Check if the entrypoint has an fc argument
603
639
  if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
604
640
  entrypoint_args["fc"] = self
@@ -629,6 +665,9 @@ class FunctionCall(BaseModel):
629
665
  # Check if the hook has an session_state argument
630
666
  if "session_state" in signature(hook).parameters:
631
667
  hook_args["session_state"] = self.function._session_state
668
+ # Check if the hook has an dependencies argument
669
+ if "dependencies" in signature(hook).parameters:
670
+ hook_args["dependencies"] = self.function._dependencies
632
671
 
633
672
  if "name" in signature(hook).parameters:
634
673
  hook_args["name"] = name
agno/tools/mcp.py CHANGED
@@ -8,7 +8,7 @@ from typing import Any, Dict, List, Literal, Optional, Union
8
8
 
9
9
  from agno.tools import Toolkit
10
10
  from agno.tools.function import Function
11
- from agno.utils.log import log_debug, log_info, log_warning, logger
11
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
12
12
  from agno.utils.mcp import get_entrypoint_for_tool
13
13
 
14
14
  try:
@@ -102,7 +102,7 @@ class MCPTools(Toolkit):
102
102
  transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
103
103
  server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = None,
104
104
  session: Optional[ClientSession] = None,
105
- timeout_seconds: int = 5,
105
+ timeout_seconds: int = 10,
106
106
  client=None,
107
107
  include_tools: Optional[list[str]] = None,
108
108
  exclude_tools: Optional[list[str]] = None,
@@ -338,12 +338,13 @@ class MCPTools(Toolkit):
338
338
  self.functions[f.name] = f
339
339
  log_debug(f"Function: {f.name} registered with {self.name}")
340
340
  except Exception as e:
341
- logger.error(f"Failed to register tool {tool.name}: {e}")
341
+ log_error(f"Failed to register tool {tool.name}: {e}")
342
342
 
343
343
  log_debug(f"{self.name} initialized with {len(filtered_tools)} tools")
344
344
  self._initialized = True
345
+
345
346
  except Exception as e:
346
- logger.error(f"Failed to get MCP tools: {e}")
347
+ log_error(f"Failed to get MCP tools: {e}")
347
348
  raise
348
349
 
349
350
 
@@ -372,6 +373,7 @@ class MultiMCPTools(Toolkit):
372
373
  client=None,
373
374
  include_tools: Optional[list[str]] = None,
374
375
  exclude_tools: Optional[list[str]] = None,
376
+ allow_partial_failure: bool = False,
375
377
  **kwargs,
376
378
  ):
377
379
  """
@@ -387,6 +389,7 @@ class MultiMCPTools(Toolkit):
387
389
  timeout_seconds: Timeout in seconds for managing timeouts for Client Session if Agent or Tool doesn't respond.
388
390
  include_tools: Optional list of tool names to include (if None, includes all).
389
391
  exclude_tools: Optional list of tool names to exclude (if None, excludes none).
392
+ allow_partial_failure: If True, allows toolkit to initialize even if some MCP servers fail to connect. If False, any failure will raise an exception.
390
393
  """
391
394
  super().__init__(name="MultiMCPTools", **kwargs)
392
395
 
@@ -445,12 +448,16 @@ class MultiMCPTools(Toolkit):
445
448
  self.server_params_list.append(StreamableHTTPClientParams(url=url))
446
449
 
447
450
  self._async_exit_stack = AsyncExitStack()
451
+ self._successful_connections = 0
452
+
448
453
  self._initialized = False
449
454
  self._connection_task = None
450
455
  self._active_contexts: list[Any] = []
451
456
  self._used_as_context_manager = False
452
457
 
453
458
  self._client = client
459
+ self._initialized = False
460
+ self.allow_partial_failure = allow_partial_failure
454
461
 
455
462
  def cleanup():
456
463
  """Cancel active connections"""
@@ -511,39 +518,53 @@ class MultiMCPTools(Toolkit):
511
518
  if self._initialized:
512
519
  return
513
520
 
521
+ server_connection_errors = []
522
+
514
523
  for server_params in self.server_params_list:
515
- # Handle stdio connections
516
- if isinstance(server_params, StdioServerParameters):
517
- stdio_transport = await self._async_exit_stack.enter_async_context(stdio_client(server_params))
518
- self._active_contexts.append(stdio_transport)
519
- read, write = stdio_transport
520
- session = await self._async_exit_stack.enter_async_context(
521
- ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
522
- )
523
- self._active_contexts.append(session)
524
- await self.initialize(session)
525
- # Handle SSE connections
526
- elif isinstance(server_params, SSEClientParams):
527
- client_connection = await self._async_exit_stack.enter_async_context(
528
- sse_client(**asdict(server_params))
529
- )
530
- self._active_contexts.append(client_connection)
531
- read, write = client_connection
532
- session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
533
- self._active_contexts.append(session)
534
- await self.initialize(session)
535
- # Handle Streamable HTTP connections
536
- elif isinstance(server_params, StreamableHTTPClientParams):
537
- client_connection = await self._async_exit_stack.enter_async_context(
538
- streamablehttp_client(**asdict(server_params))
539
- )
540
- self._active_contexts.append(client_connection)
541
- read, write = client_connection[0:2]
542
- session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
543
- self._active_contexts.append(session)
544
- await self.initialize(session)
524
+ try:
525
+ # Handle stdio connections
526
+ if isinstance(server_params, StdioServerParameters):
527
+ stdio_transport = await self._async_exit_stack.enter_async_context(stdio_client(server_params))
528
+ read, write = stdio_transport
529
+ session = await self._async_exit_stack.enter_async_context(
530
+ ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
531
+ )
532
+ await self.initialize(session)
533
+ self._successful_connections += 1
545
534
 
546
- self._initialized = True
535
+ # Handle SSE connections
536
+ elif isinstance(server_params, SSEClientParams):
537
+ client_connection = await self._async_exit_stack.enter_async_context(
538
+ sse_client(**asdict(server_params))
539
+ )
540
+ read, write = client_connection
541
+ session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
542
+ await self.initialize(session)
543
+ self._successful_connections += 1
544
+
545
+ # Handle Streamable HTTP connections
546
+ elif isinstance(server_params, StreamableHTTPClientParams):
547
+ client_connection = await self._async_exit_stack.enter_async_context(
548
+ streamablehttp_client(**asdict(server_params))
549
+ )
550
+ read, write = client_connection[0:2]
551
+ session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
552
+ await self.initialize(session)
553
+ self._successful_connections += 1
554
+
555
+ except Exception as e:
556
+ if not self.allow_partial_failure:
557
+ raise ValueError(f"MCP connection failed: {e}")
558
+
559
+ log_error(f"Failed to initialize MCP server with params {server_params}: {e}")
560
+ server_connection_errors.append(str(e))
561
+ continue
562
+
563
+ if self._successful_connections == 0 and server_connection_errors:
564
+ raise ValueError(f"All MCP connections failed: {server_connection_errors}")
565
+
566
+ if not self._initialized and self._successful_connections > 0:
567
+ self._initialized = True
547
568
 
548
569
  async def close(self) -> None:
549
570
  """Close the MCP connections and clean up resources"""
@@ -563,6 +584,8 @@ class MultiMCPTools(Toolkit):
563
584
  ):
564
585
  """Exit the async context manager."""
565
586
  await self._async_exit_stack.aclose()
587
+ self._initialized = False
588
+ self._successful_connections = 0
566
589
 
567
590
  async def initialize(self, session: ClientSession) -> None:
568
591
  """Initialize the MCP toolkit by getting available tools from the MCP server"""
@@ -602,10 +625,10 @@ class MultiMCPTools(Toolkit):
602
625
  self.functions[f.name] = f
603
626
  log_debug(f"Function: {f.name} registered with {self.name}")
604
627
  except Exception as e:
605
- logger.error(f"Failed to register tool {tool.name}: {e}")
628
+ log_error(f"Failed to register tool {tool.name}: {e}")
606
629
 
607
- log_debug(f"{self.name} initialized with {len(filtered_tools)} tools")
630
+ log_debug(f"{self.name} initialized with {len(filtered_tools)} tools from one MCP server")
608
631
  self._initialized = True
609
632
  except Exception as e:
610
- logger.error(f"Failed to get MCP tools: {e}")
633
+ log_error(f"Failed to get MCP tools: {e}")
611
634
  raise
agno/tools/memori.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  from typing import Any, Dict, List, Optional
3
3
 
4
- from agno.agent import Agent
5
4
  from agno.tools.toolkit import Toolkit
6
5
  from agno.utils.log import log_debug, log_error, log_info, log_warning
7
6
 
@@ -122,7 +121,6 @@ class MemoriTools(Toolkit):
122
121
 
123
122
  def search_memory(
124
123
  self,
125
- agent: Agent,
126
124
  query: str,
127
125
  limit: Optional[int] = None,
128
126
  ) -> str:
@@ -180,7 +178,7 @@ class MemoriTools(Toolkit):
180
178
  log_error(f"Error searching memory: {e}")
181
179
  return json.dumps({"success": False, "error": f"Memory search error: {str(e)}"})
182
180
 
183
- def record_conversation(self, agent: Agent, content: str) -> str:
181
+ def record_conversation(self, content: str) -> str:
184
182
  """
185
183
  Add important information or facts to memory.
186
184
 
@@ -222,7 +220,6 @@ class MemoriTools(Toolkit):
222
220
 
223
221
  def get_memory_stats(
224
222
  self,
225
- agent: Agent,
226
223
  ) -> str:
227
224
  """
228
225
  Get statistics about the memory system.
@@ -340,52 +337,3 @@ class MemoriTools(Toolkit):
340
337
  except Exception as e:
341
338
  log_error(f"Failed to disable memory system: {e}")
342
339
  return False
343
-
344
-
345
- def create_memori_search_tool(memori_toolkit: MemoriTools):
346
- """
347
- Create a standalone memory search function for use with Agno agents.
348
-
349
- This is a convenience function that creates a memory search tool similar
350
- to the pattern shown in the Memori example code.
351
-
352
- Args:
353
- memori_toolkit: An initialized MemoriTools instance
354
-
355
- Returns:
356
- Callable: A memory search function that can be used as an agent tool
357
-
358
- Example:
359
- ```python
360
- memori_tools = MemoriTools(database_connect="sqlite:///memory.db")
361
- search_tool = create_memori_search_tool(memori_tools)
362
-
363
- agent = Agent(
364
- model=OpenAIChat(),
365
- tools=[search_tool],
366
- description="Agent with memory search capability"
367
- )
368
- ```
369
- """
370
-
371
- def search_memory(query: str) -> str:
372
- """
373
- Search the agent's memory for past conversations and information.
374
-
375
- Args:
376
- query: What to search for in memory
377
-
378
- Returns:
379
- str: Search results or error message
380
- """
381
- try:
382
- if not query.strip():
383
- return "Please provide a search query"
384
-
385
- result = memori_toolkit._memory_tool.execute(query=query.strip())
386
- return str(result) if result else "No relevant memories found"
387
-
388
- except Exception as e:
389
- return f"Memory search error: {str(e)}"
390
-
391
- return search_memory
agno/utils/events.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, List, Optional
1
+ from typing import Any, Dict, List, Optional
2
2
 
3
3
  from agno.media import Audio, Image
4
4
  from agno.models.message import Citations
@@ -76,6 +76,7 @@ def create_team_run_completed_event(from_run_response: TeamRunOutput) -> TeamRun
76
76
  content_type=from_run_response.content_type, # type: ignore
77
77
  reasoning_content=from_run_response.reasoning_content, # type: ignore
78
78
  citations=from_run_response.citations, # type: ignore
79
+ model_provider_data=from_run_response.model_provider_data, # type: ignore
79
80
  images=from_run_response.images, # type: ignore
80
81
  videos=from_run_response.videos, # type: ignore
81
82
  audio=from_run_response.audio, # type: ignore
@@ -100,6 +101,7 @@ def create_run_completed_event(from_run_response: RunOutput) -> RunCompletedEven
100
101
  content_type=from_run_response.content_type, # type: ignore
101
102
  reasoning_content=from_run_response.reasoning_content, # type: ignore
102
103
  citations=from_run_response.citations, # type: ignore
104
+ model_provider_data=from_run_response.model_provider_data, # type: ignore
103
105
  images=from_run_response.images, # type: ignore
104
106
  videos=from_run_response.videos, # type: ignore
105
107
  audio=from_run_response.audio, # type: ignore
@@ -343,6 +345,7 @@ def create_run_output_content_event(
343
345
  content_type: Optional[str] = None,
344
346
  reasoning_content: Optional[str] = None,
345
347
  redacted_reasoning_content: Optional[str] = None,
348
+ model_provider_data: Optional[Dict[str, Any]] = None,
346
349
  citations: Optional[Citations] = None,
347
350
  response_audio: Optional[Audio] = None,
348
351
  image: Optional[Image] = None,
@@ -364,6 +367,7 @@ def create_run_output_content_event(
364
367
  additional_input=from_run_response.additional_input,
365
368
  reasoning_steps=from_run_response.reasoning_steps,
366
369
  reasoning_messages=from_run_response.reasoning_messages,
370
+ model_provider_data=model_provider_data,
367
371
  )
368
372
 
369
373
 
@@ -374,6 +378,7 @@ def create_team_run_output_content_event(
374
378
  reasoning_content: Optional[str] = None,
375
379
  redacted_reasoning_content: Optional[str] = None,
376
380
  citations: Optional[Citations] = None,
381
+ model_provider_data: Optional[Dict[str, Any]] = None,
377
382
  response_audio: Optional[Audio] = None,
378
383
  image: Optional[Image] = None,
379
384
  ) -> TeamRunContentEvent:
@@ -388,6 +393,7 @@ def create_team_run_output_content_event(
388
393
  content_type=content_type or "str",
389
394
  reasoning_content=thinking_combined,
390
395
  citations=citations,
396
+ model_provider_data=model_provider_data,
391
397
  response_audio=response_audio,
392
398
  image=image,
393
399
  references=from_run_response.references, # type: ignore