agno 2.2.1__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 (67) hide show
  1. agno/agent/agent.py +735 -574
  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 +3 -3
  25. agno/knowledge/reader/reader_factory.py +16 -0
  26. agno/knowledge/reader/tavily_reader.py +194 -0
  27. agno/memory/manager.py +28 -25
  28. agno/models/anthropic/claude.py +63 -6
  29. agno/models/base.py +251 -32
  30. agno/models/response.py +69 -0
  31. agno/os/router.py +7 -5
  32. agno/os/routers/memory/memory.py +2 -1
  33. agno/os/routers/memory/schemas.py +5 -2
  34. agno/os/schema.py +26 -20
  35. agno/os/utils.py +9 -2
  36. agno/run/agent.py +23 -30
  37. agno/run/base.py +17 -1
  38. agno/run/team.py +23 -29
  39. agno/run/workflow.py +17 -12
  40. agno/session/agent.py +3 -0
  41. agno/session/summary.py +4 -1
  42. agno/session/team.py +1 -1
  43. agno/team/team.py +591 -365
  44. agno/tools/dalle.py +2 -4
  45. agno/tools/eleven_labs.py +23 -25
  46. agno/tools/function.py +40 -0
  47. agno/tools/mcp/__init__.py +10 -0
  48. agno/tools/mcp/mcp.py +324 -0
  49. agno/tools/mcp/multi_mcp.py +347 -0
  50. agno/tools/mcp/params.py +24 -0
  51. agno/tools/slack.py +18 -3
  52. agno/tools/tavily.py +146 -0
  53. agno/utils/agent.py +366 -1
  54. agno/utils/mcp.py +92 -2
  55. agno/utils/media.py +166 -1
  56. agno/utils/print_response/workflow.py +17 -1
  57. agno/utils/team.py +89 -1
  58. agno/workflow/step.py +0 -1
  59. agno/workflow/types.py +10 -15
  60. {agno-2.2.1.dist-info → agno-2.2.2.dist-info}/METADATA +28 -25
  61. {agno-2.2.1.dist-info → agno-2.2.2.dist-info}/RECORD +64 -61
  62. agno/db/async_postgres/schemas.py +0 -139
  63. agno/db/async_postgres/utils.py +0 -347
  64. agno/tools/mcp.py +0 -679
  65. {agno-2.2.1.dist-info → agno-2.2.2.dist-info}/WHEEL +0 -0
  66. {agno-2.2.1.dist-info → agno-2.2.2.dist-info}/licenses/LICENSE +0 -0
  67. {agno-2.2.1.dist-info → agno-2.2.2.dist-info}/top_level.txt +0 -0
agno/agent/agent.py CHANGED
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
3
+ from asyncio import CancelledError, create_task
4
4
  from collections import ChainMap, deque
5
5
  from dataclasses import dataclass
6
+ from inspect import iscoroutinefunction
6
7
  from os import getenv
7
8
  from textwrap import dedent
8
9
  from typing import (
@@ -65,18 +66,35 @@ from agno.run.cancel import (
65
66
  from agno.run.messages import RunMessages
66
67
  from agno.run.team import TeamRunOutputEvent
67
68
  from agno.session import AgentSession, SessionSummaryManager, TeamSession, WorkflowSession
69
+ from agno.session.summary import SessionSummary
68
70
  from agno.tools import Toolkit
69
71
  from agno.tools.function import Function
70
72
  from agno.utils.agent import (
73
+ aget_chat_history_util,
74
+ aget_last_run_output_util,
75
+ aget_run_output_util,
76
+ aget_session_metrics_util,
77
+ aget_session_name_util,
78
+ aget_session_state_util,
79
+ aset_session_name_util,
80
+ aupdate_session_state_util,
71
81
  await_for_background_tasks,
72
82
  await_for_background_tasks_stream,
73
83
  collect_joint_audios,
74
84
  collect_joint_files,
75
85
  collect_joint_images,
76
86
  collect_joint_videos,
87
+ get_chat_history_util,
88
+ get_last_run_output_util,
89
+ get_run_output_util,
90
+ get_session_metrics_util,
91
+ get_session_name_util,
92
+ get_session_state_util,
77
93
  scrub_history_messages_from_run_output,
78
94
  scrub_media_from_run_output,
79
95
  scrub_tool_results_from_run_output,
96
+ set_session_name_util,
97
+ update_session_state_util,
80
98
  wait_for_background_tasks,
81
99
  wait_for_background_tasks_stream,
82
100
  )
@@ -615,17 +633,16 @@ class Agent:
615
633
  self.telemetry = telemetry
616
634
 
617
635
  # If we are caching the agent session
618
- self._agent_session: Optional[AgentSession] = None
636
+ self._cached_session: Optional[AgentSession] = None
619
637
 
620
638
  self._tool_instructions: Optional[List[str]] = None
621
- self._tools_for_model: Optional[List[Dict[str, Any]]] = None
622
- self._functions_for_model: Optional[Dict[str, Function]] = None
623
- self._rebuild_tools: bool = True
624
639
 
625
640
  self._formatter: Optional[SafeFormatter] = None
626
641
 
627
642
  self._hooks_normalised = False
628
643
 
644
+ self._mcp_tools_initialized_on_run: List[Any] = []
645
+
629
646
  # Lazy-initialized shared thread pool executor for background tasks (memory, cultural knowledge, etc.)
630
647
  self._background_executor: Optional[Any] = None
631
648
 
@@ -642,6 +659,14 @@ class Agent:
642
659
  self._background_executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="agno-bg")
643
660
  return self._background_executor
644
661
 
662
+ @property
663
+ def should_parse_structured_output(self) -> bool:
664
+ return self.output_schema is not None and self.parse_response and self.parser_model is None
665
+
666
+ @property
667
+ def cached_session(self) -> Optional[AgentSession]:
668
+ return self._cached_session
669
+
645
670
  def set_id(self) -> None:
646
671
  if self.id is None:
647
672
  self.id = generate_id_from_name(self.name)
@@ -798,19 +823,28 @@ class Agent:
798
823
  if self._formatter is None:
799
824
  self._formatter = SafeFormatter()
800
825
 
801
- @property
802
- def should_parse_structured_output(self) -> bool:
803
- return self.output_schema is not None and self.parse_response and self.parser_model is None
804
-
805
826
  def add_tool(self, tool: Union[Toolkit, Callable, Function, Dict]):
806
827
  if not self.tools:
807
828
  self.tools = []
808
829
  self.tools.append(tool)
809
- self._rebuild_tools = True
810
830
 
811
831
  def set_tools(self, tools: Sequence[Union[Toolkit, Callable, Function, Dict]]):
812
832
  self.tools = list(tools) if tools else []
813
- self._rebuild_tools = True
833
+
834
+ async def _connect_mcp_tools(self) -> None:
835
+ """Connect the MCP tools to the agent."""
836
+ if self.tools:
837
+ for tool in self.tools:
838
+ if tool.__class__.__name__ in ["MCPTools", "MultiMCPTools"] and not tool.initialized: # type: ignore
839
+ # Connect the MCP server
840
+ await tool.connect() # type: ignore
841
+ self._mcp_tools_initialized_on_run.append(tool)
842
+
843
+ async def _disconnect_mcp_tools(self) -> None:
844
+ """Disconnect the MCP tools from the agent."""
845
+ for tool in self._mcp_tools_initialized_on_run:
846
+ await tool.close()
847
+ self._mcp_tools_initialized_on_run = []
814
848
 
815
849
  def _initialize_session(
816
850
  self,
@@ -909,15 +943,19 @@ class Agent:
909
943
  deque(pre_hook_iterator, maxlen=0)
910
944
 
911
945
  # 2. Determine tools for model
912
- self._determine_tools_for_model(
946
+ processed_tools = self.get_tools(
947
+ run_response=run_response,
948
+ session=session,
949
+ user_id=user_id,
950
+ knowledge_filters=knowledge_filters,
951
+ )
952
+ _tools = self._determine_tools_for_model(
913
953
  model=self.model,
954
+ processed_tools=processed_tools,
914
955
  run_response=run_response,
915
956
  session=session,
916
957
  session_state=session_state,
917
958
  dependencies=dependencies,
918
- user_id=user_id,
919
- async_mode=False,
920
- knowledge_filters=knowledge_filters,
921
959
  )
922
960
 
923
961
  # 3. Prepare run messages
@@ -937,6 +975,7 @@ class Agent:
937
975
  add_dependencies_to_context=add_dependencies_to_context,
938
976
  add_session_state_to_context=add_session_state_to_context,
939
977
  metadata=metadata,
978
+ tools=_tools,
940
979
  **kwargs,
941
980
  )
942
981
  if len(run_messages.messages) == 0:
@@ -978,8 +1017,7 @@ class Agent:
978
1017
  self.model = cast(Model, self.model)
979
1018
  model_response: ModelResponse = self.model.response(
980
1019
  messages=run_messages.messages,
981
- tools=self._tools_for_model,
982
- functions=self._functions_for_model,
1020
+ tools=_tools,
983
1021
  tool_choice=self.tool_choice,
984
1022
  tool_call_limit=self.tool_call_limit,
985
1023
  response_format=response_format,
@@ -1128,15 +1166,19 @@ class Agent:
1128
1166
  yield event
1129
1167
 
1130
1168
  # 2. Determine tools for model
1131
- self._determine_tools_for_model(
1169
+ processed_tools = self.get_tools(
1170
+ run_response=run_response,
1171
+ session=session,
1172
+ user_id=user_id,
1173
+ knowledge_filters=knowledge_filters,
1174
+ )
1175
+ _tools = self._determine_tools_for_model(
1132
1176
  model=self.model,
1177
+ processed_tools=processed_tools,
1133
1178
  run_response=run_response,
1134
1179
  session=session,
1135
1180
  session_state=session_state,
1136
1181
  dependencies=dependencies,
1137
- user_id=user_id,
1138
- async_mode=False,
1139
- knowledge_filters=knowledge_filters,
1140
1182
  )
1141
1183
 
1142
1184
  # 3. Prepare run messages
@@ -1156,6 +1198,7 @@ class Agent:
1156
1198
  add_dependencies_to_context=add_dependencies_to_context,
1157
1199
  add_session_state_to_context=add_session_state_to_context,
1158
1200
  metadata=metadata,
1201
+ tools=_tools,
1159
1202
  **kwargs,
1160
1203
  )
1161
1204
  if len(run_messages.messages) == 0:
@@ -1210,6 +1253,7 @@ class Agent:
1210
1253
  session=session,
1211
1254
  run_response=run_response,
1212
1255
  run_messages=run_messages,
1256
+ tools=_tools,
1213
1257
  response_format=response_format,
1214
1258
  stream_events=stream_events,
1215
1259
  ):
@@ -1225,6 +1269,7 @@ class Agent:
1225
1269
  session=session,
1226
1270
  run_response=run_response,
1227
1271
  run_messages=run_messages,
1272
+ tools=_tools,
1228
1273
  response_format=response_format,
1229
1274
  stream_events=stream_events,
1230
1275
  ):
@@ -1359,7 +1404,10 @@ class Agent:
1359
1404
  # Handle run cancellation during streaming
1360
1405
  log_info(f"Run {run_response.run_id} was cancelled during streaming")
1361
1406
  run_response.status = RunStatus.cancelled
1362
- run_response.content = str(e)
1407
+ # Don't overwrite content - preserve any partial content that was streamed
1408
+ # Only set content if it's empty
1409
+ if not run_response.content:
1410
+ run_response.content = str(e)
1363
1411
 
1364
1412
  # Yield the cancellation event
1365
1413
  yield handle_event( # type: ignore
@@ -1745,15 +1793,19 @@ class Agent:
1745
1793
 
1746
1794
  # 5. Determine tools for model
1747
1795
  self.model = cast(Model, self.model)
1748
- await self._adetermine_tools_for_model(
1796
+ processed_tools = await self.aget_tools(
1797
+ run_response=run_response,
1798
+ session=agent_session,
1799
+ user_id=user_id,
1800
+ knowledge_filters=knowledge_filters,
1801
+ )
1802
+ _tools = self._determine_tools_for_model(
1749
1803
  model=self.model,
1804
+ processed_tools=processed_tools,
1750
1805
  run_response=run_response,
1751
1806
  session=agent_session,
1752
1807
  session_state=session_state,
1753
1808
  dependencies=dependencies,
1754
- user_id=user_id,
1755
- async_mode=True,
1756
- knowledge_filters=knowledge_filters,
1757
1809
  )
1758
1810
 
1759
1811
  # 6. Prepare run messages
@@ -1773,6 +1825,7 @@ class Agent:
1773
1825
  add_dependencies_to_context=add_dependencies_to_context,
1774
1826
  add_session_state_to_context=add_session_state_to_context,
1775
1827
  metadata=metadata,
1828
+ tools=_tools,
1776
1829
  **kwargs,
1777
1830
  )
1778
1831
  if len(run_messages.messages) == 0:
@@ -1781,10 +1834,8 @@ class Agent:
1781
1834
  # 7. Start memory creation as a background task (runs concurrently with the main execution)
1782
1835
  memory_task = None
1783
1836
  if run_messages.user_message is not None and self.memory_manager is not None and not self.enable_agentic_memory:
1784
- import asyncio
1785
-
1786
1837
  log_debug("Starting memory creation in background task.")
1787
- memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
1838
+ memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
1788
1839
 
1789
1840
  # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1790
1841
  cultural_knowledge_task = None
@@ -1793,10 +1844,8 @@ class Agent:
1793
1844
  and self.culture_manager is not None
1794
1845
  and self.update_cultural_knowledge
1795
1846
  ):
1796
- import asyncio
1797
-
1798
1847
  log_debug("Starting cultural knowledge creation in background thread.")
1799
- cultural_knowledge_task = asyncio.create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
1848
+ cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
1800
1849
 
1801
1850
  try:
1802
1851
  # Check for cancellation before model call
@@ -1811,8 +1860,7 @@ class Agent:
1811
1860
  # 9. Generate a response from the Model (includes running function calls)
1812
1861
  model_response: ModelResponse = await self.model.aresponse(
1813
1862
  messages=run_messages.messages,
1814
- tools=self._tools_for_model,
1815
- functions=self._functions_for_model,
1863
+ tools=_tools,
1816
1864
  tool_choice=self.tool_choice,
1817
1865
  tool_call_limit=self.tool_call_limit,
1818
1866
  response_format=response_format,
@@ -1905,23 +1953,22 @@ class Agent:
1905
1953
  return run_response
1906
1954
 
1907
1955
  finally:
1956
+ # Always disconnect MCP tools
1957
+ await self._disconnect_mcp_tools()
1958
+
1908
1959
  # Cancel the memory task if it's still running
1909
1960
  if memory_task is not None and not memory_task.done():
1910
- import asyncio
1911
-
1912
1961
  memory_task.cancel()
1913
1962
  try:
1914
1963
  await memory_task
1915
- except asyncio.CancelledError:
1964
+ except CancelledError:
1916
1965
  pass
1917
1966
  # Cancel the cultural knowledge task if it's still running
1918
1967
  if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
1919
- import asyncio
1920
-
1921
1968
  cultural_knowledge_task.cancel()
1922
1969
  try:
1923
1970
  await cultural_knowledge_task
1924
- except asyncio.CancelledError:
1971
+ except CancelledError:
1925
1972
  pass
1926
1973
  # Always clean up the run tracking
1927
1974
  cleanup_run(run_response.run_id) # type: ignore
@@ -2011,14 +2058,18 @@ class Agent:
2011
2058
 
2012
2059
  # 5. Determine tools for model
2013
2060
  self.model = cast(Model, self.model)
2014
- self._determine_tools_for_model(
2015
- model=self.model,
2061
+ processed_tools = await self.aget_tools(
2016
2062
  run_response=run_response,
2017
2063
  session=agent_session,
2018
- session_state=session_state,
2019
2064
  user_id=user_id,
2020
- async_mode=True,
2021
2065
  knowledge_filters=knowledge_filters,
2066
+ )
2067
+ _tools = self._determine_tools_for_model(
2068
+ model=self.model,
2069
+ processed_tools=processed_tools,
2070
+ run_response=run_response,
2071
+ session=agent_session,
2072
+ session_state=session_state,
2022
2073
  dependencies=dependencies,
2023
2074
  )
2024
2075
 
@@ -2039,6 +2090,7 @@ class Agent:
2039
2090
  add_dependencies_to_context=add_dependencies_to_context,
2040
2091
  add_session_state_to_context=add_session_state_to_context,
2041
2092
  metadata=metadata,
2093
+ tools=_tools,
2042
2094
  **kwargs,
2043
2095
  )
2044
2096
  if len(run_messages.messages) == 0:
@@ -2047,10 +2099,8 @@ class Agent:
2047
2099
  # 7. Start memory creation as a background task (runs concurrently with the main execution)
2048
2100
  memory_task = None
2049
2101
  if run_messages.user_message is not None and self.memory_manager is not None and not self.enable_agentic_memory:
2050
- import asyncio
2051
-
2052
2102
  log_debug("Starting memory creation in background task.")
2053
- memory_task = asyncio.create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2103
+ memory_task = create_task(self._amake_memories(run_messages=run_messages, user_id=user_id))
2054
2104
 
2055
2105
  # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2056
2106
  cultural_knowledge_task = None
@@ -2059,10 +2109,8 @@ class Agent:
2059
2109
  and self.culture_manager is not None
2060
2110
  and self.update_cultural_knowledge
2061
2111
  ):
2062
- import asyncio
2063
-
2064
2112
  log_debug("Starting cultural knowledge creation in background task.")
2065
- cultural_knowledge_task = asyncio.create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2113
+ cultural_knowledge_task = create_task(self._acreate_cultural_knowledge(run_messages=run_messages))
2066
2114
 
2067
2115
  # Register run for cancellation tracking
2068
2116
  register_run(run_response.run_id) # type: ignore
@@ -2085,6 +2133,7 @@ class Agent:
2085
2133
  session=agent_session,
2086
2134
  run_response=run_response,
2087
2135
  run_messages=run_messages,
2136
+ tools=_tools,
2088
2137
  response_format=response_format,
2089
2138
  stream_events=stream_events,
2090
2139
  ):
