agno 1.7.4__py3-none-any.whl → 1.7.6__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 (51) hide show
  1. agno/agent/agent.py +28 -15
  2. agno/app/agui/async_router.py +5 -5
  3. agno/app/agui/sync_router.py +5 -5
  4. agno/app/agui/utils.py +84 -14
  5. agno/app/fastapi/app.py +1 -1
  6. agno/app/fastapi/async_router.py +67 -16
  7. agno/app/fastapi/sync_router.py +80 -14
  8. agno/document/chunking/row.py +39 -0
  9. agno/document/reader/base.py +0 -7
  10. agno/embedder/jina.py +73 -0
  11. agno/knowledge/agent.py +39 -2
  12. agno/knowledge/combined.py +1 -1
  13. agno/memory/agent.py +2 -2
  14. agno/memory/team.py +2 -2
  15. agno/models/aws/bedrock.py +311 -15
  16. agno/models/litellm/chat.py +12 -3
  17. agno/models/openai/chat.py +1 -22
  18. agno/models/openai/responses.py +5 -5
  19. agno/models/portkey/__init__.py +3 -0
  20. agno/models/portkey/portkey.py +88 -0
  21. agno/models/xai/xai.py +54 -0
  22. agno/run/v2/workflow.py +4 -0
  23. agno/storage/mysql.py +1 -0
  24. agno/storage/postgres.py +1 -0
  25. agno/storage/session/v2/workflow.py +29 -5
  26. agno/storage/singlestore.py +4 -1
  27. agno/storage/sqlite.py +0 -1
  28. agno/team/team.py +52 -22
  29. agno/tools/bitbucket.py +292 -0
  30. agno/tools/daytona.py +411 -63
  31. agno/tools/decorator.py +45 -2
  32. agno/tools/evm.py +123 -0
  33. agno/tools/function.py +16 -12
  34. agno/tools/linkup.py +54 -0
  35. agno/tools/mcp.py +10 -3
  36. agno/tools/mem0.py +15 -2
  37. agno/tools/postgres.py +175 -162
  38. agno/utils/log.py +16 -0
  39. agno/utils/pprint.py +2 -0
  40. agno/utils/string.py +14 -0
  41. agno/vectordb/pgvector/pgvector.py +4 -5
  42. agno/vectordb/surrealdb/__init__.py +3 -0
  43. agno/vectordb/surrealdb/surrealdb.py +493 -0
  44. agno/workflow/v2/workflow.py +144 -19
  45. agno/workflow/workflow.py +90 -63
  46. {agno-1.7.4.dist-info → agno-1.7.6.dist-info}/METADATA +19 -1
  47. {agno-1.7.4.dist-info → agno-1.7.6.dist-info}/RECORD +51 -42
  48. {agno-1.7.4.dist-info → agno-1.7.6.dist-info}/WHEEL +0 -0
  49. {agno-1.7.4.dist-info → agno-1.7.6.dist-info}/entry_points.txt +0 -0
  50. {agno-1.7.4.dist-info → agno-1.7.6.dist-info}/licenses/LICENSE +0 -0
  51. {agno-1.7.4.dist-info → agno-1.7.6.dist-info}/top_level.txt +0 -0