@@ -2100,6 +2149,7 @@ class Agent:
2100
2149
  session=agent_session,
2101
2150
  run_response=run_response,
2102
2151
  run_messages=run_messages,
2152
+ tools=_tools,
2103
2153
  response_format=response_format,
2104
2154
  stream_events=stream_events,
2105
2155
  ):
@@ -2237,7 +2287,10 @@ class Agent:
2237
2287
  # Handle run cancellation during async streaming
2238
2288
  log_info(f"Run {run_response.run_id} was cancelled during async streaming")
2239
2289
  run_response.status = RunStatus.cancelled
2240
- run_response.content = str(e)
2290
+ # Don't overwrite content - preserve any partial content that was streamed
2291
+ # Only set content if it's empty
2292
+ if not run_response.content:
2293
+ run_response.content = str(e)
2241
2294
 
2242
2295
  # Yield the cancellation event
2243
2296
  yield handle_event( # type: ignore
@@ -2250,19 +2303,22 @@ class Agent:
2250
2303
  # Cleanup and store the run response and session
2251
2304
  await self._acleanup_and_store(run_response=run_response, session=agent_session, user_id=user_id)
2252
2305
  finally:
2306
+ # Always disconnect MCP tools
2307
+ await self._disconnect_mcp_tools()
2308
+
2253
2309
  # Cancel the memory task if it's still running
2254
2310
  if memory_task is not None and not memory_task.done():
2255
2311
  memory_task.cancel()
2256
2312
  try:
2257
2313
  await memory_task
2258
- except asyncio.CancelledError:
2314
+ except CancelledError:
2259
2315
  pass
2260
2316
 
2261
2317
  if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2262
2318
  cultural_knowledge_task.cancel()
2263
2319
  try:
2264
2320
  await cultural_knowledge_task
2265
- except asyncio.CancelledError:
2321
+ except CancelledError:
2266
2322
  pass
2267
2323
 
2268
2324
  # Always clean up the run tracking
@@ -2704,15 +2760,19 @@ class Agent:
2704
2760
  response_format = self._get_response_format()
2705
2761
  self.model = cast(Model, self.model)
2706
2762
 
2707
- self._determine_tools_for_model(
2763
+ processed_tools = self.get_tools(
2764
+ run_response=run_response,
2765
+ session=agent_session,
2766
+ user_id=user_id,
2767
+ knowledge_filters=effective_filters,
2768
+ )
2769
+ _tools = self._determine_tools_for_model(
2708
2770
  model=self.model,
2771
+ processed_tools=processed_tools,
2709
2772
  run_response=run_response,
2710
2773
  session=agent_session,
2711
2774
  session_state=session_state,
2712
2775
  dependencies=run_dependencies,
2713
- user_id=user_id,
2714
- async_mode=False,
2715
- knowledge_filters=effective_filters,
2716
2776
  )
2717
2777
 
2718
2778
  last_exception = None
@@ -2735,6 +2795,7 @@ class Agent:
2735
2795
  response_iterator = self._continue_run_stream(
2736
2796
  run_response=run_response,
2737
2797
  run_messages=run_messages,
2798
+ tools=_tools,
2738
2799
  user_id=user_id,
2739
2800
  session=agent_session,
2740
2801
  session_state=session_state,
@@ -2750,6 +2811,7 @@ class Agent:
2750
2811
  response = self._continue_run(
2751
2812
  run_response=run_response,
2752
2813
  run_messages=run_messages,
2814
+ tools=_tools,
2753
2815
  user_id=user_id,
2754
2816
  session=agent_session,
2755
2817
  session_state=session_state,
@@ -2802,6 +2864,7 @@ class Agent:
2802
2864
  run_response: RunOutput,
2803
2865
  run_messages: RunMessages,
2804
2866
  session: AgentSession,
2867
+ tools: List[Union[Function, dict]],
2805
2868
  session_state: Optional[Dict[str, Any]] = None,
2806
2869
  dependencies: Optional[Dict[str, Any]] = None,
2807
2870
  metadata: Optional[Dict[str, Any]] = None,
@@ -2828,7 +2891,7 @@ class Agent:
2828
2891
  self.model = cast(Model, self.model)
2829
2892
 
2830
2893
  # 1. Handle the updated tools
2831
- self._handle_tool_call_updates(run_response=run_response, run_messages=run_messages)
2894
+ self._handle_tool_call_updates(run_response=run_response, run_messages=run_messages, tools=tools)
2832
2895
 
2833
2896
  try:
2834
2897
  # Check for cancellation before model call
@@ -2839,8 +2902,7 @@ class Agent:
2839
2902
  model_response: ModelResponse = self.model.response(
2840
2903
  messages=run_messages.messages,
2841
2904
  response_format=response_format,
2842
- tools=self._tools_for_model,
2843
- functions=self._functions_for_model,
2905
+ tools=tools,
2844
2906
  tool_choice=self.tool_choice,
2845
2907
  tool_call_limit=self.tool_call_limit,
2846
2908
  )
@@ -2920,6 +2982,7 @@ class Agent:
2920
2982
  run_response: RunOutput,
2921
2983
  run_messages: RunMessages,
2922
2984
  session: AgentSession,
2985
+ tools: List[Union[Function, dict]],
2923
2986
  session_state: Optional[Dict[str, Any]] = None,
2924
2987
  metadata: Optional[Dict[str, Any]] = None,
2925
2988
  user_id: Optional[str] = None,
@@ -2955,7 +3018,7 @@ class Agent:
2955
3018
 
2956
3019
  # 2. Handle the updated tools
2957
3020
  yield from self._handle_tool_call_updates_stream(
2958
- run_response=run_response, run_messages=run_messages, stream_events=stream_events
3021
+ run_response=run_response, run_messages=run_messages, tools=tools, stream_events=stream_events
2959
3022
  )
2960
3023
 
2961
3024
  try:
@@ -2964,6 +3027,7 @@ class Agent:
2964
3027
  session=session,
2965
3028
  run_response=run_response,
2966
3029
  run_messages=run_messages,
3030
+ tools=tools,
2967
3031
  response_format=response_format,
2968
3032
  stream_events=stream_events,
2969
3033
  ):
@@ -3352,15 +3416,19 @@ class Agent:
3352
3416
 
3353
3417
  # 5. Determine tools for model
3354
3418
  self.model = cast(Model, self.model)
3355
- await self._adetermine_tools_for_model(
3419
+ processed_tools = await self.aget_tools(
3420
+ run_response=run_response,
3421
+ session=agent_session,
3422
+ user_id=user_id,
3423
+ knowledge_filters=knowledge_filters,
3424
+ )
3425
+ _tools = self._determine_tools_for_model(
3356
3426
  model=self.model,
3427
+ processed_tools=processed_tools,
3357
3428
  run_response=run_response,
3358
3429
  session=agent_session,
3359
3430
  session_state=session_state,
3360
3431
  dependencies=dependencies,
3361
- user_id=user_id,
3362
- async_mode=True,
3363
- knowledge_filters=knowledge_filters,
3364
3432
  )
3365
3433
 
3366
3434
  # 6. Prepare run messages
@@ -3373,14 +3441,13 @@ class Agent:
3373
3441
 
3374
3442
  try:
3375
3443
  # 7. Handle the updated tools
3376
- await self._ahandle_tool_call_updates(run_response=run_response, run_messages=run_messages)
3444
+ await self._ahandle_tool_call_updates(run_response=run_response, run_messages=run_messages, tools=_tools)
3377
3445
 
3378
3446
  # 8. Get model response
3379
3447
  model_response: ModelResponse = await self.model.aresponse(
3380
3448
  messages=run_messages.messages,
3381
3449
  response_format=response_format,
3382
- tools=self._tools_for_model,
3383
- functions=self._functions_for_model,
3450
+ tools=_tools,
3384
3451
  tool_choice=self.tool_choice,
3385
3452
  tool_call_limit=self.tool_call_limit,
3386
3453
  )
@@ -3467,6 +3534,9 @@ class Agent:
3467
3534
 
3468
3535
  return run_response
3469
3536
  finally:
3537
+ # Always disconnect MCP tools
3538
+ await self._disconnect_mcp_tools()
3539
+
3470
3540
  # Always clean up the run tracking
3471
3541
  cleanup_run(run_response.run_id) # type: ignore
3472
3542
 
@@ -3508,7 +3578,7 @@ class Agent:
3508
3578
  await self._aresolve_run_dependencies(dependencies=dependencies)
3509
3579
 
3510
3580
  # 2. Read existing session from db
3511
- agent_session = self._read_or_create_session(session_id=session_id, user_id=user_id)
3581
+ agent_session = await self._aread_or_create_session(session_id=session_id, user_id=user_id)
3512
3582
 
3513
3583
  # 3. Update session state and metadata
3514
3584
  self._update_metadata(session=agent_session)
@@ -3543,15 +3613,19 @@ class Agent:
3543
3613
 
3544
3614
  # 5. Determine tools for model
3545
3615
  self.model = cast(Model, self.model)
3546
- await self._adetermine_tools_for_model(
3616
+ processed_tools = await self.aget_tools(
3617
+ run_response=run_response,
3618
+ session=agent_session,
3619
+ user_id=user_id,
3620
+ knowledge_filters=knowledge_filters,
3621
+ )
3622
+ _tools = self._determine_tools_for_model(
3547
3623
  model=self.model,
3624
+ processed_tools=processed_tools,
3548
3625
  run_response=run_response,
3549
3626
  session=agent_session,
3550
3627
  session_state=session_state,
3551
3628
  dependencies=dependencies,
3552
- user_id=user_id,
3553
- async_mode=True,
3554
- knowledge_filters=knowledge_filters,
3555
3629
  )
3556
3630
 
3557
3631
  # 6. Prepare run messages
@@ -3574,7 +3648,7 @@ class Agent:
3574
3648
 
3575
3649
  # 7. Handle the updated tools
3576
3650
  async for event in self._ahandle_tool_call_updates_stream(
3577
- run_response=run_response, run_messages=run_messages
3651
+ run_response=run_response, run_messages=run_messages, tools=_tools, stream_events=stream_events
3578
3652
  ):
3579
3653
  raise_if_cancelled(run_response.run_id) # type: ignore
3580
3654
  yield event
@@ -3585,6 +3659,7 @@ class Agent:
3585
3659
  session=agent_session,
3586
3660
  run_response=run_response,
3587
3661
  run_messages=run_messages,
3662
+ tools=_tools,
3588
3663
  response_format=response_format,
3589
3664
  stream_events=stream_events,
3590
3665
  ):
@@ -3600,6 +3675,7 @@ class Agent:
3600
3675
  session=agent_session,
3601
3676
  run_response=run_response,
3602
3677
  run_messages=run_messages,
3678
+ tools=_tools,
3603
3679
  response_format=response_format,
3604
3680
  stream_events=stream_events,
3605
3681
  ):
@@ -3733,6 +3809,9 @@ class Agent:
3733
3809
  # Cleanup and store the run response and session
3734
3810
  await self._acleanup_and_store(run_response=run_response, session=agent_session, user_id=user_id)
3735
3811
  finally:
3812
+ # Always disconnect MCP tools
3813
+ await self._disconnect_mcp_tools()
3814
+
3736
3815
  # Always clean up the run tracking
3737
3816
  cleanup_run(run_response.run_id) # type: ignore
3738
3817
 
@@ -3851,7 +3930,7 @@ class Agent:
3851
3930
  # Filter arguments to only include those that the hook accepts
3852
3931
  filtered_args = filter_hook_args(hook, all_args)
3853
3932
 
3854
- if asyncio.iscoroutinefunction(hook):
3933
+ if iscoroutinefunction(hook):
3855
3934
  await hook(**filtered_args)
3856
3935
  else:
3857
3936
  # Synchronous function
@@ -3985,8 +4064,9 @@ class Agent:
3985
4064
  try:
3986
4065
  # Filter arguments to only include those that the hook accepts
3987
4066
  filtered_args = filter_hook_args(hook, all_args)
4067
+ from inspect import iscoroutinefunction
3988
4068
 
3989
- if asyncio.iscoroutinefunction(hook):
4069
+ if iscoroutinefunction(hook):
3990
4070
  await hook(**filtered_args)
3991
4071
  else:
3992
4072
  hook(**filtered_args)
@@ -4183,11 +4263,12 @@ class Agent:
4183
4263
  run_response: RunOutput,
4184
4264
  run_messages: RunMessages,
4185
4265
  tool: ToolExecution,
4266
+ functions: Optional[Dict[str, Function]] = None,
4186
4267
  stream_events: bool = False,
4187
4268
  ) -> Iterator[RunOutputEvent]:
4188
4269
  self.model = cast(Model, self.model)
4189
4270
  # Execute the tool
4190
- function_call = self.model.get_function_call_to_run_from_tool_execution(tool, self._functions_for_model)
4271
+ function_call = self.model.get_function_call_to_run_from_tool_execution(tool, functions)
4191
4272
  function_call_results: List[Message] = []
4192
4273
 
4193
4274
  for call_result in self.model.run_function_call(
@@ -4221,9 +4302,11 @@ class Agent:
4221
4302
  if len(function_call_results) > 0:
4222
4303
  run_messages.messages.extend(function_call_results)
4223
4304
 
4224
- def _reject_tool_call(self, run_messages: RunMessages, tool: ToolExecution):
4305
+ def _reject_tool_call(
4306
+ self, run_messages: RunMessages, tool: ToolExecution, functions: Optional[Dict[str, Function]] = None
4307
+ ):
4225
4308
  self.model = cast(Model, self.model)
4226
- function_call = self.model.get_function_call_to_run_from_tool_execution(tool, self._functions_for_model)
4309
+ function_call = self.model.get_function_call_to_run_from_tool_execution(tool, functions)
4227
4310
  function_call.error = tool.confirmation_note or "Function call was rejected by the user"
4228
4311
  function_call_result = self.model.create_function_call_result(
4229
4312
  function_call=function_call,
@@ -4236,12 +4319,13 @@ class Agent:
4236
4319
  run_response: RunOutput,
4237
4320
  run_messages: RunMessages,
4238
4321
  tool: ToolExecution,
4322
+ functions: Optional[Dict[str, Function]] = None,
4239
4323
  stream_events: bool = False,
4240
4324
  ) -> AsyncIterator[RunOutputEvent]:
4241
4325
  self.model = cast(Model, self.model)
4242
4326
 
4243
4327
  # Execute the tool
4244
- function_call = self.model.get_function_call_to_run_from_tool_execution(tool, self._functions_for_model)
4328
+ function_call = self.model.get_function_call_to_run_from_tool_execution(tool, functions)
4245
4329
  function_call_results: List[Message] = []
4246
4330
 
4247
4331
  async for call_result in self.model.arun_function_calls(
@@ -4274,17 +4358,21 @@ class Agent:
4274
4358
  if len(function_call_results) > 0:
4275
4359
  run_messages.messages.extend(function_call_results)
4276
4360
 
4277
- def _handle_tool_call_updates(self, run_response: RunOutput, run_messages: RunMessages):
4361
+ def _handle_tool_call_updates(
4362
+ self, run_response: RunOutput, run_messages: RunMessages, tools: List[Union[Function, dict]]
4363
+ ):
4278
4364
  self.model = cast(Model, self.model)
4365
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4366
+
4279
4367
  for _t in run_response.tools or []:
4280
4368
  # Case 1: Handle confirmed tools and execute them
4281
- if _t.requires_confirmation is not None and _t.requires_confirmation is True and self._functions_for_model:
4369
+ if _t.requires_confirmation is not None and _t.requires_confirmation is True and _functions:
4282
4370
  # Tool is confirmed and hasn't been run before
4283
4371
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4284
4372
  # Consume the generator without yielding
4285
- deque(self._run_tool(run_response, run_messages, _t), maxlen=0)
4373
+ deque(self._run_tool(run_response, run_messages, _t, functions=_functions), maxlen=0)
4286
4374
  else:
4287
- self._reject_tool_call(run_messages, _t)
4375
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4288
4376
  _t.confirmed = False
4289
4377
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4290
4378
  _t.tool_call_error = True
@@ -4309,20 +4397,28 @@ class Agent:
4309
4397
  _t.requires_user_input = False
4310
4398
  _t.answered = True
4311
4399
  # Consume the generator without yielding
4312
- deque(self._run_tool(run_response, run_messages, _t), maxlen=0)
4400
+ deque(self._run_tool(run_response, run_messages, _t, functions=_functions), maxlen=0)
4313
4401
 
4314
4402
  def _handle_tool_call_updates_stream(
4315
- self, run_response: RunOutput, run_messages: RunMessages, stream_events: bool = False
4403
+ self,
4404
+ run_response: RunOutput,
4405
+ run_messages: RunMessages,
4406
+ tools: List[Union[Function, dict]],
4407
+ stream_events: bool = False,
4316
4408
  ) -> Iterator[RunOutputEvent]:
4317
4409
  self.model = cast(Model, self.model)
4410
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4411
+
4318
4412
  for _t in run_response.tools or []:
4319
4413
  # Case 1: Handle confirmed tools and execute them
4320
- if _t.requires_confirmation is not None and _t.requires_confirmation is True and self._functions_for_model:
4414
+ if _t.requires_confirmation is not None and _t.requires_confirmation is True and _functions:
4321
4415
  # Tool is confirmed and hasn't been run before
4322
4416
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4323
- yield from self._run_tool(run_response, run_messages, _t, stream_events=stream_events)
4417
+ yield from self._run_tool(
4418
+ run_response, run_messages, _t, functions=_functions, stream_events=stream_events
4419
+ )
4324
4420
  else:
4325
- self._reject_tool_call(run_messages, _t)
4421
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4326
4422
  _t.confirmed = False
4327
4423
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4328
4424
  _t.tool_call_error = True
@@ -4345,21 +4441,27 @@ class Agent:
4345
4441
  # Case 4: Handle user input required tools
4346
4442
  elif _t.requires_user_input is not None and _t.requires_user_input is True:
4347
4443
  self._handle_user_input_update(tool=_t)
4348
- yield from self._run_tool(run_response, run_messages, _t, stream_events=stream_events)
4444
+ yield from self._run_tool(
4445
+ run_response, run_messages, _t, functions=_functions, stream_events=stream_events
4446
+ )
4349
4447
  _t.requires_user_input = False
4350
4448
  _t.answered = True
4351
4449
 
4352
- async def _ahandle_tool_call_updates(self, run_response: RunOutput, run_messages: RunMessages):
4450
+ async def _ahandle_tool_call_updates(
4451
+ self, run_response: RunOutput, run_messages: RunMessages, tools: List[Union[Function, dict]]
4452
+ ):
4353
4453
  self.model = cast(Model, self.model)
4454
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4455
+
4354
4456
  for _t in run_response.tools or []:
4355
4457
  # Case 1: Handle confirmed tools and execute them
4356
- if _t.requires_confirmation is not None and _t.requires_confirmation is True and self._functions_for_model:
4458
+ if _t.requires_confirmation is not None and _t.requires_confirmation is True and _functions:
4357
4459
  # Tool is confirmed and hasn't been run before
4358
4460
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4359
- async for _ in self._arun_tool(run_response, run_messages, _t):
4461
+ async for _ in self._arun_tool(run_response, run_messages, _t, functions=_functions):
4360
4462
  pass
4361
4463
  else:
4362
- self._reject_tool_call(run_messages, _t)
4464
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4363
4465
  _t.confirmed = False
4364
4466
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4365
4467
  _t.tool_call_error = True
@@ -4380,24 +4482,32 @@ class Agent:
4380
4482
  # Case 4: Handle user input required tools
4381
4483
  elif _t.requires_user_input is not None and _t.requires_user_input is True:
4382
4484
  self._handle_user_input_update(tool=_t)
4383
- async for _ in self._arun_tool(run_response, run_messages, _t):
4485
+ async for _ in self._arun_tool(run_response, run_messages, _t, functions=_functions):
4384
4486
  pass
4385
4487
  _t.requires_user_input = False
4386
4488
  _t.answered = True
4387
4489
 
4388
4490
  async def _ahandle_tool_call_updates_stream(
4389
- self, run_response: RunOutput, run_messages: RunMessages, stream_events: bool = False
4491
+ self,
4492
+ run_response: RunOutput,
4493
+ run_messages: RunMessages,
4494
+ tools: List[Union[Function, dict]],
4495
+ stream_events: bool = False,
4390
4496
  ) -> AsyncIterator[RunOutputEvent]:
4391
4497
  self.model = cast(Model, self.model)
4498
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4499
+
4392
4500
  for _t in run_response.tools or []:
4393
4501
  # Case 1: Handle confirmed tools and execute them
4394
- if _t.requires_confirmation is not None and _t.requires_confirmation is True and self._functions_for_model:
4502
+ if _t.requires_confirmation is not None and _t.requires_confirmation is True and _functions:
4395
4503
  # Tool is confirmed and hasn't been run before
4396
4504
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4397
- async for event in self._arun_tool(run_response, run_messages, _t, stream_events=stream_events):
4505
+ async for event in self._arun_tool(
4506
+ run_response, run_messages, _t, functions=_functions, stream_events=stream_events
4507
+ ):
4398
4508
  yield event
4399
4509
  else:
4400
- self._reject_tool_call(run_messages, _t)
4510
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4401
4511
  _t.confirmed = False
4402
4512
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4403
4513
  _t.tool_call_error = True
@@ -4418,7 +4528,9 @@ class Agent:
4418
4528
  # # Case 4: Handle user input required tools
4419
4529
  elif _t.requires_user_input is not None and _t.requires_user_input is True:
4420
4530
  self._handle_user_input_update(tool=_t)
4421
- async for event in self._arun_tool(run_response, run_messages, _t, stream_events=stream_events):
4531
+ async for event in self._arun_tool(
4532
+ run_response, run_messages, _t, functions=_functions, stream_events=stream_events
4533
+ ):
4422
4534
  yield event
4423
4535
  _t.requires_user_input = False
4424
4536
  _t.answered = True
@@ -4524,6 +4636,7 @@ class Agent:
4524
4636
  session: AgentSession,
4525
4637
  run_response: RunOutput,
4526
4638
  run_messages: RunMessages,
4639
+ tools: Optional[List[Union[Function, dict]]] = None,
4527
4640
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
4528
4641
  stream_events: bool = False,
4529
4642
  ) -> Iterator[RunOutputEvent]:
@@ -4543,8 +4656,7 @@ class Agent:
4543
4656
  for model_response_event in self.model.response_stream(
4544
4657
  messages=run_messages.messages,
4545
4658
  response_format=response_format,
4546
- tools=self._tools_for_model,
4547
- functions=self._functions_for_model,
4659
+ tools=tools,
4548
4660
  tool_choice=self.tool_choice,
4549
4661
  tool_call_limit=self.tool_call_limit,
4550
4662
  stream_model_response=stream_model_response,
@@ -4602,6 +4714,7 @@ class Agent:
4602
4714
  session: AgentSession,
4603
4715
  run_response: RunOutput,
4604
4716
  run_messages: RunMessages,
4717
+ tools: Optional[List[Union[Function, dict]]] = None,
4605
4718
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
4606
4719
  stream_events: bool = False,
4607
4720
  ) -> AsyncIterator[RunOutputEvent]:
@@ -4621,8 +4734,7 @@ class Agent:
4621
4734
  model_response_stream = self.model.aresponse_stream(
4622
4735
  messages=run_messages.messages,
4623
4736
  response_format=response_format,
4624
- tools=self._tools_for_model,
4625
- functions=self._functions_for_model,
4737
+ tools=tools,
4626
4738
  tool_choice=self.tool_choice,
4627
4739
  tool_call_limit=self.tool_call_limit,
4628
4740
  stream_model_response=stream_model_response,
@@ -5128,69 +5240,34 @@ class Agent:
5128
5240
  self,
5129
5241
  run_response: RunOutput,
5130
5242
  session: AgentSession,
5131
- async_mode: bool = False,
5132
5243
  user_id: Optional[str] = None,
5133
5244
  knowledge_filters: Optional[Dict[str, Any]] = None,
5134
- ) -> Optional[List[Union[Toolkit, Callable, Function, Dict]]]:
5245
+ ) -> List[Union[Toolkit, Callable, Function, Dict]]:
5135
5246
  agent_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
5136
5247
 
5137
5248
  # Add provided tools
5138
5249
  if self.tools is not None:
5139
5250
  # If not running in async mode, raise if any tool is async
5140
- if not async_mode:
5141
- self._raise_if_async_tools()
5251
+ self._raise_if_async_tools()
5142
5252
  agent_tools.extend(self.tools)
5143
5253
 
5144
- # If any of the tools has "agent" as parameter, set _rebuild_tools to True
5145
- for tool in agent_tools:
5146
- param_names = {
5147
- "agent",
5148
- "session_state",
5149
- "team",
5150
- "images",
5151
- "videos",
5152
- "audios",
5153
- "files",
5154
- }
5155
-
5156
- if isinstance(tool, Function):
5157
- if param_names & set(tool.parameters):
5158
- self._rebuild_tools = True
5159
- break
5160
- elif isinstance(tool, Toolkit):
5161
- for func in tool.functions.values():
5162
- if param_names & set(func.parameters):
5163
- self._rebuild_tools = True
5164
- break
5165
- elif callable(tool):
5166
- from inspect import signature
5167
-
5168
- if param_names & set(signature(tool).parameters):
5169
- self._rebuild_tools = True
5170
- break
5171
-
5172
5254
  # Add tools for accessing memory
5173
5255
  if self.read_chat_history:
5174
5256
  agent_tools.append(self._get_chat_history_function(session=session))
5175
- self._rebuild_tools = True
5176
5257
  if self.read_tool_call_history:
5177
5258
  agent_tools.append(self._get_tool_call_history_function(session=session))
5178
- self._rebuild_tools = True
5179
5259
  if self.search_session_history:
5180
5260
  agent_tools.append(
5181
5261
  self._get_previous_sessions_messages_function(
5182
5262
  num_history_sessions=self.num_history_sessions, user_id=user_id
5183
5263
  )
5184
5264
  )
5185
- self._rebuild_tools = True
5186
5265
 
5187
5266
  if self.enable_agentic_memory:
5188
- agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=async_mode))
5189
- self._rebuild_tools = True
5267
+ agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=False))
5190
5268
 
5191
5269
  if self.enable_agentic_culture:
5192
- agent_tools.append(self._get_update_cultural_knowledge_function(async_mode=async_mode))
5193
- self._rebuild_tools = True
5270
+ agent_tools.append(self._get_update_cultural_knowledge_function(async_mode=False))
5194
5271
 
5195
5272
  if self.enable_agentic_state:
5196
5273
  agent_tools.append(Function(name="update_session_state", entrypoint=self._update_session_state_tool))
@@ -5200,7 +5277,7 @@ class Agent:
5200
5277
  # Check if knowledge retriever is an async function but used in sync mode
5201
5278
  from inspect import iscoroutinefunction
5202
5279
 
5203
- if not async_mode and self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
5280
+ if self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
5204
5281
  log_warning(
5205
5282
  "Async knowledge retriever function is being used with synchronous agent.run() or agent.print_response(). "
5206
5283
  "It is recommended to use agent.arun() or agent.aprint_response() instead."
@@ -5212,7 +5289,7 @@ class Agent:
5212
5289
  agent_tools.append(
5213
5290
  self._search_knowledge_base_with_agentic_filters_function(
5214
5291
  run_response=run_response,
5215
- async_mode=async_mode,
5292
+ async_mode=False,
5216
5293
  knowledge_filters=knowledge_filters,
5217
5294
  )
5218
5295
  )
@@ -5220,11 +5297,10 @@ class Agent:
5220
5297
  agent_tools.append(
5221
5298
  self._get_search_knowledge_base_function(
5222
5299
  run_response=run_response,
5223
- async_mode=async_mode,
5300
+ async_mode=False,
5224
5301
  knowledge_filters=knowledge_filters,
5225
5302
  )
5226
5303
  )
5227
- self._rebuild_tools = True
5228
5304
 
5229
5305
  if self.update_knowledge:
5230
5306
  agent_tools.append(self.add_to_knowledge)
@@ -5235,82 +5311,69 @@ class Agent:
5235
5311
  self,
5236
5312
  run_response: RunOutput,
5237
5313
  session: AgentSession,
5238
- async_mode: bool = False,
5239
5314
  user_id: Optional[str] = None,
5240
5315
  knowledge_filters: Optional[Dict[str, Any]] = None,
5241
- ) -> Optional[List[Union[Toolkit, Callable, Function, Dict]]]:
5316
+ check_mcp_tools: bool = True,
5317
+ ) -> List[Union[Toolkit, Callable, Function, Dict]]:
5242
5318
  agent_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