agno/tools/evm.py ADDED
@@ -0,0 +1,123 @@
1
+ from os import getenv
2
+ from typing import Optional
3
+
4
+ from agno.tools import Toolkit
5
+ from agno.utils.log import log_debug, log_error
6
+
7
+ try:
8
+ from eth_account.account import LocalAccount
9
+ from eth_account.datastructures import SignedTransaction
10
+ from hexbytes import HexBytes
11
+ from web3 import Web3
12
+ from web3.main import Web3 as Web3Type
13
+ from web3.providers.rpc import HTTPProvider
14
+ from web3.types import TxParams, TxReceipt
15
+ except ImportError:
16
+ raise ImportError("`web3` not installed. Please install using `pip install web3`")
17
+
18
+
19
+ class EvmTools(Toolkit):
20
+ def __init__(
21
+ self,
22
+ private_key: Optional[str] = None,
23
+ rpc_url: Optional[str] = None,
24
+ **kwargs,
25
+ ):
26
+ """Initialize EVM tools for blockchain interactions.
27
+
28
+ Args:
29
+ private_key: Private key for signing transactions (defaults to EVM_PRIVATE_KEY env var)
30
+ rpc_url: RPC URL for blockchain connection (defaults to EVM_RPC_URL env var)
31
+ **kwargs: Additional arguments passed to parent Toolkit class
32
+ """
33
+
34
+ self.private_key = private_key or getenv("EVM_PRIVATE_KEY")
35
+ self.rpc_url = rpc_url or getenv("EVM_RPC_URL")
36
+
37
+ if not self.private_key:
38
+ log_error("Private Key is required")
39
+ raise ValueError("Private Key is required")
40
+ if not self.rpc_url:
41
+ log_error("RPC Url is needed to interact with EVM blockchain")
42
+ raise ValueError("RPC Url is needed to interact with EVM blockchain")
43
+
44
+ # Ensure private key has 0x prefix
45
+ if not self.private_key.startswith("0x"):
46
+ self.private_key = f"0x{self.private_key}"
47
+
48
+ # Initialize Web3 client and account
49
+ self.web3_client: "Web3Type" = Web3(HTTPProvider(self.rpc_url))
50
+ self.account: "LocalAccount" = self.web3_client.eth.account.from_key(self.private_key)
51
+ log_debug(f"Your wallet address is: {self.account.address}")
52
+
53
+ super().__init__(name="evm_tools", tools=[self.send_transaction], **kwargs)
54
+
55
+ def get_max_priority_fee_per_gas(self) -> int:
56
+ """Get the max priority fee per gas for the transaction.
57
+
58
+ Returns:
59
+ int: The max priority fee per gas for the transaction (1 gwei)
60
+ """
61
+ max_priority_fee_per_gas = self.web3_client.to_wei(1, "gwei")
62
+ return max_priority_fee_per_gas
63
+
64
+ def get_max_fee_per_gas(self, max_priority_fee_per_gas: int) -> int:
65
+ """Get the max fee per gas for the transaction.
66
+
67
+ Args:
68
+ max_priority_fee_per_gas: The max priority fee per gas
69
+
70
+ Returns:
71
+ int: The max fee per gas for the transaction
72
+ """
73
+ latest_block = self.web3_client.eth.get_block("latest")
74
+ base_fee_per_gas = latest_block.get("baseFeePerGas")
75
+ if base_fee_per_gas is None:
76
+ log_error("Base fee per gas not found in the latest block.")
77
+ raise ValueError("Base fee per gas not found in the latest block.")
78
+ max_fee_per_gas = (2 * base_fee_per_gas) + max_priority_fee_per_gas
79
+ return max_fee_per_gas
80
+
81
+ def send_transaction(self, to_address: str, amount_in_wei: int) -> str:
82
+ """Sends a transaction to the address provided.
83
+
84
+ Args:
85
+ to_address: The address to which you want to send ETH
86
+ amount_in_wei: The amount of ETH to send in wei
87
+
88
+ Returns:
89
+ str: The transaction hash of the transaction or error message
90
+ """
91
+ try:
92
+ max_priority_fee_per_gas = self.get_max_priority_fee_per_gas()
93
+ max_fee_per_gas = self.get_max_fee_per_gas(max_priority_fee_per_gas)
94
+
95
+ transaction_params: "TxParams" = {
96
+ "from": self.account.address,
97
+ "to": to_address,
98
+ "value": amount_in_wei, # type: ignore[typeddict-item]
99
+ "nonce": self.web3_client.eth.get_transaction_count(self.account.address),
100
+ "gas": 21000,
101
+ "maxFeePerGas": max_fee_per_gas, # type: ignore[typeddict-item]
102
+ "maxPriorityFeePerGas": max_priority_fee_per_gas, # type: ignore[typeddict-item]
103
+ "chainId": self.web3_client.eth.chain_id,
104
+ "type": 2, # EIP-1559 dynamic fee transaction
105
+ }
106
+
107
+ signed_transaction: "SignedTransaction" = self.web3_client.eth.account.sign_transaction(
108
+ transaction_params, self.private_key
109
+ )
110
+ transaction_hash: "HexBytes" = self.web3_client.eth.send_raw_transaction(signed_transaction.raw_transaction)
111
+ log_debug(f"Ongoing Transaction hash: 0x{transaction_hash.hex()}")
112
+
113
+ transaction_receipt: "TxReceipt" = self.web3_client.eth.wait_for_transaction_receipt(transaction_hash)
114
+ if transaction_receipt.get("status") == 1:
115
+ log_debug(f"Transaction successful! Transaction hash: 0x{transaction_hash.hex()}")
116
+ return f"0x{transaction_hash.hex()}"
117
+ else:
118
+ log_error("Transaction failed!")
119
+ raise Exception("Transaction failed!")
120
+
121
+ except Exception as e:
122
+ log_error(f"Error sending transaction: {e}")
123
+ return f"error: {e}"
agno/tools/function.py CHANGED
@@ -151,7 +151,7 @@ class Function(BaseModel):
151
151
  param_type_hints = {
152
152
  name: type_hints.get(name)
153
153
  for name in sig.parameters
154
- if name != "return" and name not in ["agent", "team"]
154
+ if name != "return" and name not in ["agent", "team", "self"]
155
155
  }