5243
5319
 
5320
+ # Connect MCP tools
5321
+ await self._connect_mcp_tools()
5322
+
5244
5323
  # Add provided tools
5245
5324
  if self.tools is not None:
5246
- agent_tools.extend(self.tools)
5325
+ for tool in self.tools:
5326
+ if tool.__class__.__name__ in ["MCPTools", "MultiMCPTools"]:
5327
+ if tool.refresh_connection: # type: ignore
5328
+ try:
5329
+ is_alive = await tool.is_alive() # type: ignore
5330
+ if not is_alive:
5331
+ await tool.connect(force=True) # type: ignore
5332
+ except (RuntimeError, BaseException) as e:
5333
+ log_warning(f"Failed to check if MCP tool is alive or to connect to it: {e}")
5334
+ continue
5247
5335
 
5248
- # If any of the tools has "agent" as parameter, set _rebuild_tools to True
5249
- for tool in agent_tools:
5250
- if isinstance(tool, Function):
5251
- if "agent" in tool.parameters:
5252
- self._rebuild_tools = True
5253
- break
5254
- if "team" in tool.parameters:
5255
- self._rebuild_tools = True
5256
- break
5257
- if isinstance(tool, Toolkit):
5258
- for func in tool.functions.values():
5259
- if "agent" in func.parameters:
5260
- self._rebuild_tools = True
5261
- break
5262
- if "team" in func.parameters:
5263
- self._rebuild_tools = True
5264
- break
5265
- if callable(tool):
5266
- from inspect import signature
5267
-
5268
- sig = signature(tool)
5269
- if "agent" in sig.parameters:
5270
- self._rebuild_tools = True
5271
- break
5272
- if "team" in sig.parameters:
5273
- self._rebuild_tools = True
5274
- break
5336
+ try:
5337
+ await tool.build_tools() # type: ignore
5338
+ except (RuntimeError, BaseException) as e:
5339
+ log_warning(f"Failed to build tools for {str(tool)}: {e}")
5340
+ continue
5341
+
5342
+ # Only add the tool if it successfully connected and built its tools
5343
+ if check_mcp_tools and not tool.initialized: # type: ignore
5344
+ continue
5345
+
5346
+ agent_tools.append(tool)
5347
+ else:
5348
+ agent_tools.append(tool)
5275
5349
 
5276
5350
  # Add tools for accessing memory
5277
5351
  if self.read_chat_history:
5278
5352
  agent_tools.append(self._get_chat_history_function(session=session))
5279
- self._rebuild_tools = True
5280
5353
  if self.read_tool_call_history:
5281
5354
  agent_tools.append(self._get_tool_call_history_function(session=session))
5282
- self._rebuild_tools = True
5283
5355
  if self.search_session_history:
5284
5356
  agent_tools.append(
5285
- await self._aget_previous_sessions_messages_function(num_history_sessions=self.num_history_sessions)
5357
+ await self._aget_previous_sessions_messages_function(
5358
+ num_history_sessions=self.num_history_sessions, user_id=user_id
5359
+ )
5286
5360
  )
5287
- self._rebuild_tools = True
5288
5361
 
5289
5362
  if self.enable_agentic_memory:
5290
- agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=async_mode))
5291
- self._rebuild_tools = True
5363
+ agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=True))
5292
5364
 
5293
5365
  if self.enable_agentic_state:
5294
5366
  agent_tools.append(Function(name="update_session_state", entrypoint=self._update_session_state_tool))
5295
5367
 
5296
5368
  # Add tools for accessing knowledge
5297
5369
  if self.knowledge is not None or self.knowledge_retriever is not None:
5298
- # Check if knowledge retriever is an async function but used in sync mode
5299
- from inspect import iscoroutinefunction
5300
-
5301
- if not async_mode and self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
5302
- log_warning(
5303
- "Async knowledge retriever function is being used with synchronous agent.run() or agent.print_response(). "
5304
- "It is recommended to use agent.arun() or agent.aprint_response() instead."
5305
- )
5306
-
5307
5370
  if self.search_knowledge:
5308
5371
  # Use async or sync search based on async_mode
5309
5372
  if self.enable_agentic_knowledge_filters:
5310
5373
  agent_tools.append(
5311
5374
  self._search_knowledge_base_with_agentic_filters_function(
5312
5375
  run_response=run_response,
5313
- async_mode=async_mode,
5376
+ async_mode=True,
5314
5377
  knowledge_filters=knowledge_filters,
5315
5378
  )
5316
5379
  )
@@ -5318,11 +5381,10 @@ class Agent:
5318
5381
  agent_tools.append(
5319
5382
  self._get_search_knowledge_base_function(
5320
5383
  run_response=run_response,
5321
- async_mode=async_mode,
5384
+ async_mode=True,
5322
5385
  knowledge_filters=knowledge_filters,
5323
5386
  )
5324
5387
  )
5325
- self._rebuild_tools = True
5326
5388
 
5327
5389
  if self.update_knowledge:
5328
5390
  agent_tools.append(self.add_to_knowledge)
@@ -5332,231 +5394,105 @@ class Agent:
5332
5394
  def _determine_tools_for_model(
5333
5395
  self,
5334
5396
  model: Model,
5397
+ processed_tools: List[Union[Toolkit, Callable, Function, Dict]],
5335
5398
  run_response: RunOutput,
5336
5399
  session: AgentSession,
5337
5400
  session_state: Optional[Dict[str, Any]] = None,
5338
5401
  dependencies: Optional[Dict[str, Any]] = None,
5339
- user_id: Optional[str] = None,
5340
- async_mode: bool = False,
5341
- knowledge_filters: Optional[Dict[str, Any]] = None,
5342
- ) -> None:
5343
- if self._rebuild_tools:
5344
- self._rebuild_tools = False
5345
-
5346
- agent_tools = self.get_tools(
5347
- run_response=run_response,
5348
- session=session,
5349
- async_mode=async_mode,
5350
- user_id=user_id,
5351
- knowledge_filters=knowledge_filters,
5352
- )
5353
-
5354
- self._tools_for_model = []
5355
- self._functions_for_model = {}
5356
- self._tool_instructions = []
5357
-
5358
- # Get Agent tools
5359
- if agent_tools is not None and len(agent_tools) > 0:
5360
- log_debug("Processing tools for model")
5361
-
5362
- # Check if we need strict mode for the functions for the model
5363
- strict = False
5364
- if (
5365
- self.output_schema is not None
5366
- and (self.structured_outputs or (not self.use_json_mode))
5367
- and model.supports_native_structured_outputs
5368
- ):
5369
- strict = True
5370
-
5371
- for tool in agent_tools:
5372
- if isinstance(tool, Dict):
5373
- # If a dict is passed, it is a builtin tool
5374
- # that is run by the model provider and not the Agent
5375
- self._tools_for_model.append(tool)
5376
- log_debug(f"Included builtin tool {tool}")
5377
-
5378
- elif isinstance(tool, Toolkit):
5379
- # For each function in the toolkit and process entrypoint
5380
- for name, func in tool.functions.items():
5381
- # If the function does not exist in self.functions
5382
- if name not in self._functions_for_model:
5383
- func._agent = self
5384
- func.process_entrypoint(strict=strict)
5385
- if strict and func.strict is None:
5386
- func.strict = True
5387
- if self.tool_hooks is not None:
5388
- func.tool_hooks = self.tool_hooks
5389
- self._functions_for_model[name] = func
5390
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5391
- log_debug(f"Added tool {name} from {tool.name}")
5392
-
5393
- # Add instructions from the toolkit
5394
- if tool.add_instructions and tool.instructions is not None:
5395
- self._tool_instructions.append(tool.instructions)
5396
-
5397
- elif isinstance(tool, Function):
5398
- if tool.name not in self._functions_for_model:
5399
- tool._agent = self
5400
- tool.process_entrypoint(strict=strict)
5401
- if strict and tool.strict is None:
5402
- tool.strict = True
5403
- if self.tool_hooks is not None:
5404
- tool.tool_hooks = self.tool_hooks
5405
- self._functions_for_model[tool.name] = tool
5406
- self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
5407
- log_debug(f"Added tool {tool.name}")
5408
-
5409
- # Add instructions from the Function
5410
- if tool.add_instructions and tool.instructions is not None:
5411
- self._tool_instructions.append(tool.instructions)
5412
-
5413
- elif callable(tool):
5414
- try:
5415
- function_name = tool.__name__
5416
- if function_name not in self._functions_for_model:
5417
- func = Function.from_callable(tool, strict=strict)
5418
- func._agent = self
5419
- if strict:
5420
- func.strict = True
5421
- if self.tool_hooks is not None:
5422
- func.tool_hooks = self.tool_hooks
5423
- self._functions_for_model[func.name] = func
5424
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5425
- log_debug(f"Added tool {func.name}")
5426
- except Exception as e:
5427
- log_warning(f"Could not add tool {tool}: {e}")
5428
-
5429
- # Update the session state for the functions
5430
- if self._functions_for_model:
5431
- from inspect import signature
5432
-
5433
- # Check if any functions need media before collecting
5434
- needs_media = any(
5435
- any(param in signature(func.entrypoint).parameters for param in ["images", "videos", "audios", "files"])
5436
- for func in self._functions_for_model.values()
5437
- if func.entrypoint is not None
5438
- )
5439
-
5440
- # Only collect media if functions actually need them
5441
- joint_images = collect_joint_images(run_response.input, session) if needs_media else None
5442
- joint_files = collect_joint_files(run_response.input) if needs_media else None
5443
- joint_audios = collect_joint_audios(run_response.input, session) if needs_media else None
5444
- joint_videos = collect_joint_videos(run_response.input, session) if needs_media else None
5402
+ ) -> List[Union[Function, dict]]:
5403
+ _function_names = []
5404
+ _functions: List[Union[Function, dict]] = []
5405
+ self._tool_instructions = []
5445
5406
 
5446
- for func in self._functions_for_model.values():
5447
- func._session_state = session_state
5448
- func._dependencies = dependencies
5449
- func._images = joint_images
5450
- func._files = joint_files
5451
- func._audios = joint_audios
5452
- func._videos = joint_videos
5407
+ # Get Agent tools
5408
+ if processed_tools is not None and len(processed_tools) > 0:
5409
+ log_debug("Processing tools for model")
5453
5410
 
5454
- async def _adetermine_tools_for_model(
5455
- self,
5456
- model: Model,
5457
- run_response: RunOutput,
5458
- session: AgentSession,
5459
- session_state: Optional[Dict[str, Any]] = None,
5460
- dependencies: Optional[Dict[str, Any]] = None,
5461
- user_id: Optional[str] = None,
5462
- async_mode: bool = False,
5463
- knowledge_filters: Optional[Dict[str, Any]] = None,
5464
- ) -> None:
5465
- if self._rebuild_tools:
5466
- self._rebuild_tools = False
5467
-
5468
- agent_tools = await self.aget_tools(
5469
- run_response=run_response,
5470
- session=session,
5471
- async_mode=async_mode,
5472
- user_id=user_id,
5473
- knowledge_filters=knowledge_filters,
5474
- )
5411
+ # Check if we need strict mode for the functions for the model
5412
+ strict = False
5413
+ if (
5414
+ self.output_schema is not None
5415
+ and (self.structured_outputs or (not self.use_json_mode))
5416
+ and model.supports_native_structured_outputs
5417
+ ):
5418
+ strict = True
5475
5419
 
5476
- self._tools_for_model = []
5477
- self._functions_for_model = {}
5478
- self._tool_instructions = []
5420
+ for tool in processed_tools:
5421
+ if isinstance(tool, Dict):
5422
+ # If a dict is passed, it is a builtin tool
5423
+ # that is run by the model provider and not the Agent
5424
+ _functions.append(tool)
5425
+ log_debug(f"Included builtin tool {tool}")
5479
5426
 
5480
- # Get Agent tools
5481
- if agent_tools is not None and len(agent_tools) > 0:
5482
- log_debug("Processing tools for model")
5427
+ elif isinstance(tool, Toolkit):
5428
+ # For each function in the toolkit and process entrypoint
5429
+ for name, _func in tool.functions.items():
5430
+ if name in _function_names:
5431
+ continue
5432
+ _function_names.append(name)
5433
+ _func = _func.model_copy(deep=True)
5434
+ _func._agent = self
5435
+ _func.process_entrypoint(strict=strict)
5436
+ if strict and _func.strict is None:
5437
+ _func.strict = True
5438
+ if self.tool_hooks is not None:
5439
+ _func.tool_hooks = self.tool_hooks
5440
+ _functions.append(_func)
5441
+ log_debug(f"Added tool {name} from {tool.name}")
5442
+
5443
+ # Add instructions from the toolkit
5444
+ if tool.add_instructions and tool.instructions is not None:
5445
+ self._tool_instructions.append(tool.instructions)
5446
+
5447
+ elif isinstance(tool, Function):
5448
+ if tool.name in _function_names:
5449
+ continue
5450
+ _function_names.append(tool.name)
5451
+
5452
+ tool.process_entrypoint(strict=strict)
5453
+ tool = tool.model_copy(deep=True)
5454
+
5455
+ tool._agent = self
5456
+ if strict and tool.strict is None:
5457
+ tool.strict = True
5458
+ if self.tool_hooks is not None:
5459
+ tool.tool_hooks = self.tool_hooks
5460
+ _functions.append(tool)
5461
+ log_debug(f"Added tool {tool.name}")
5462
+
5463
+ # Add instructions from the Function
5464
+ if tool.add_instructions and tool.instructions is not None:
5465
+ self._tool_instructions.append(tool.instructions)
5483
5466
 
5484
- # Check if we need strict mode for the functions for the model
5485
- strict = False
5486
- if (
5487
- self.output_schema is not None
5488
- and (self.structured_outputs or (not self.use_json_mode))
5489
- and model.supports_native_structured_outputs
5490
- ):
5491
- strict = True
5492
-
5493
- for tool in agent_tools:
5494
- if isinstance(tool, Dict):
5495
- # If a dict is passed, it is a builtin tool
5496
- # that is run by the model provider and not the Agent
5497
- self._tools_for_model.append(tool)
5498
- log_debug(f"Included builtin tool {tool}")
5499
-
5500
- elif isinstance(tool, Toolkit):
5501
- # For each function in the toolkit and process entrypoint
5502
- for name, func in tool.functions.items():
5503
- # If the function does not exist in self.functions
5504
- if name not in self._functions_for_model:
5505
- func._agent = self
5506
- func.process_entrypoint(strict=strict)
5507
- if strict and func.strict is None:
5508
- func.strict = True
5509
- if self.tool_hooks is not None:
5510
- func.tool_hooks = self.tool_hooks
5511
- self._functions_for_model[name] = func
5512
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5513
- log_debug(f"Added tool {name} from {tool.name}")
5514
-
5515
- # Add instructions from the toolkit
5516
- if tool.add_instructions and tool.instructions is not None:
5517
- self._tool_instructions.append(tool.instructions)
5518
-
5519
- elif isinstance(tool, Function):
5520
- if tool.name not in self._functions_for_model:
5521
- tool._agent = self
5522
- tool.process_entrypoint(strict=strict)
5523
- if strict and tool.strict is None:
5524
- tool.strict = True
5525
- if self.tool_hooks is not None:
5526
- tool.tool_hooks = self.tool_hooks
5527
- self._functions_for_model[tool.name] = tool
5528
- self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
5529
- log_debug(f"Added tool {tool.name}")
5530
-
5531
- # Add instructions from the Function
5532
- if tool.add_instructions and tool.instructions is not None:
5533
- self._tool_instructions.append(tool.instructions)
5534
-
5535
- elif callable(tool):
5536
- try:
5537
- function_name = tool.__name__
5538
- if function_name not in self._functions_for_model:
5539
- func = Function.from_callable(tool, strict=strict)
5540
- func._agent = self
5541
- if strict:
5542
- func.strict = True
5543
- if self.tool_hooks is not None:
5544
- func.tool_hooks = self.tool_hooks
5545
- self._functions_for_model[func.name] = func
5546
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5547
- log_debug(f"Added tool {func.name}")
5548
- except Exception as e:
5549
- log_warning(f"Could not add tool {tool}: {e}")
5467
+ elif callable(tool):
5468
+ try:
5469
+ function_name = tool.__name__
5470
+
5471
+ if function_name in _function_names:
5472
+ continue
5473
+ _function_names.append(function_name)
5474
+
5475
+ _func = Function.from_callable(tool, strict=strict)
5476
+ _func = _func.model_copy(deep=True)
5477
+ _func._agent = self
5478
+ if strict:
5479
+ _func.strict = True
5480
+ if self.tool_hooks is not None:
5481
+ _func.tool_hooks = self.tool_hooks
5482
+ _functions.append(_func)
5483
+ log_debug(f"Added tool {_func.name}")
5484
+ except Exception as e:
5485
+ log_warning(f"Could not add tool {tool}: {e}")
5550
5486
 
5551
5487
  # Update the session state for the functions
5552
- if self._functions_for_model:
5488
+ if _functions:
5553
5489
  from inspect import signature
5554
5490
 
5555
5491
  # Check if any functions need media before collecting
5556
5492
  needs_media = any(
5557
5493
  any(param in signature(func.entrypoint).parameters for param in ["images", "videos", "audios", "files"])
5558
- for func in self._functions_for_model.values()
5559
- if func.entrypoint is not None
5494
+ for func in _functions
5495
+ if isinstance(func, Function) and func.entrypoint is not None
5560
5496
  )
5561
5497
 
5562
5498
  # Only collect media if functions actually need them
@@ -5565,13 +5501,16 @@ class Agent:
5565
5501
  joint_audios = collect_joint_audios(run_response.input, session) if needs_media else None
5566
5502
  joint_videos = collect_joint_videos(run_response.input, session) if needs_media else None
5567
5503
 