156
156
 
157
157
  # Parse docstring for parameters
@@ -177,7 +177,9 @@ class Function(BaseModel):
177
177
  # If strict=True mark all fields as required
178
178
  # See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
179
179
  if strict:
180
- parameters["required"] = [name for name in parameters["properties"] if name not in ["agent", "team"]]
180
+ parameters["required"] = [
181
+ name for name in parameters["properties"] if name not in ["agent", "team", "self"]
182
+ ]
181
183
  else:
182
184
  # Mark a field as required if it has no default value (this would include optional fields)
183
185
  parameters["required"] = [
@@ -235,7 +237,7 @@ class Function(BaseModel):
235
237
  # log_info(f"Type hints for {self.name}: {type_hints}")
236
238
 
237
239
  # Filter out return type and only process parameters
238
- excluded_params = ["return", "agent", "team"]
240
+ excluded_params = ["return", "agent", "team", "self"]
239
241
  if self.requires_user_input and self.user_input_fields:
240
242
  if len(self.user_input_fields) == 0:
241
243
  excluded_params.extend(list(type_hints.keys()))
@@ -337,7 +339,9 @@ class Function(BaseModel):
337
339
 
338
340
  def process_schema_for_strict(self):
339
341
  self.parameters["additionalProperties"] = False
340
- self.parameters["required"] = [name for name in self.parameters["properties"] if name not in ["agent", "team"]]
342
+ self.parameters["required"] = [
343
+ name for name in self.parameters["properties"] if name not in ["agent", "team", "self"]
344
+ ]
341
345
 
342
346
  def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
343
347
  """Generate a cache key based on function name and arguments."""
@@ -554,7 +558,7 @@ class FunctionCall(BaseModel):
554
558
  from functools import reduce
555
559
  from inspect import iscoroutinefunction
556
560
 
557
- def execute_entrypoint():
561
+ def execute_entrypoint(name, func, args):
558
562
  """Execute the entrypoint function."""
559
563
  arguments = entrypoint_args.copy()
560
564
  if self.arguments is not None:
@@ -572,9 +576,9 @@ class FunctionCall(BaseModel):
572
576
  # Pass the inner function as next_func to the hook
573
577
  # The hook will call next_func to continue the chain
574
578
  def next_func(**kwargs):
575
- return inner_func()
579
+ return inner_func(name, func, kwargs)
576
580
 
577
- hook_args = self._build_hook_args(hook, name, func, args)
581
+ hook_args = self._build_hook_args(hook, name, next_func, args)
578
582
 
579
583
  return hook(**hook_args)
580
584
 
@@ -716,7 +720,7 @@ class FunctionCall(BaseModel):
716
720
  from functools import reduce
717
721
  from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction
718
722
 
719
- async def execute_entrypoint_async():
723
+ async def execute_entrypoint_async(name, func, args):
720
724
  """Execute the entrypoint function asynchronously."""
721
725
  arguments = entrypoint_args.copy()
722
726
  if self.arguments is not None:
@@ -729,7 +733,7 @@ class FunctionCall(BaseModel):
729
733
  result = await result
730
734
  return result
731
735
 
732
- def execute_entrypoint():
736
+ def execute_entrypoint(name, func, args):
733
737
  """Execute the entrypoint function synchronously."""
734
738
  arguments = entrypoint_args.copy()
735
739
  if self.arguments is not None:
@@ -750,11 +754,11 @@ class FunctionCall(BaseModel):
750
754
  # The hook will call next_func to continue the chain
751
755
  async def next_func(**kwargs):
752
756
  if iscoroutinefunction(inner_func):
753
- return await inner_func()
757
+ return await inner_func(name, func, kwargs)
754
758
  else:
755
- return inner_func()
759
+ return inner_func(name, func, kwargs)
756
760
 
757
- hook_args = self._build_hook_args(hook, name, func, args)
761
+ hook_args = self._build_hook_args(hook, name, next_func, args)
758
762
 
759
763
  if iscoroutinefunction(hook):
760
764
  return await hook(**hook_args)
agno/tools/linkup.py ADDED
@@ -0,0 +1,54 @@
1
+ from os import getenv
2
+ from typing import Any, List, Literal, Optional
3
+
4
+ from agno.tools import Toolkit
5
+ from agno.utils.log import logger
6
+
7
+ try:
8
+ from linkup import LinkupClient
9
+ except ImportError:
10
+ raise ImportError("`linkup-sdk` not installed. Please install using `pip install linkup-sdk`")
11
+
12
+
13
+ class LinkupTools(Toolkit):
14
+ def __init__(
15
+ self,
16
+ api_key: Optional[str] = None,
17
+ depth: Literal["standard", "deep"] = "standard",
18
+ output_type: Literal["sourcedAnswer", "searchResults"] = "searchResults",
19
+ **kwargs,
20
+ ):
21
+ self.api_key = api_key or getenv("LINKUP_API_KEY")
22
+ if not self.api_key:
23
+ logger.error("LINKUP_API_KEY not set. Please set the LINKUP_API_KEY environment variable.")
24
+
25
+ self.linkup = LinkupClient(api_key=api_key)
26
+ self.depth = depth
27
+ self.output_type = output_type
28
+
29
+ tools: List[Any] = [self.web_search_with_linkup]
30
+
31
+ super().__init__(name="linkup_tools", tools=tools, **kwargs)
32
+
33
+ def web_search_with_linkup(self, query: str, depth: Optional[str] = None, output_type: Optional[str] = None) -> str:
34
+ """
35
+ Use this function to search the web for a given query.
36
+ This function uses the Linkup API to provide realtime online information about the query.
37
+
38
+ Args:
39
+ query (str): Query to search for.
40
+ depth (str): (deep|standard) Depth of the search. Defaults to 'standard'.
41
+ output_type (str): (sourcedAnswer|searchResults) Type of output. Defaults to 'searchResults'.
42
+
43
+ Returns:
44
+ str: string of results related to the query.
45
+ """
46
+ try:
47
+ response = self.linkup.search(
48
+ query=query,
49
+ depth=depth or self.depth, # type: ignore
50
+ output_type=output_type or self.output_type, # type: ignore
51
+ )
52
+ return response
53
+ except Exception as e:
54
+ return f"Error: {str(e)}"
agno/tools/mcp.py CHANGED
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Literal, Optional, Union
6
6
 
7
7
  from agno.tools import Toolkit
8
8
  from agno.tools.function import Function
9
- from agno.utils.log import log_debug, log_warning, logger
9
+ from agno.utils.log import log_debug, log_info, log_warning, logger
10
10
  from agno.utils.mcp import get_entrypoint_for_tool
11
11
 
12
12
  try:
@@ -82,6 +82,9 @@ class MCPTools(Toolkit):
82
82
  """
83
83
  super().__init__(name="MCPTools", **kwargs)
84
84
 
85
+ if transport == "sse":
86
+ log_info("SSE as a standalone transport is deprecated. Please use Streamable HTTP instead.")
87
+
85
88
  # Set these after `__init__` to bypass the `_check_tools_filters`
86
89
  # because tools are not available until `initialize()` is called.
87
90
  self.include_tools = include_tools
@@ -303,10 +306,14 @@ class MultiMCPTools(Toolkit):
303
306
  """
304
307
  super().__init__(name="MultiMCPTools", **kwargs)
305
308
 
309
+ if urls_transports is not None:
310
+ if "sse" in urls_transports:
311
+ log_info("SSE as a standalone transport is deprecated. Please use Streamable HTTP instead.")
312
+
306
313
  if urls is not None:
307
314
  if urls_transports is None:
308
315
  log_warning(
309
- "The default transport 'sse' will be used. You can explicitly set the transports by providing the urls_transports parameter."
316
+ "The default transport 'streamable-http' will be used. You can explicitly set the transports by providing the urls_transports parameter."
310
317
  )
311
318
  else:
312
319
  if len(urls) != len(urls_transports):
@@ -355,7 +362,7 @@ class MultiMCPTools(Toolkit):
355
362
  self.server_params_list.append(SSEClientParams(url=url))
356
363
  else:
357
364
  for url in urls:
358
- self.server_params_list.append(SSEClientParams(url=url))
365
+ self.server_params_list.append(StreamableHTTPClientParams(url=url))
359
366
 
360
367
  self._async_exit_stack = AsyncExitStack()
361
368
 
agno/tools/mem0.py CHANGED
@@ -7,7 +7,8 @@ from agno.tools.toolkit import Toolkit
7
7
  from agno.utils.log import log_debug, log_error, log_warning
8
8
 
9
9
  try:
10
- from mem0 import Memory, MemoryClient
10
+ from mem0.client.main import MemoryClient
11
+ from mem0.memory.main import Memory
11
12
  except ImportError:
12
13
  raise ImportError("`mem0ai` package not found. Please install it with `pip install mem0ai`")
13
14
 
@@ -18,6 +19,8 @@ class Mem0Tools(Toolkit):
18
19
  config: Optional[Dict[str, Any]] = None,
19
20
  api_key: Optional[str] = None,
20
21
  user_id: Optional[str] = None,
22
+ org_id: Optional[str] = None,
23
+ project_id: Optional[str] = None,
21
24
  infer: bool = True,
22
25
  **kwargs,
23
26
  ):