5568
- for func in self._functions_for_model.values():
5569
- func._session_state = session_state
5570
- func._dependencies = dependencies
5571
- func._images = joint_images
5572
- func._files = joint_files
5573
- func._audios = joint_audios
5574
- func._videos = joint_videos
5504
+ for func in _functions: # type: ignore
5505
+ if isinstance(func, Function):
5506
+ func._session_state = session_state
5507
+ func._dependencies = dependencies
5508
+ func._images = joint_images
5509
+ func._files = joint_files
5510
+ func._audios = joint_audios
5511
+ func._videos = joint_videos
5512
+
5513
+ return _functions
5575
5514
 
5576
5515
  def _model_should_return_structured_output(self):
5577
5516
  self.model = cast(Model, self.model)
@@ -5671,6 +5610,18 @@ class Agent:
5671
5610
  agent_data["model"] = self.model.to_dict()
5672
5611
  return agent_data
5673
5612
 
5613
+ @staticmethod
5614
+ def cancel_run(run_id: str) -> bool:
5615
+ """Cancel a running agent execution.
5616
+
5617
+ Args:
5618
+ run_id (str): The run_id to cancel.
5619
+
5620
+ Returns:
5621
+ bool: True if the run was found and marked for cancellation, False otherwise.
5622
+ """
5623
+ return cancel_run_global(run_id)
5624
+
5674
5625
  # -*- Session Database Functions
5675
5626
  def _read_session(
5676
5627
  self, session_id: str, session_type: SessionType = SessionType.AGENT
@@ -5774,8 +5725,8 @@ class Agent:
5774
5725
  from time import time
5775
5726
 
5776
5727
  # Returning cached session if we have one
5777
- if self._agent_session is not None and self._agent_session.session_id == session_id:
5778
- return self._agent_session
5728
+ if self._cached_session is not None and self._cached_session.session_id == session_id:
5729
+ return self._cached_session
5779
5730
 
5780
5731
  # Try to load from database
5781
5732
  agent_session = None
@@ -5803,7 +5754,7 @@ class Agent:
5803
5754
  )
5804
5755
 
5805
5756
  if self.cache_session:
5806
- self._agent_session = agent_session
5757
+ self._cached_session = agent_session
5807
5758
 
5808
5759
  return agent_session
5809
5760
 
@@ -5815,8 +5766,8 @@ class Agent:
5815
5766
  from time import time
5816
5767
 
5817
5768
  # Returning cached session if we have one
5818
- if self._agent_session is not None and self._agent_session.session_id == session_id:
5819
- return self._agent_session
5769
+ if self._cached_session is not None and self._cached_session.session_id == session_id:
5770
+ return self._cached_session
5820
5771
 
5821
5772
  # Try to load from database
5822
5773
  agent_session = None
@@ -5846,10 +5797,11 @@ class Agent:
5846
5797
  )
5847
5798
 
5848
5799
  if self.cache_session:
5849
- self._agent_session = agent_session
5800
+ self._cached_session = agent_session
5850
5801
 
5851
5802
  return agent_session
5852
5803
 
5804
+ # -*- Public Convenience Functions
5853
5805
  def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
5854
5806
  """
5855
5807
  Get a RunOutput from the database.
@@ -5857,23 +5809,30 @@ class Agent:
5857
5809
  Args:
5858
5810
  run_id (str): The run_id to load from storage.
5859
5811
  session_id (Optional[str]): The session_id to load from storage.
5812
+ Returns:
5813
+ Optional[RunOutput]: The RunOutput from the database or None if not found.
5860
5814
  """
5861
- if self._agent_session is not None:
5862
- run_response = self._agent_session.get_run(run_id=run_id)
5863
- if run_response is not None:
5864
- return run_response
5865
- else:
5866
- log_warning(f"RunOutput {run_id} not found in AgentSession {self._agent_session.session_id}")
5867
- return None
5868
- else:
5869
- session = self.get_session(session_id=session_id)
5870
- if session is not None:
5871
- run_response = session.get_run(run_id=run_id)
5872
- if run_response is not None:
5873
- return run_response
5874
- else:
5875
- log_warning(f"RunOutput {run_id} not found in Session {session_id}")
5876
- return None
5815
+ if not session_id and not self.session_id:
5816
+ raise Exception("No session_id provided")
5817
+
5818
+ session_id_to_load = session_id or self.session_id
5819
+ return cast(RunOutput, get_run_output_util(self, run_id=run_id, session_id=session_id_to_load))
5820
+
5821
+ async def aget_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
5822
+ """
5823
+ Get a RunOutput from the database.
5824
+
5825
+ Args:
5826
+ run_id (str): The run_id to load from storage.
5827
+ session_id (Optional[str]): The session_id to load from storage.
5828
+ Returns:
5829
+ Optional[RunOutput]: The RunOutput from the database or None if not found.
5830
+ """
5831
+ if not session_id and not self.session_id:
5832
+ raise Exception("No session_id provided")
5833
+
5834
+ session_id_to_load = session_id or self.session_id
5835
+ return cast(RunOutput, await aget_run_output_util(self, run_id=run_id, session_id=session_id_to_load))
5877
5836
 
5878
5837
  def get_last_run_output(self, session_id: Optional[str] = None) -> Optional[RunOutput]:
5879
5838
  """
@@ -5883,36 +5842,29 @@ class Agent:
5883
5842
  session_id (Optional[str]): The session_id to load from storage.
5884
5843
 
5885
5844
  Returns:
5886
- RunOutput: The last run response from the database.
5845
+ Optional[RunOutput]: The last run response from the database or None if not found.
5887
5846
  """
5888
- if (
5889
- self._agent_session is not None
5890
- and self._agent_session.runs is not None
5891
- and len(self._agent_session.runs) > 0
5892
- ):
5893
- for run_output in reversed(self._agent_session.runs):
5894
- if hasattr(run_output, "agent_id") and run_output.agent_id == self.id:
5895
- return run_output
5896
- else:
5897
- session = self.get_session(session_id=session_id)
5898
- if session is not None and session.runs is not None and len(session.runs) > 0:
5899
- for run_output in reversed(session.runs):
5900
- if hasattr(run_output, "agent_id") and run_output.agent_id == self.id:
5901
- return run_output
5902
- else:
5903
- log_warning(f"No run responses found in Session {session_id}")
5904
- return None
5847
+ if not session_id and not self.session_id:
5848
+ raise Exception("No session_id provided")
5905
5849
 
5906
- def cancel_run(self, run_id: str) -> bool:
5907
- """Cancel a running agent execution.
5850
+ session_id_to_load = session_id or self.session_id
5851
+ return cast(RunOutput, get_last_run_output_util(self, session_id=session_id_to_load))
5852
+
5853
+ async def aget_last_run_output(self, session_id: Optional[str] = None) -> Optional[RunOutput]:
5854
+ """
5855
+ Get the last run response from the database.
5908
5856
 
5909
5857
  Args:
5910
- run_id (str): The run_id to cancel.
5858
+ session_id (Optional[str]): The session_id to load from storage.
5911
5859
 
5912
5860
  Returns:
5913
- bool: True if the run was found and marked for cancellation, False otherwise.
5861
+ Optional[RunOutput]: The last run response from the database or None if not found.
5914
5862
  """
5915
- return cancel_run_global(run_id)
5863
+ if not session_id and not self.session_id:
5864
+ raise Exception("No session_id provided")
5865
+
5866
+ session_id_to_load = session_id or self.session_id
5867
+ return cast(RunOutput, await aget_last_run_output_util(self, session_id=session_id_to_load))
5916
5868
 
5917
5869
  def get_session(
5918
5870
  self,
@@ -5932,9 +5884,12 @@ class Agent:
5932
5884
  session_id_to_load = session_id or self.session_id
5933
5885
 
5934
5886
  # If there is a cached session, return it
5935
- if self.cache_session and hasattr(self, "_agent_session") and self._agent_session is not None:
5936
- if self._agent_session.session_id == session_id_to_load:
5937
- return self._agent_session
5887
+ if self.cache_session and hasattr(self, "_cached_session") and self._cached_session is not None:
5888
+ if self._cached_session.session_id == session_id_to_load:
5889
+ return self._cached_session
5890
+
5891
+ if self._has_async_db():
5892
+ raise ValueError("Async database not supported for get_session")
5938
5893
 
5939
5894
  # Load and return the session from the database
5940
5895
  if self.db is not None:
@@ -5965,7 +5920,7 @@ class Agent:
5965
5920
 
5966
5921
  # Cache the session if relevant
5967
5922
  if loaded_session is not None and self.cache_session:
5968
- self._agent_session = loaded_session
5923
+ self._cached_session = loaded_session
5969
5924
 
5970
5925
  return loaded_session
5971
5926
 
@@ -5990,29 +5945,53 @@ class Agent:
5990
5945
  session_id_to_load = session_id or self.session_id
5991
5946
 
5992
5947
  # If there is a cached session, return it
5993
- if self.cache_session and hasattr(self, "_agent_session") and self._agent_session is not None:
5994
- if self._agent_session.session_id == session_id_to_load:
5995
- return self._agent_session
5948
+ if self.cache_session and hasattr(self, "_cached_session") and self._cached_session is not None:
5949
+ if self._cached_session.session_id == session_id_to_load:
5950
+ return self._cached_session
5996
5951
 
5997
5952
  # Load and return the session from the database
5998
5953
  if self.db is not None:
5999
- agent_session = cast(AgentSession, await self._aread_session(session_id=session_id_to_load)) # type: ignore
5954
+ loaded_session = None
5955
+
5956
+ # We have a standalone agent, so we are loading an AgentSession
5957
+ if self.team_id is None and self.workflow_id is None:
5958
+ loaded_session = cast(
5959
+ AgentSession,
5960
+ await self._aread_session(session_id=session_id_to_load, session_type=SessionType.AGENT), # type: ignore
5961
+ )
5962
+
5963
+ # We have a team member agent, so we are loading a TeamSession
5964
+ if loaded_session is None and self.team_id is not None:
5965
+ # Load session for team member agents
5966
+ loaded_session = cast(
5967
+ TeamSession,
5968
+ await self._aread_session(session_id=session_id_to_load, session_type=SessionType.TEAM), # type: ignore
5969
+ )
5970
+
5971
+ # We have a workflow member agent, so we are loading a WorkflowSession
5972
+ if loaded_session is None and self.workflow_id is not None:
5973
+ # Load session for workflow memberagents
5974
+ loaded_session = cast(
5975
+ WorkflowSession,
5976
+ await self._aread_session(session_id=session_id_to_load, session_type=SessionType.WORKFLOW), # type: ignore
5977
+ )
6000
5978
 
6001
5979
  # Cache the session if relevant
6002
- if agent_session is not None and self.cache_session:
6003
- self._agent_session = agent_session
5980
+ if loaded_session is not None and self.cache_session:
5981
+ self._cached_session = loaded_session
6004
5982
 
6005
- return agent_session
5983
+ return loaded_session
6006
5984
 
6007
5985
  log_debug(f"AgentSession {session_id_to_load} not found in db")
6008
5986
  return None
6009
5987
 
6010
5988
  def save_session(self, session: AgentSession) -> None:
6011
- """Save the AgentSession to storage
6012
-
6013
- Returns:
6014
- Optional[AgentSession]: The saved AgentSession or None if not saved.
6015
5989
  """
5990
+ Save the AgentSession to storage
5991
+ """
5992
+ if self._has_async_db():
5993
+ raise ValueError("Async database not supported for save_session")
5994
+
6016
5995
  # If the agent is a member of a team, do not save the session to the database
6017
5996
  if (
6018
5997
  self.db is not None
@@ -6029,10 +6008,8 @@ class Agent:
6029
6008
  log_debug(f"Created or updated AgentSession record: {session.session_id}")
6030
6009
 
6031
6010
  async def asave_session(self, session: AgentSession) -> None:
6032
- """Save the AgentSession to storage
6033
-
6034
- Returns:
6035
- Optional[AgentSession]: The saved AgentSession or None if not saved.
6011
+ """
6012
+ Save the AgentSession to storage
6036
6013
  """
6037
6014
  # If the agent is a member of a team, do not save the session to the database
6038
6015
  if (
@@ -6052,27 +6029,56 @@ class Agent:
6052
6029
  log_debug(f"Created or updated AgentSession record: {session.session_id}")
6053
6030
 
6054
6031
  def get_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
6055
- """Read the chat history from the session"""
6056
- if not session_id and not self.session_id:
6057
- raise Exception("No session_id provided")
6032
+ """Read the chat history from the session
6058
6033
 
6059
- session_id_to_load = session_id or self.session_id
6060
- session = self.get_session(session_id=session_id_to_load)
6034
+ Args:
6035
+ session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
6036
+ Returns:
6037
+ List[Message]: The chat history from the session.
6038
+ """
6039
+ session_id = session_id or self.session_id
6040
+ if session_id is None:
6041
+ log_warning("Session ID is not set, cannot get chat history")
6042
+ return []
6061
6043
 
6062
- if session is None:
6063
- raise Exception(f"Session {session_id_to_load} not found")
6044
+ return get_chat_history_util(self, session_id=session_id)
6045
+
6046
+ async def aget_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
6047
+ """Read the chat history from the session
6048
+
6049
+ Args:
6050
+ session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
6051
+ Returns:
6052
+ List[Message]: The chat history from the session.
6053
+ """
6054
+ session_id = session_id or self.session_id
6055
+ if session_id is None:
6056
+ log_warning("Session ID is not set, cannot get chat history")
6057
+ return []
6064
6058
 
6065
- return session.get_chat_history()
6059
+ return await aget_chat_history_util(self, session_id=session_id)
6066
6060
 
6061
+ # -*- Session Management Functions
6067
6062
  def rename(self, name: str, session_id: Optional[str] = None) -> None:
6068
- """Rename the Agent and save to storage"""
6063
+ """
6064
+ Rename the Agent and save to storage
6065
+
6066
+ Args:
6067
+ name (str): The new name for the Agent.
6068
+ session_id (Optional[str]): The session_id of the session where to store the new name. If not provided, the current cached session ID is used.
6069
+ """
6069
6070
 
6070
6071
  session_id = session_id or self.session_id
6071
6072
 
6072
6073
  if session_id is None:
6073
6074
  raise Exception("Session ID is not set")
6074
6075
 
6075
- session = self.get_session(session_id=session_id) # type: ignore
6076
+ if self._has_async_db():
6077
+ import asyncio
6078
+
6079
+ session = asyncio.run(self.aget_session(session_id=session_id))
6080
+ else:
6081
+ session = self.get_session(session_id=session_id)
6076
6082
 
6077
6083
  if session is None:
6078
6084
  raise Exception("Session not found")
@@ -6085,7 +6091,12 @@ class Agent:
6085
6091
  session.agent_data = {"name": name}
6086
6092
 
6087
6093
  # -*- Save to storage
6088
- self.save_session(session=session) # type: ignore
6094
+ if self._has_async_db():
6095
+ import asyncio
6096
+
6097
+ asyncio.run(self.asave_session(session=session))
6098
+ else:
6099
+ self.save_session(session=session)
6089
6100
 
6090
6101
  def set_session_name(
6091
6102
  self,
@@ -6093,38 +6104,63 @@ class Agent:
6093
6104
  autogenerate: bool = False,
6094
6105
  session_name: Optional[str] = None,
6095
6106
  ) -> AgentSession:
6096
- """Set the session name and save to storage"""
6107
+ """
6108
+ Set the session name and save to storage
6109
+
6110
+ Args:
6111
+ session_id: The session ID to set the name for. If not provided, the current cached session ID is used.
6112
+ autogenerate: Whether to autogenerate the session name.
6113
+ session_name: The session name to set. If not provided, the session name will be autogenerated.
6114
+ Returns:
6115
+ AgentSession: The updated session.
6116
+ """
6097
6117
  session_id = session_id or self.session_id
6098
6118
 
6099
6119
  if session_id is None:
6100
6120
  raise Exception("Session ID is not set")
6101
6121
 
6102
- # -*- Read from storage
6103
- session = self.get_session(session_id=session_id) # type: ignore
6122
+ return cast(
6123
+ AgentSession,
6124
+ set_session_name_util(self, session_id=session_id, autogenerate=autogenerate, session_name=session_name),
6125
+ )
6104
6126
 
6105
- if session is None:
6106
- raise Exception("Session not found")
6127
+ async def aset_session_name(
6128
+ self,
6129
+ session_id: Optional[str] = None,
6130
+ autogenerate: bool = False,
6131
+ session_name: Optional[str] = None,
6132
+ ) -> AgentSession:
6133
+ """
6134
+ Set the session name and save to storage
6107
6135
 
6108
- # -*- Generate name for session
6109
- if autogenerate:
6110
- session_name = self._generate_session_name(session=session)
6111
- log_debug(f"Generated Session Name: {session_name}")
6112
- elif session_name is None:
6113
- raise Exception("Session Name is not set")
6136
+ Args:
6137
+ session_id: The session ID to set the name for. If not provided, the current cached session ID is used.
6138
+ autogenerate: Whether to autogenerate the session name.
6139
+ session_name: The session name to set. If not provided, the session name will be autogenerated.
6140
+ Returns:
6141
+ AgentSession: The updated session.
6142
+ """
6143
+ session_id = session_id or self.session_id
6114
6144
 
6115
- # -*- Rename session
6116
- if session.session_data is not None:
6117
- session.session_data["session_name"] = session_name
6118
- else:
6119
- session.session_data = {"session_name": session_name}
6145
+ if session_id is None:
6146
+ raise Exception("Session ID is not set")
6120
6147
 
6121
- # -*- Save to storage
6122
- self.save_session(session=session) # type: ignore
6148
+ return cast(
6149
+ AgentSession,
6150
+ await aset_session_name_util(
6151
+ self, session_id=session_id, autogenerate=autogenerate, session_name=session_name
6152
+ ),
6153
+ )
6123
6154
 
6124
- return session
6155
+ def generate_session_name(self, session: AgentSession) -> str:
6156
+ """
6157
+ Generate a name for the session using the first 6 messages from the memory
6125
6158
 
6126
- def _generate_session_name(self, session: AgentSession) -> str:
6127
- """Generate a name for the session using the first 6 messages from the memory"""
6159
+ Args:
6160
+ session (AgentSession): The session to generate a name for.
6161
+ Returns:
6162
+ str: The generated session name.
6163
+ """
6128
6164
 
6129
6165
  if self.model is None:
6130
6166
  raise Exception("Model not set")
@@ -6151,32 +6187,68 @@ class Agent:
6151
6187
  content = generated_name.content
6152
6188
  if content is None:
6153
6189
  log_error("Generated name is None. Trying again.")
6154
- return self._generate_session_name(session=session)
6190
+ return self.generate_session_name(session=session)
6155
6191
 
6156
6192
  if len(content.split()) > 5:
6157
6193
  log_error("Generated name is too long. It should be less than 5 words. Trying again.")
6158
- return self._generate_session_name(session=session)
6194
+ return self.generate_session_name(session=session)
6159
6195
  return content.replace('"', "").strip()
6160
6196
 
6161
6197
  def get_session_name(self, session_id: Optional[str] = None) -> str:
6162
- """Get the session name for the given session ID and user ID."""
6198
+ """
6199
+ Get the session name for the given session ID.
6200
+
6201
+ Args:
6202
+ session_id: The session ID to get the name for. If not provided, the current cached session ID is used.
6203
+ Returns:
6204
+ str: The session name.
6205
+ """
6163
6206
  session_id = session_id or self.session_id
6164
6207
  if session_id is None:
6165
6208
  raise Exception("Session ID is not set")
6166
- session = self.get_session(session_id=session_id) # type: ignore
6167
- if session is None:
6168
- raise Exception("Session not found")
6169
- return session.session_data.get("session_name", "") if session.session_data is not None else ""
6209
+ return get_session_name_util(self, session_id=session_id)
6210
+
6211
+ async def aget_session_name(self, session_id: Optional[str] = None) -> str:
6212
+ """
6213
+ Get the session name for the given session ID.
6214
+
6215
+ Args:
6216
+ session_id: The session ID to get the name for. If not provided, the current cached session ID is used.
6217
+ Returns:
6218
+ str: The session name.
6219
+ """
6220
+ session_id = session_id or self.session_id
6221
+ if session_id is None:
6222
+ raise Exception("Session ID is not set")
6223
+ return await aget_session_name_util(self, session_id=session_id)
6170
6224
 
6171
6225
  def get_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
6172
- """Get the session state for the given session ID and user ID."""
6226
+ """
6227
+ Get the session state for the given session ID.
6228
+
6229
+ Args:
6230
+ session_id: The session ID to get the state for. If not provided, the current cached session ID is used.
6231
+ Returns:
6232
+ Dict[str, Any]: The session state.
6233
+ """
6173
6234
  session_id = session_id or self.session_id
6174
6235
  if session_id is None:
6175
6236
  raise Exception("Session ID is not set")
6176
- session = self.get_session(session_id=session_id) # type: ignore
6177
- if session is None:
6178
- raise Exception("Session not found")
6179
- return session.session_data.get("session_state", {}) if session.session_data is not None else {}
6237
+ return get_session_state_util(self, session_id=session_id)
6238
+
6239
+ async def aget_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
6240
+ """
6241
+ Get the session state for the given session ID.
6242
+
6243
+ Args:
6244
+ session_id: The session ID to get the state for. If not provided, the current cached session ID is used.
6245
+ Returns:
6246
+ Dict[str, Any]: The session state.
6247
+ """
6248
+ session_id = session_id or self.session_id
6249
+ if session_id is None:
6250
+ raise Exception("Session ID is not set")
6251
+ return await aget_session_state_util(self, session_id=session_id)
6180
6252
 
6181
6253
  def update_session_state(self, session_state_updates: Dict[str, Any], session_id: Optional[str] = None) -> str:
6182
6254
  """
@@ -6190,20 +6262,7 @@ class Agent:
6190
6262
  session_id = session_id or self.session_id
6191
6263
  if session_id is None:
6192
6264
  raise Exception("Session ID is not set")
6193
- session = self.get_session(session_id=session_id) # type: ignore
6194
- if session is None:
6195
- raise Exception("Session not found")
6196
-
6197
- if session.session_data is not None and "session_state" not in session.session_data:
6198
- session.session_data["session_state"] = {}
6199
-
6200
- # Overwrite the loaded DB session state with the new session state
6201
- for key, value in session_state_updates.items():
6202
- session.session_data["session_state"][key] = value # type: ignore
6203
-
6204
- self.save_session(session=session)
6205
-
6206
- return session.session_data["session_state"] # type: ignore
6265
+ return update_session_state_util(self, session_state_updates=session_state_updates, session_id=session_id)
6207
6266
 
6208
6267
  async def aupdate_session_state(
6209
6268
  self, session_state_updates: Dict[str, Any], session_id: Optional[str] = None
@@ -6219,52 +6278,88 @@ class Agent:
6219
6278
  session_id = session_id or self.session_id
6220
6279
  if session_id is None:
6221
6280
  raise Exception("Session ID is not set")
6222
- session = await self.aget_session(session_id=session_id) # type: ignore
6223
- if session is None:
6224
- raise Exception("Session not found")
6281
+ return await aupdate_session_state_util(
6282
+ self, session_state_updates=session_state_updates, session_id=session_id
6283
+ )
6225
6284
 
6226
- if session.session_data is not None and "session_state" not in session.session_data:
6227
- session.session_data["session_state"] = {}
6285
+ def get_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
6286
+ """Get the session metrics for the given session ID.
6228
6287
 
6229
- for key, value in session_state_updates.items():
6230
- session.session_data["session_state"][key] = value # type: ignore
6288
+ Args:
6289
+ session_id: The session ID to get the metrics for. If not provided, the current cached session ID is used.
6290
+ Returns:
6291
+ Optional[Metrics]: The session metrics.
6292
+ """
6293
+ session_id = session_id or self.session_id
6294
+ if session_id is None:
6295
+ raise Exception("Session ID is not set")
6231
6296
 
6232
- await self.asave_session(session=session)
6297
+ return get_session_metrics_util(self, session_id=session_id)
6233
6298
 
6234
- return session.session_data["session_state"] # type: ignore
6299
+ async def aget_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
6300
+ """Get the session metrics for the given session ID.
6235
6301
 
6236
- def get_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
6237
- """Get the session metrics for the given session ID and user ID."""
6302
+ Args:
6303
+ session_id: The session ID to get the metrics for. If not provided, the current cached session ID is used.
6304
+ Returns:
6305
+ Optional[Metrics]: The session metrics.
6306
+ """
6238
6307
  session_id = session_id or self.session_id
6239
6308
  if session_id is None:
6240
6309
  raise Exception("Session ID is not set")
6241
6310
 
6242
- session = self.get_session(session_id=session_id) # type: ignore
6243
- if session is None:
6244
- raise Exception("Session not found")
6245
-
6246
- if session.session_data is not None and session.session_data.get("session_metrics") is not None:
6247
- if isinstance(session.session_data.get("session_metrics"), dict):
6248
- return Metrics(**session.session_data.get("session_metrics", {}))
6249
- elif isinstance(session.session_data.get("session_metrics"), Metrics):
6250
- return session.session_data.get("session_metrics", None)
6251
- return None
6311
+ return await aget_session_metrics_util(self, session_id=session_id)
6252
6312
 
6253
6313
  def delete_session(self, session_id: str):
6254
6314
  """Delete the current session and save to storage"""
6255
6315
  if self.db is None:
6256
6316
  return
6257
- # -*- Delete session
6317
+
6258
6318
  self.db.delete_session(session_id=session_id)
6259
6319
 
6320
+ async def adelete_session(self, session_id: str):
6321
+ """Delete the current session and save to storage"""
6322
+ if self.db is None:
6323
+ return
6324
+ await self.db.delete_session(session_id=session_id) # type: ignore
6325
+
6260
6326
  def get_messages_for_session(self, session_id: Optional[str] = None) -> List[Message]:
6261
- """Get messages for a session"""
6327
+ """Get messages for a session
6328
+
6329
+ Args:
6330
+ session_id: The session ID to get the messages for. If not provided, the current cached session ID is used.
6331
+ Returns:
6332
+ List[Message]: The messages for the session.
6333
+ """
6334
+ session_id = session_id or self.session_id
6335
+ if session_id is None:
6336
+ log_warning("Session ID is not set, cannot get messages for session")
6337
+ return []
6338
+
6339
+ session = self.get_session(session_id=session_id)
6340
+
6341
+ if session is None:
6342
+ raise Exception("Session not found")
6343
+
6344
+ # Only filter by agent_id if this is part of a team
6345
+ return session.get_messages_from_last_n_runs(
6346
+ agent_id=self.id if self.team_id is not None else None,
6347
+ )
6348
+
6349
+ async def aget_messages_for_session(self, session_id: Optional[str] = None) -> List[Message]:
6350
+ """Get messages for a session
6351
+
6352
+ Args:
6353
+ session_id: The session ID to get the messages for. If not provided, the current cached session ID is used.
6354
+ Returns:
6355
+ List[Message]: The messages for the session.
6356
+ """
6262
6357
  session_id = session_id or self.session_id
6263
6358
  if session_id is None:
6264
6359
  log_warning("Session ID is not set, cannot get messages for session")
6265
6360
  return []
6266
6361
 
6267
- session = self.get_session(session_id=session_id) # type: ignore
6362
+ session = await self.aget_session(session_id=session_id)
6268
6363
 
6269
6364
  if session is None:
6270
6365
  raise Exception("Session not found")
@@ -6274,8 +6369,14 @@ class Agent:
6274
6369
  agent_id=self.id if self.team_id is not None else None,
6275
6370
  )
6276
6371
 
6277
- def get_session_summary(self, session_id: Optional[str] = None):
6278
- """Get the session summary for the given session ID and user ID."""
6372
+ def get_session_summary(self, session_id: Optional[str] = None) -> Optional[SessionSummary]:
6373
+ """Get the session summary for the given session ID and user ID
6374
+
6375
+ Args:
6376
+ session_id: The session ID to get the summary for. If not provided, the current cached session ID is used.
6377
+ Returns:
6378
+ SessionSummary: The session summary.
6379
+ """
6279
6380
  session_id = session_id if session_id is not None else self.session_id
6280
6381
  if session_id is None:
6281
6382
  raise ValueError("Session ID is required")
@@ -6287,8 +6388,33 @@ class Agent:
6287
6388
 
6288
6389
  return session.get_session_summary()
6289
6390
 
6391
+ async def aget_session_summary(self, session_id: Optional[str] = None) -> Optional[SessionSummary]:
6392
+ """Get the session summary for the given session ID and user ID.
6393
+
6394
+ Args:
6395
+ session_id: The session ID to get the summary for. If not provided, the current cached session ID is used.
6396
+ Returns:
6397
+ SessionSummary: The session summary.
6398
+ """
6399
+ session_id = session_id if session_id is not None else self.session_id
6400
+ if session_id is None:
6401
+ raise ValueError("Session ID is required")
6402
+
6403
+ session = await self.aget_session(session_id=session_id)
6404
+
6405
+ if session is None:
6406
+ raise Exception(f"Session {session_id} not found")
6407
+
6408
+ return session.get_session_summary()
6409
+
6290
6410
  def get_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
6291
- """Get the user memories for the given user ID."""
6411
+ """Get the user memories for the given user ID.
6412
+
6413
+ Args:
6414
+ user_id: The user ID to get the memories for. If not provided, the current cached user ID is used.
6415
+ Returns:
6416
+ Optional[List[UserMemory]]: The user memories.
6417
+ """
6292
6418
  if self.memory_manager is None:
6293
6419
  return None
6294
6420
  user_id = user_id if user_id is not None else self.user_id
@@ -6298,7 +6424,13 @@ class Agent:
6298
6424
  return self.memory_manager.get_user_memories(user_id=user_id)
6299
6425
 
6300
6426
  async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
6301
- """Get the user memories for the given user ID."""
6427
+ """Get the user memories for the given user ID.
6428
+
6429
+ Args:
6430
+ user_id: The user ID to get the memories for. If not provided, the current cached user ID is used.
6431
+ Returns:
6432
+ Optional[List[UserMemory]]: The user memories.
6433
+ """
6302
6434
  if self.memory_manager is None:
6303
6435
  return None
6304
6436
  user_id = user_id if user_id is not None else self.user_id
@@ -6308,19 +6440,28 @@ class Agent:
6308
6440
  return await self.memory_manager.aget_user_memories(user_id=user_id)
6309
6441
 
6310
6442
  def get_culture_knowledge(self) -> Optional[List[CulturalKnowledge]]:
6311
- """Get the cultural knowledge the agent has access to"""
6443
+ """Get the cultural knowledge the agent has access to
6444
+
6445
+ Returns:
6446
+ Optional[List[CulturalKnowledge]]: The cultural knowledge.
6447
+ """
6312
6448
  if self.culture_manager is None:
6313
6449
  return None
6314
6450
 
6315
6451
  return self.culture_manager.get_all_knowledge()
6316
6452
 
6317
6453
  async def aget_culture_knowledge(self) -> Optional[List[CulturalKnowledge]]:
6318
- """Get the cultural knowledge the agent has access to"""
6454
+ """Get the cultural knowledge the agent has access to
6455
+
6456
+ Returns:
6457
+ Optional[List[CulturalKnowledge]]: The cultural knowledge.
6458
+ """
6319
6459
  if self.culture_manager is None:
6320
6460
  return None
6321
6461
 
6322
6462
  return await self.culture_manager.aget_all_knowledge()
6323
6463
 
6464
+ # -*- System & User Message Functions
6324
6465
  def _format_message_with_state_variables(
6325
6466
  self,
6326
6467
  message: Any,
@@ -6365,6 +6506,7 @@ class Agent:
6365
6506
  session: AgentSession,
6366
6507
  session_state: Optional[Dict[str, Any]] = None,
6367
6508
  user_id: Optional[str] = None,
6509
+ tools: Optional[List[Union[Function, dict]]] = None,
6368
6510
  dependencies: Optional[Dict[str, Any]] = None,
6369
6511
  metadata: Optional[Dict[str, Any]] = None,
6370
6512
  add_session_state_to_context: Optional[bool] = None,
@@ -6436,7 +6578,7 @@ class Agent:
6436
6578
  instructions.extend(_instructions)
6437
6579
 
6438
6580
  # 3.1.1 Add instructions from the Model
6439
- _model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
6581
+ _model_instructions = self.model.get_instructions_for_model(tools)
6440
6582
  if _model_instructions is not None:
6441
6583
  instructions.extend(_model_instructions)
6442
6584
 
@@ -6671,7 +6813,7 @@ class Agent:
6671
6813
  )
6672
6814
 
6673
6815
  # 3.3.12 Add the system message from the Model
6674
- system_message_from_model = self.model.get_system_message_for_model(self._tools_for_model)
6816
+ system_message_from_model = self.model.get_system_message_for_model(tools)
6675
6817
  if system_message_from_model is not None:
6676
6818
  system_message_content += system_message_from_model
6677
6819
 
@@ -6707,6 +6849,7 @@ class Agent:
6707
6849
  session: AgentSession,
6708
6850
  session_state: Optional[Dict[str, Any]] = None,
6709
6851
  user_id: Optional[str] = None,
6852
+ tools: Optional[List[Union[Function, dict]]] = None,
6710
6853
  dependencies: Optional[Dict[str, Any]] = None,
6711
6854
  metadata: Optional[Dict[str, Any]] = None,
6712
6855
  ) -> Optional[Message]:
@@ -6777,7 +6920,7 @@ class Agent:
6777
6920
  instructions.extend(_instructions)
6778
6921
 
6779
6922
  # 3.1.1 Add instructions from the Model
6780
- _model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
6923
+ _model_instructions = self.model.get_instructions_for_model(tools)
6781
6924
  if _model_instructions is not None:
6782
6925
  instructions.extend(_model_instructions)
6783
6926
 
@@ -7015,7 +7158,7 @@ class Agent:
7015
7158
  )
7016
7159
 
7017
7160
  # 3.3.12 Add the system message from the Model
7018
- system_message_from_model = self.model.get_system_message_for_model(self._tools_for_model)
7161
+ system_message_from_model = self.model.get_system_message_for_model(tools)
7019
7162
  if system_message_from_model is not None:
7020
7163
  system_message_content += system_message_from_model
7021
7164
 
@@ -7234,6 +7377,7 @@ class Agent:
7234
7377
  add_dependencies_to_context: Optional[bool] = None,
7235
7378
  add_session_state_to_context: Optional[bool] = None,
7236
7379
  metadata: Optional[Dict[str, Any]] = None,
7380
+ tools: Optional[List[Union[Function, dict]]] = None,
7237
7381
  **kwargs: Any,
7238
7382
  ) -> RunMessages:
7239
7383
  """This function returns a RunMessages object with the following attributes:
@@ -7268,6 +7412,7 @@ class Agent:
7268
7412
  session=session,
7269
7413
  session_state=session_state,
7270
7414
  user_id=user_id,
7415
+ tools=tools,
7271
7416
  dependencies=dependencies,
7272
7417
  metadata=metadata,
7273
7418
  add_session_state_to_context=add_session_state_to_context,
@@ -7439,6 +7584,7 @@ class Agent:
7439
7584
  add_dependencies_to_context: Optional[bool] = None,
7440
7585
  add_session_state_to_context: Optional[bool] = None,
7441
7586
  metadata: Optional[Dict[str, Any]] = None,
7587
+ tools: Optional[List[Union[Function, dict]]] = None,
7442
7588
  **kwargs: Any,
7443
7589
  ) -> RunMessages:
7444
7590
  """This function returns a RunMessages object with the following attributes:
@@ -7473,6 +7619,7 @@ class Agent:
7473
7619
  session=session,
7474
7620
  session_state=session_state,
7475
7621
  user_id=user_id,
7622
+ tools=tools,
7476
7623
  dependencies=dependencies,
7477
7624
  metadata=metadata,
7478
7625
  )
@@ -9340,6 +9487,8 @@ class Agent:
9340
9487
  document_name = query.replace(" ", "_").replace("?", "").replace("!", "").replace(".", "")
9341
9488
  document_content = json.dumps({"query": query, "result": result})
9342
9489
  log_info(f"Adding document to Knowledge: {document_name}: {document_content}")
9490
+ import asyncio
9491
+
9343
9492
  from agno.knowledge.reader.text_reader import TextReader
9344
9493
 
9345
9494
  asyncio.run(
@@ -9414,12 +9563,14 @@ class Agent:
9414
9563
 
9415
9564
  return get_previous_session_messages
9416
9565
 
9417
- async def _aget_previous_sessions_messages_function(self, num_history_sessions: Optional[int] = 2) -> Callable:
9566
+ async def _aget_previous_sessions_messages_function(
9567
+ self, num_history_sessions: Optional[int] = 2, user_id: Optional[str] = None
9568
+ ) -> Function:
9418
9569
  """Factory function to create a get_previous_session_messages function.
9419
9570
 
9420
9571
  Args:
9421
9572
  num_history_sessions: The last n sessions to be taken from db
9422
-
9573
+ user_id: The user ID to filter sessions by
9423
9574
  Returns:
9424
9575
  Callable: A function that retrieves messages from previous sessions
9425
9576
  """
@@ -9437,12 +9588,22 @@ class Agent:
9437
9588
  if self.db is None:
9438
9589
  return "Previous session messages not available"
9439
9590
 
9440
- if isinstance(self.db, AsyncBaseDb):
9441
- selected_sessions = await self.db.get_sessions(
9442
- session_type=SessionType.AGENT, limit=num_history_sessions
9591
+ if self._has_async_db():
9592
+ selected_sessions = await self.db.get_sessions( # type: ignore
9593
+ session_type=SessionType.AGENT,
9594
+ limit=num_history_sessions,
9595
+ user_id=user_id,
9596
+ sort_by="created_at",
9597
+ sort_order="desc",
9443
9598
  )
9444
9599
  else:
9445
- selected_sessions = self.db.get_sessions(session_type=SessionType.AGENT, limit=num_history_sessions)
9600
+ selected_sessions = self.db.get_sessions(
9601
+ session_type=SessionType.AGENT,
9602
+ limit=num_history_sessions,
9603
+ user_id=user_id,
9604
+ sort_by="created_at",
9605
+ sort_order="desc",
9606
+ )
9446
9607
 
9447
9608
  all_messages = []
9448
9609
  seen_message_pairs = set()
@@ -9475,7 +9636,7 @@ class Agent:
9475
9636
 
9476
9637
  return json.dumps([msg.to_dict() for msg in all_messages]) if all_messages else "No history found"
9477
9638
 
9478
- return aget_previous_session_messages
9639
+ return Function.from_callable(aget_previous_session_messages, name="get_previous_session_messages")
9479
9640
 
9480
9641
  ###########################################################################
9481
9642
  # Print Response