@@ -33,13 +36,20 @@ class Mem0Tools(Toolkit):
33
36
  )
34
37
  self.api_key = api_key or getenv("MEM0_API_KEY")
35
38
  self.user_id = user_id
39
+ self.org_id = org_id or getenv("MEM0_ORG_ID")
40
+ self.project_id = project_id or getenv("MEM0_PROJECT_ID")
36
41
  self.client: Union[Memory, MemoryClient]
37
42
  self.infer = infer
38
43
 
39
44
  try:
40
45
  if self.api_key:
41
46
  log_debug("Using Mem0 Platform API key.")
42
- self.client = MemoryClient(api_key=self.api_key)
47
+ client_kwargs = {"api_key": self.api_key}
48
+ if self.org_id:
49
+ client_kwargs["org_id"] = self.org_id
50
+ if self.project_id:
51
+ client_kwargs["project_id"] = self.project_id
52
+ self.client = MemoryClient(**client_kwargs)
43
53
  elif config is not None:
44
54
  log_debug("Using Mem0 with config.")
45
55
  self.client = Memory.from_config(config)
@@ -100,6 +110,7 @@ class Mem0Tools(Toolkit):
100
110
  messages_list,
101
111
  user_id=resolved_user_id,
102
112
  infer=self.infer,
113
+ output_format="v1.1",
103
114
  )
104
115
  return json.dumps(result)
105
116
  except Exception as e:
@@ -120,6 +131,7 @@ class Mem0Tools(Toolkit):
120
131
  results = self.client.search(
121
132
  query=query,
122
133
  user_id=resolved_user_id,
134
+ output_format="v1.1",
123
135
  )
124
136
 
125
137
  if isinstance(results, dict) and "results" in results:
@@ -147,6 +159,7 @@ class Mem0Tools(Toolkit):
147
159
  try:
148
160
  results = self.client.get_all(
149
161
  user_id=resolved_user_id,
162
+ output_format="v1.1",
150
163
  )
151
164
 
152
165
  if isinstance(results, dict) and "results" in results: