agno 2.2.0__py3-none-any.whl → 2.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. agno/agent/agent.py +751 -575
  2. agno/culture/manager.py +22 -24
  3. agno/db/async_postgres/__init__.py +1 -1
  4. agno/db/dynamo/dynamo.py +0 -2
  5. agno/db/firestore/firestore.py +0 -2
  6. agno/db/gcs_json/gcs_json_db.py +0 -4
  7. agno/db/gcs_json/utils.py +0 -24
  8. agno/db/in_memory/in_memory_db.py +0 -3
  9. agno/db/json/json_db.py +4 -10
  10. agno/db/json/utils.py +0 -24
  11. agno/db/mongo/mongo.py +0 -2
  12. agno/db/mysql/mysql.py +0 -3
  13. agno/db/postgres/__init__.py +1 -1
  14. agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
  15. agno/db/postgres/postgres.py +7 -10
  16. agno/db/postgres/utils.py +106 -2
  17. agno/db/redis/redis.py +0 -2
  18. agno/db/singlestore/singlestore.py +0 -3
  19. agno/db/sqlite/__init__.py +2 -1
  20. agno/db/sqlite/async_sqlite.py +2269 -0
  21. agno/db/sqlite/sqlite.py +0 -2
  22. agno/db/sqlite/utils.py +96 -0
  23. agno/db/surrealdb/surrealdb.py +0 -6
  24. agno/knowledge/knowledge.py +14 -3
  25. agno/knowledge/reader/pptx_reader.py +101 -0
  26. agno/knowledge/reader/reader_factory.py +30 -0
  27. agno/knowledge/reader/tavily_reader.py +194 -0
  28. agno/knowledge/types.py +1 -0
  29. agno/memory/manager.py +28 -25
  30. agno/models/anthropic/claude.py +63 -6
  31. agno/models/base.py +255 -36
  32. agno/models/response.py +69 -0
  33. agno/os/router.py +7 -5
  34. agno/os/routers/memory/memory.py +2 -1
  35. agno/os/routers/memory/schemas.py +5 -2
  36. agno/os/schema.py +26 -20
  37. agno/os/utils.py +9 -2
  38. agno/run/agent.py +28 -30
  39. agno/run/base.py +17 -1
  40. agno/run/team.py +28 -29
  41. agno/run/workflow.py +32 -17
  42. agno/session/agent.py +3 -0
  43. agno/session/summary.py +4 -1
  44. agno/session/team.py +1 -1
  45. agno/team/team.py +620 -374
  46. agno/tools/dalle.py +2 -4
  47. agno/tools/eleven_labs.py +23 -25
  48. agno/tools/function.py +40 -0
  49. agno/tools/mcp/__init__.py +10 -0
  50. agno/tools/mcp/mcp.py +324 -0
  51. agno/tools/mcp/multi_mcp.py +347 -0
  52. agno/tools/mcp/params.py +24 -0
  53. agno/tools/slack.py +18 -3
  54. agno/tools/tavily.py +146 -0
  55. agno/utils/agent.py +366 -1
  56. agno/utils/mcp.py +92 -2
  57. agno/utils/media.py +166 -1
  58. agno/utils/message.py +60 -0
  59. agno/utils/print_response/workflow.py +17 -1
  60. agno/utils/team.py +89 -1
  61. agno/workflow/step.py +0 -1
  62. agno/workflow/types.py +10 -15
  63. agno/workflow/workflow.py +86 -1
  64. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/METADATA +31 -25
  65. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/RECORD +68 -64
  66. agno/db/async_postgres/schemas.py +0 -139
  67. agno/db/async_postgres/utils.py +0 -347
  68. agno/tools/mcp.py +0 -679
  69. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/WHEEL +0 -0
  70. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/licenses/LICENSE +0 -0
  71. {agno-2.2.0.dist-info → agno-2.2.2.dist-info}/top_level.txt +0 -0
agno/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
  )
@@ -117,7 +135,7 @@ from agno.utils.log import (
117
135
  set_log_level_to_info,
118
136
  )
119
137
  from agno.utils.merge_dict import merge_dictionaries
120
- from agno.utils.message import get_text_from_message
138
+ from agno.utils.message import filter_tool_calls, get_text_from_message
121
139
  from agno.utils.print_response.agent import (
122
140
  aprint_response,
123
141
  aprint_response_stream,
@@ -203,6 +221,8 @@ class Agent:
203
221
  add_history_to_context: bool = False
204
222
  # Number of historical runs to include in the messages
205
223
  num_history_runs: int = 3
224
+ # Maximum number of tool calls to include from history (None = no limit)
225
+ max_tool_calls_from_history: Optional[int] = None
206
226
 
207
227
  # --- Knowledge ---
208
228
  knowledge: Optional[Knowledge] = None
@@ -419,6 +439,7 @@ class Agent:
419
439
  session_summary_manager: Optional[SessionSummaryManager] = None,
420
440
  add_history_to_context: bool = False,
421
441
  num_history_runs: int = 3,
442
+ max_tool_calls_from_history: Optional[int] = None,
422
443
  store_media: bool = True,
423
444
  store_tool_messages: bool = True,
424
445
  store_history_messages: bool = True,
@@ -520,6 +541,7 @@ class Agent:
520
541
 
521
542
  self.add_history_to_context = add_history_to_context
522
543
  self.num_history_runs = num_history_runs
544
+ self.max_tool_calls_from_history = max_tool_calls_from_history
523
545
 
524
546
  self.store_media = store_media
525
547
  self.store_tool_messages = store_tool_messages
@@ -611,17 +633,16 @@ class Agent:
611
633
  self.telemetry = telemetry
612
634
 
613
635
  # If we are caching the agent session
614
- self._agent_session: Optional[AgentSession] = None
636
+ self._cached_session: Optional[AgentSession] = None
615
637
 
616
638
  self._tool_instructions: Optional[List[str]] = None
617
- self._tools_for_model: Optional[List[Dict[str, Any]]] = None
618
- self._functions_for_model: Optional[Dict[str, Function]] = None
619
- self._rebuild_tools: bool = True
620
639
 
621
640
  self._formatter: Optional[SafeFormatter] = None
622
641
 
623
642
  self._hooks_normalised = False
624
643
 
644
+ self._mcp_tools_initialized_on_run: List[Any] = []
645
+
625
646
  # Lazy-initialized shared thread pool executor for background tasks (memory, cultural knowledge, etc.)
626
647
  self._background_executor: Optional[Any] = None
627
648
 
@@ -638,6 +659,14 @@ class Agent:
638
659
  self._background_executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="agno-bg")
639
660
  return self._background_executor
640
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
+
641
670
  def set_id(self) -> None:
642
671
  if self.id is None:
643
672
  self.id = generate_id_from_name(self.name)
@@ -794,19 +823,28 @@ class Agent:
794
823
  if self._formatter is None:
795
824
  self._formatter = SafeFormatter()
796
825
 
797
- @property
798
- def should_parse_structured_output(self) -> bool:
799
- return self.output_schema is not None and self.parse_response and self.parser_model is None
800
-
801
826
  def add_tool(self, tool: Union[Toolkit, Callable, Function, Dict]):
802
827
  if not self.tools:
803
828
  self.tools = []
804
829
  self.tools.append(tool)
805
- self._rebuild_tools = True
806
830
 
807
831
  def set_tools(self, tools: Sequence[Union[Toolkit, Callable, Function, Dict]]):
808
832
  self.tools = list(tools) if tools else []
809
- 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 = []
810
848
 
811
849
  def _initialize_session(
812
850
  self,
@@ -905,15 +943,19 @@ class Agent:
905
943
  deque(pre_hook_iterator, maxlen=0)
906
944
 
907
945
  # 2. Determine tools for model
908
- 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(
909
953
  model=self.model,
954
+ processed_tools=processed_tools,
910
955
  run_response=run_response,
911
956
  session=session,
912
957
  session_state=session_state,
913
958
  dependencies=dependencies,
914
- user_id=user_id,
915
- async_mode=False,
916
- knowledge_filters=knowledge_filters,
917
959
  )
918
960
 
919
961
  # 3. Prepare run messages
@@ -933,6 +975,7 @@ class Agent:
933
975
  add_dependencies_to_context=add_dependencies_to_context,
934
976
  add_session_state_to_context=add_session_state_to_context,
935
977
  metadata=metadata,
978
+ tools=_tools,
936
979
  **kwargs,
937
980
  )
938
981
  if len(run_messages.messages) == 0:
@@ -974,8 +1017,7 @@ class Agent:
974
1017
  self.model = cast(Model, self.model)
975
1018
  model_response: ModelResponse = self.model.response(
976
1019
  messages=run_messages.messages,
977
- tools=self._tools_for_model,
978
- functions=self._functions_for_model,
1020
+ tools=_tools,
979
1021
  tool_choice=self.tool_choice,
980
1022
  tool_call_limit=self.tool_call_limit,
981
1023
  response_format=response_format,
@@ -1124,15 +1166,19 @@ class Agent:
1124
1166
  yield event
1125
1167
 
1126
1168
  # 2. Determine tools for model
1127
- 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(
1128
1176
  model=self.model,
1177
+ processed_tools=processed_tools,
1129
1178
  run_response=run_response,
1130
1179
  session=session,
1131
1180
  session_state=session_state,
1132
1181
  dependencies=dependencies,
1133
- user_id=user_id,
1134
- async_mode=False,
1135
- knowledge_filters=knowledge_filters,
1136
1182
  )
1137
1183
 
1138
1184
  # 3. Prepare run messages
@@ -1152,6 +1198,7 @@ class Agent:
1152
1198
  add_dependencies_to_context=add_dependencies_to_context,
1153
1199
  add_session_state_to_context=add_session_state_to_context,
1154
1200
  metadata=metadata,
1201
+ tools=_tools,
1155
1202
  **kwargs,
1156
1203
  )
1157
1204
  if len(run_messages.messages) == 0:
@@ -1206,6 +1253,7 @@ class Agent:
1206
1253
  session=session,
1207
1254
  run_response=run_response,
1208
1255
  run_messages=run_messages,
1256
+ tools=_tools,
1209
1257
  response_format=response_format,
1210
1258
  stream_events=stream_events,
1211
1259
  ):
@@ -1221,6 +1269,7 @@ class Agent:
1221
1269
  session=session,
1222
1270
  run_response=run_response,
1223
1271
  run_messages=run_messages,
1272
+ tools=_tools,
1224
1273
  response_format=response_format,
1225
1274
  stream_events=stream_events,
1226
1275
  ):
@@ -1355,7 +1404,10 @@ class Agent:
1355
1404
  # Handle run cancellation during streaming
1356
1405
  log_info(f"Run {run_response.run_id} was cancelled during streaming")
1357
1406
  run_response.status = RunStatus.cancelled
1358
- 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)
1359
1411
 
1360
1412
  # Yield the cancellation event
1361
1413
  yield handle_event( # type: ignore
@@ -1741,15 +1793,19 @@ class Agent:
1741
1793
 
1742
1794
  # 5. Determine tools for model
1743
1795
  self.model = cast(Model, self.model)
1744
- 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(
1745
1803
  model=self.model,
1804
+ processed_tools=processed_tools,
1746
1805
  run_response=run_response,
1747
1806
  session=agent_session,
1748
1807
  session_state=session_state,
1749
1808
  dependencies=dependencies,
1750
- user_id=user_id,
1751
- async_mode=True,
1752
- knowledge_filters=knowledge_filters,
1753
1809
  )
1754
1810
 
1755
1811
  # 6. Prepare run messages
@@ -1769,6 +1825,7 @@ class Agent:
1769
1825
  add_dependencies_to_context=add_dependencies_to_context,
1770
1826
  add_session_state_to_context=add_session_state_to_context,
1771
1827
  metadata=metadata,
1828
+ tools=_tools,
1772
1829
  **kwargs,
1773
1830
  )
1774
1831
  if len(run_messages.messages) == 0:
@@ -1777,10 +1834,8 @@ class Agent:
1777
1834
  # 7. Start memory creation as a background task (runs concurrently with the main execution)
1778
1835
  memory_task = None
1779
1836
  if run_messages.user_message is not None and self.memory_manager is not None and not self.enable_agentic_memory:
1780
- import asyncio
1781
-
1782
1837
  log_debug("Starting memory creation in background task.")
1783
- 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))
1784
1839
 
1785
1840
  # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
1786
1841
  cultural_knowledge_task = None
@@ -1789,10 +1844,8 @@ class Agent:
1789
1844
  and self.culture_manager is not None
1790
1845
  and self.update_cultural_knowledge
1791
1846
  ):
1792
- import asyncio
1793
-
1794
1847
  log_debug("Starting cultural knowledge creation in background thread.")
1795
- 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))
1796
1849
 
1797
1850
  try:
1798
1851
  # Check for cancellation before model call
@@ -1807,8 +1860,7 @@ class Agent:
1807
1860
  # 9. Generate a response from the Model (includes running function calls)
1808
1861
  model_response: ModelResponse = await self.model.aresponse(
1809
1862
  messages=run_messages.messages,
1810
- tools=self._tools_for_model,
1811
- functions=self._functions_for_model,
1863
+ tools=_tools,
1812
1864
  tool_choice=self.tool_choice,
1813
1865
  tool_call_limit=self.tool_call_limit,
1814
1866
  response_format=response_format,
@@ -1901,23 +1953,22 @@ class Agent:
1901
1953
  return run_response
1902
1954
 
1903
1955
  finally:
1956
+ # Always disconnect MCP tools
1957
+ await self._disconnect_mcp_tools()
1958
+
1904
1959
  # Cancel the memory task if it's still running
1905
1960
  if memory_task is not None and not memory_task.done():
1906
- import asyncio
1907
-
1908
1961
  memory_task.cancel()
1909
1962
  try:
1910
1963
  await memory_task
1911
- except asyncio.CancelledError:
1964
+ except CancelledError:
1912
1965
  pass
1913
1966
  # Cancel the cultural knowledge task if it's still running
1914
1967
  if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
1915
- import asyncio
1916
-
1917
1968
  cultural_knowledge_task.cancel()
1918
1969
  try:
1919
1970
  await cultural_knowledge_task
1920
- except asyncio.CancelledError:
1971
+ except CancelledError:
1921
1972
  pass
1922
1973
  # Always clean up the run tracking
1923
1974
  cleanup_run(run_response.run_id) # type: ignore
@@ -1995,6 +2046,9 @@ class Agent:
1995
2046
  run_response=run_response,
1996
2047
  run_input=run_input,
1997
2048
  session=agent_session,
2049
+ session_state=session_state,
2050
+ dependencies=dependencies,
2051
+ metadata=metadata,
1998
2052
  user_id=user_id,
1999
2053
  debug_mode=debug_mode,
2000
2054
  **kwargs,
@@ -2004,14 +2058,18 @@ class Agent:
2004
2058
 
2005
2059
  # 5. Determine tools for model
2006
2060
  self.model = cast(Model, self.model)
2007
- self._determine_tools_for_model(
2008
- model=self.model,
2061
+ processed_tools = await self.aget_tools(
2009
2062
  run_response=run_response,
2010
2063
  session=agent_session,
2011
- session_state=session_state,
2012
2064
  user_id=user_id,
2013
- async_mode=True,
2014
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,
2015
2073
  dependencies=dependencies,
2016
2074
  )
2017
2075
 
@@ -2032,6 +2090,7 @@ class Agent:
2032
2090
  add_dependencies_to_context=add_dependencies_to_context,
2033
2091
  add_session_state_to_context=add_session_state_to_context,
2034
2092
  metadata=metadata,
2093
+ tools=_tools,
2035
2094
  **kwargs,
2036
2095
  )
2037
2096
  if len(run_messages.messages) == 0:
@@ -2040,10 +2099,8 @@ class Agent:
2040
2099
  # 7. Start memory creation as a background task (runs concurrently with the main execution)
2041
2100
  memory_task = None
2042
2101
  if run_messages.user_message is not None and self.memory_manager is not None and not self.enable_agentic_memory:
2043
- import asyncio
2044
-
2045
2102
  log_debug("Starting memory creation in background task.")
2046
- 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))
2047
2104
 
2048
2105
  # Start cultural knowledge creation on a separate thread (runs concurrently with the main execution loop)
2049
2106
  cultural_knowledge_task = None
@@ -2052,10 +2109,8 @@ class Agent:
2052
2109
  and self.culture_manager is not None
2053
2110
  and self.update_cultural_knowledge
2054
2111
  ):
2055
- import asyncio
2056
-
2057
2112
  log_debug("Starting cultural knowledge creation in background task.")
2058
- 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))
2059
2114
 
2060
2115
  # Register run for cancellation tracking
2061
2116
  register_run(run_response.run_id) # type: ignore
@@ -2078,6 +2133,7 @@ class Agent:
2078
2133
  session=agent_session,
2079
2134
  run_response=run_response,
2080
2135
  run_messages=run_messages,
2136
+ tools=_tools,
2081
2137
  response_format=response_format,
2082
2138
  stream_events=stream_events,
2083
2139
  ):
@@ -2093,6 +2149,7 @@ class Agent:
2093
2149
  session=agent_session,
2094
2150
  run_response=run_response,
2095
2151
  run_messages=run_messages,
2152
+ tools=_tools,
2096
2153
  response_format=response_format,
2097
2154
  stream_events=stream_events,
2098
2155
  ):
@@ -2230,7 +2287,10 @@ class Agent:
2230
2287
  # Handle run cancellation during async streaming
2231
2288
  log_info(f"Run {run_response.run_id} was cancelled during async streaming")
2232
2289
  run_response.status = RunStatus.cancelled
2233
- 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)
2234
2294
 
2235
2295
  # Yield the cancellation event
2236
2296
  yield handle_event( # type: ignore
@@ -2243,19 +2303,22 @@ class Agent:
2243
2303
  # Cleanup and store the run response and session
2244
2304
  await self._acleanup_and_store(run_response=run_response, session=agent_session, user_id=user_id)
2245
2305
  finally:
2306
+ # Always disconnect MCP tools
2307
+ await self._disconnect_mcp_tools()
2308
+
2246
2309
  # Cancel the memory task if it's still running
2247
2310
  if memory_task is not None and not memory_task.done():
2248
2311
  memory_task.cancel()
2249
2312
  try:
2250
2313
  await memory_task
2251
- except asyncio.CancelledError:
2314
+ except CancelledError:
2252
2315
  pass
2253
2316
 
2254
2317
  if cultural_knowledge_task is not None and not cultural_knowledge_task.done():
2255
2318
  cultural_knowledge_task.cancel()
2256
2319
  try:
2257
2320
  await cultural_knowledge_task
2258
- except asyncio.CancelledError:
2321
+ except CancelledError:
2259
2322
  pass
2260
2323
 
2261
2324
  # Always clean up the run tracking
@@ -2697,15 +2760,19 @@ class Agent:
2697
2760
  response_format = self._get_response_format()
2698
2761
  self.model = cast(Model, self.model)
2699
2762
 
2700
- 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(
2701
2770
  model=self.model,
2771
+ processed_tools=processed_tools,
2702
2772
  run_response=run_response,
2703
2773
  session=agent_session,
2704
2774
  session_state=session_state,
2705
2775
  dependencies=run_dependencies,
2706
- user_id=user_id,
2707
- async_mode=False,
2708
- knowledge_filters=effective_filters,
2709
2776
  )
2710
2777
 
2711
2778
  last_exception = None
@@ -2728,6 +2795,7 @@ class Agent:
2728
2795
  response_iterator = self._continue_run_stream(
2729
2796
  run_response=run_response,
2730
2797
  run_messages=run_messages,
2798
+ tools=_tools,
2731
2799
  user_id=user_id,
2732
2800
  session=agent_session,
2733
2801
  session_state=session_state,
@@ -2743,6 +2811,7 @@ class Agent:
2743
2811
  response = self._continue_run(
2744
2812
  run_response=run_response,
2745
2813
  run_messages=run_messages,
2814
+ tools=_tools,
2746
2815
  user_id=user_id,
2747
2816
  session=agent_session,
2748
2817
  session_state=session_state,
@@ -2795,6 +2864,7 @@ class Agent:
2795
2864
  run_response: RunOutput,
2796
2865
  run_messages: RunMessages,
2797
2866
  session: AgentSession,
2867
+ tools: List[Union[Function, dict]],
2798
2868
  session_state: Optional[Dict[str, Any]] = None,
2799
2869
  dependencies: Optional[Dict[str, Any]] = None,
2800
2870
  metadata: Optional[Dict[str, Any]] = None,
@@ -2821,7 +2891,7 @@ class Agent:
2821
2891
  self.model = cast(Model, self.model)
2822
2892
 
2823
2893
  # 1. Handle the updated tools
2824
- 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)
2825
2895
 
2826
2896
  try:
2827
2897
  # Check for cancellation before model call
@@ -2832,8 +2902,7 @@ class Agent:
2832
2902
  model_response: ModelResponse = self.model.response(
2833
2903
  messages=run_messages.messages,
2834
2904
  response_format=response_format,
2835
- tools=self._tools_for_model,
2836
- functions=self._functions_for_model,
2905
+ tools=tools,
2837
2906
  tool_choice=self.tool_choice,
2838
2907
  tool_call_limit=self.tool_call_limit,
2839
2908
  )
@@ -2913,6 +2982,7 @@ class Agent:
2913
2982
  run_response: RunOutput,
2914
2983
  run_messages: RunMessages,
2915
2984
  session: AgentSession,
2985
+ tools: List[Union[Function, dict]],
2916
2986
  session_state: Optional[Dict[str, Any]] = None,
2917
2987
  metadata: Optional[Dict[str, Any]] = None,
2918
2988
  user_id: Optional[str] = None,
@@ -2948,7 +3018,7 @@ class Agent:
2948
3018
 
2949
3019
  # 2. Handle the updated tools
2950
3020
  yield from self._handle_tool_call_updates_stream(
2951
- 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
2952
3022
  )
2953
3023
 
2954
3024
  try:
@@ -2957,6 +3027,7 @@ class Agent:
2957
3027
  session=session,
2958
3028
  run_response=run_response,
2959
3029
  run_messages=run_messages,
3030
+ tools=tools,
2960
3031
  response_format=response_format,
2961
3032
  stream_events=stream_events,
2962
3033
  ):
@@ -3345,15 +3416,19 @@ class Agent:
3345
3416
 
3346
3417
  # 5. Determine tools for model
3347
3418
  self.model = cast(Model, self.model)
3348
- 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(
3349
3426
  model=self.model,
3427
+ processed_tools=processed_tools,
3350
3428
  run_response=run_response,
3351
3429
  session=agent_session,
3352
3430
  session_state=session_state,
3353
3431
  dependencies=dependencies,
3354
- user_id=user_id,
3355
- async_mode=True,
3356
- knowledge_filters=knowledge_filters,
3357
3432
  )
3358
3433
 
3359
3434
  # 6. Prepare run messages
@@ -3366,14 +3441,13 @@ class Agent:
3366
3441
 
3367
3442
  try:
3368
3443
  # 7. Handle the updated tools
3369
- 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)
3370
3445
 
3371
3446
  # 8. Get model response
3372
3447
  model_response: ModelResponse = await self.model.aresponse(
3373
3448
  messages=run_messages.messages,
3374
3449
  response_format=response_format,
3375
- tools=self._tools_for_model,
3376
- functions=self._functions_for_model,
3450
+ tools=_tools,
3377
3451
  tool_choice=self.tool_choice,
3378
3452
  tool_call_limit=self.tool_call_limit,
3379
3453
  )
@@ -3460,6 +3534,9 @@ class Agent:
3460
3534
 
3461
3535
  return run_response
3462
3536
  finally:
3537
+ # Always disconnect MCP tools
3538
+ await self._disconnect_mcp_tools()
3539
+
3463
3540
  # Always clean up the run tracking
3464
3541
  cleanup_run(run_response.run_id) # type: ignore
3465
3542
 
@@ -3501,7 +3578,7 @@ class Agent:
3501
3578
  await self._aresolve_run_dependencies(dependencies=dependencies)
3502
3579
 
3503
3580
  # 2. Read existing session from db
3504
- 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)
3505
3582
 
3506
3583
  # 3. Update session state and metadata
3507
3584
  self._update_metadata(session=agent_session)
@@ -3536,15 +3613,19 @@ class Agent:
3536
3613
 
3537
3614
  # 5. Determine tools for model
3538
3615
  self.model = cast(Model, self.model)
3539
- 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(
3540
3623
  model=self.model,
3624
+ processed_tools=processed_tools,
3541
3625
  run_response=run_response,
3542
3626
  session=agent_session,
3543
3627
  session_state=session_state,
3544
3628
  dependencies=dependencies,
3545
- user_id=user_id,
3546
- async_mode=True,
3547
- knowledge_filters=knowledge_filters,
3548
3629
  )
3549
3630
 
3550
3631
  # 6. Prepare run messages
@@ -3567,7 +3648,7 @@ class Agent:
3567
3648
 
3568
3649
  # 7. Handle the updated tools
3569
3650
  async for event in self._ahandle_tool_call_updates_stream(
3570
- run_response=run_response, run_messages=run_messages
3651
+ run_response=run_response, run_messages=run_messages, tools=_tools, stream_events=stream_events
3571
3652
  ):
3572
3653
  raise_if_cancelled(run_response.run_id) # type: ignore
3573
3654
  yield event
@@ -3578,6 +3659,7 @@ class Agent:
3578
3659
  session=agent_session,
3579
3660
  run_response=run_response,
3580
3661
  run_messages=run_messages,
3662
+ tools=_tools,
3581
3663
  response_format=response_format,
3582
3664
  stream_events=stream_events,
3583
3665
  ):
@@ -3593,6 +3675,7 @@ class Agent:
3593
3675
  session=agent_session,
3594
3676
  run_response=run_response,
3595
3677
  run_messages=run_messages,
3678
+ tools=_tools,
3596
3679
  response_format=response_format,
3597
3680
  stream_events=stream_events,
3598
3681
  ):
@@ -3726,6 +3809,9 @@ class Agent:
3726
3809
  # Cleanup and store the run response and session
3727
3810
  await self._acleanup_and_store(run_response=run_response, session=agent_session, user_id=user_id)
3728
3811
  finally:
3812
+ # Always disconnect MCP tools
3813
+ await self._disconnect_mcp_tools()
3814
+
3729
3815
  # Always clean up the run tracking
3730
3816
  cleanup_run(run_response.run_id) # type: ignore
3731
3817
 
@@ -3844,7 +3930,7 @@ class Agent:
3844
3930
  # Filter arguments to only include those that the hook accepts
3845
3931
  filtered_args = filter_hook_args(hook, all_args)
3846
3932
 
3847
- if asyncio.iscoroutinefunction(hook):
3933
+ if iscoroutinefunction(hook):
3848
3934
  await hook(**filtered_args)
3849
3935
  else:
3850
3936
  # Synchronous function
@@ -3978,8 +4064,9 @@ class Agent:
3978
4064
  try:
3979
4065
  # Filter arguments to only include those that the hook accepts
3980
4066
  filtered_args = filter_hook_args(hook, all_args)
4067
+ from inspect import iscoroutinefunction
3981
4068
 
3982
- if asyncio.iscoroutinefunction(hook):
4069
+ if iscoroutinefunction(hook):
3983
4070
  await hook(**filtered_args)
3984
4071
  else:
3985
4072
  hook(**filtered_args)
@@ -4176,11 +4263,12 @@ class Agent:
4176
4263
  run_response: RunOutput,
4177
4264
  run_messages: RunMessages,
4178
4265
  tool: ToolExecution,
4266
+ functions: Optional[Dict[str, Function]] = None,
4179
4267
  stream_events: bool = False,
4180
4268
  ) -> Iterator[RunOutputEvent]:
4181
4269
  self.model = cast(Model, self.model)
4182
4270
  # Execute the tool
4183
- 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)
4184
4272
  function_call_results: List[Message] = []
4185
4273
 
4186
4274
  for call_result in self.model.run_function_call(
@@ -4214,9 +4302,11 @@ class Agent:
4214
4302
  if len(function_call_results) > 0:
4215
4303
  run_messages.messages.extend(function_call_results)
4216
4304
 
4217
- 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
+ ):
4218
4308
  self.model = cast(Model, self.model)
4219
- 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)
4220
4310
  function_call.error = tool.confirmation_note or "Function call was rejected by the user"
4221
4311
  function_call_result = self.model.create_function_call_result(
4222
4312
  function_call=function_call,
@@ -4229,12 +4319,13 @@ class Agent:
4229
4319
  run_response: RunOutput,
4230
4320
  run_messages: RunMessages,
4231
4321
  tool: ToolExecution,
4322
+ functions: Optional[Dict[str, Function]] = None,
4232
4323
  stream_events: bool = False,
4233
4324
  ) -> AsyncIterator[RunOutputEvent]:
4234
4325
  self.model = cast(Model, self.model)
4235
4326
 
4236
4327
  # Execute the tool
4237
- 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)
4238
4329
  function_call_results: List[Message] = []
4239
4330
 
4240
4331
  async for call_result in self.model.arun_function_calls(
@@ -4267,17 +4358,21 @@ class Agent:
4267
4358
  if len(function_call_results) > 0:
4268
4359
  run_messages.messages.extend(function_call_results)
4269
4360
 
4270
- 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
+ ):
4271
4364
  self.model = cast(Model, self.model)
4365
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4366
+
4272
4367
  for _t in run_response.tools or []:
4273
4368
  # Case 1: Handle confirmed tools and execute them
4274
- 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:
4275
4370
  # Tool is confirmed and hasn't been run before
4276
4371
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4277
4372
  # Consume the generator without yielding
4278
- 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)
4279
4374
  else:
4280
- self._reject_tool_call(run_messages, _t)
4375
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4281
4376
  _t.confirmed = False
4282
4377
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4283
4378
  _t.tool_call_error = True
@@ -4302,20 +4397,28 @@ class Agent:
4302
4397
  _t.requires_user_input = False
4303
4398
  _t.answered = True
4304
4399
  # Consume the generator without yielding
4305
- 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)
4306
4401
 
4307
4402
  def _handle_tool_call_updates_stream(
4308
- 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,
4309
4408
  ) -> Iterator[RunOutputEvent]:
4310
4409
  self.model = cast(Model, self.model)
4410
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4411
+
4311
4412
  for _t in run_response.tools or []:
4312
4413
  # Case 1: Handle confirmed tools and execute them
4313
- 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:
4314
4415
  # Tool is confirmed and hasn't been run before
4315
4416
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4316
- 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
+ )
4317
4420
  else:
4318
- self._reject_tool_call(run_messages, _t)
4421
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4319
4422
  _t.confirmed = False
4320
4423
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4321
4424
  _t.tool_call_error = True
@@ -4338,21 +4441,27 @@ class Agent:
4338
4441
  # Case 4: Handle user input required tools
4339
4442
  elif _t.requires_user_input is not None and _t.requires_user_input is True:
4340
4443
  self._handle_user_input_update(tool=_t)
4341
- 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
+ )
4342
4447
  _t.requires_user_input = False
4343
4448
  _t.answered = True
4344
4449
 
4345
- 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
+ ):
4346
4453
  self.model = cast(Model, self.model)
4454
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4455
+
4347
4456
  for _t in run_response.tools or []:
4348
4457
  # Case 1: Handle confirmed tools and execute them
4349
- 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:
4350
4459
  # Tool is confirmed and hasn't been run before
4351
4460
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4352
- 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):
4353
4462
  pass
4354
4463
  else:
4355
- self._reject_tool_call(run_messages, _t)
4464
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4356
4465
  _t.confirmed = False
4357
4466
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4358
4467
  _t.tool_call_error = True
@@ -4373,24 +4482,32 @@ class Agent:
4373
4482
  # Case 4: Handle user input required tools
4374
4483
  elif _t.requires_user_input is not None and _t.requires_user_input is True:
4375
4484
  self._handle_user_input_update(tool=_t)
4376
- 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):
4377
4486
  pass
4378
4487
  _t.requires_user_input = False
4379
4488
  _t.answered = True
4380
4489
 
4381
4490
  async def _ahandle_tool_call_updates_stream(
4382
- 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,
4383
4496
  ) -> AsyncIterator[RunOutputEvent]:
4384
4497
  self.model = cast(Model, self.model)
4498
+ _functions = {tool.name: tool for tool in tools if isinstance(tool, Function)}
4499
+
4385
4500
  for _t in run_response.tools or []:
4386
4501
  # Case 1: Handle confirmed tools and execute them
4387
- 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:
4388
4503
  # Tool is confirmed and hasn't been run before
4389
4504
  if _t.confirmed is not None and _t.confirmed is True and _t.result is None:
4390
- 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
+ ):
4391
4508
  yield event
4392
4509
  else:
4393
- self._reject_tool_call(run_messages, _t)
4510
+ self._reject_tool_call(run_messages, _t, functions=_functions)
4394
4511
  _t.confirmed = False
4395
4512
  _t.confirmation_note = _t.confirmation_note or "Tool call was rejected"
4396
4513
  _t.tool_call_error = True
@@ -4411,7 +4528,9 @@ class Agent:
4411
4528
  # # Case 4: Handle user input required tools
4412
4529
  elif _t.requires_user_input is not None and _t.requires_user_input is True:
4413
4530
  self._handle_user_input_update(tool=_t)
4414
- 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
+ ):
4415
4534
  yield event
4416
4535
  _t.requires_user_input = False
4417
4536
  _t.answered = True
@@ -4517,6 +4636,7 @@ class Agent:
4517
4636
  session: AgentSession,
4518
4637
  run_response: RunOutput,
4519
4638
  run_messages: RunMessages,
4639
+ tools: Optional[List[Union[Function, dict]]] = None,
4520
4640
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
4521
4641
  stream_events: bool = False,
4522
4642
  ) -> Iterator[RunOutputEvent]:
@@ -4536,8 +4656,7 @@ class Agent:
4536
4656
  for model_response_event in self.model.response_stream(
4537
4657
  messages=run_messages.messages,
4538
4658
  response_format=response_format,
4539
- tools=self._tools_for_model,
4540
- functions=self._functions_for_model,
4659
+ tools=tools,
4541
4660
  tool_choice=self.tool_choice,
4542
4661
  tool_call_limit=self.tool_call_limit,
4543
4662
  stream_model_response=stream_model_response,
@@ -4595,6 +4714,7 @@ class Agent:
4595
4714
  session: AgentSession,
4596
4715
  run_response: RunOutput,
4597
4716
  run_messages: RunMessages,
4717
+ tools: Optional[List[Union[Function, dict]]] = None,
4598
4718
  response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
4599
4719
  stream_events: bool = False,
4600
4720
  ) -> AsyncIterator[RunOutputEvent]:
@@ -4614,8 +4734,7 @@ class Agent:
4614
4734
  model_response_stream = self.model.aresponse_stream(
4615
4735
  messages=run_messages.messages,
4616
4736
  response_format=response_format,
4617
- tools=self._tools_for_model,
4618
- functions=self._functions_for_model,
4737
+ tools=tools,
4619
4738
  tool_choice=self.tool_choice,
4620
4739
  tool_call_limit=self.tool_call_limit,
4621
4740
  stream_model_response=stream_model_response,
@@ -5121,69 +5240,34 @@ class Agent:
5121
5240
  self,
5122
5241
  run_response: RunOutput,
5123
5242
  session: AgentSession,
5124
- async_mode: bool = False,
5125
5243
  user_id: Optional[str] = None,
5126
5244
  knowledge_filters: Optional[Dict[str, Any]] = None,
5127
- ) -> Optional[List[Union[Toolkit, Callable, Function, Dict]]]:
5245
+ ) -> List[Union[Toolkit, Callable, Function, Dict]]:
5128
5246
  agent_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
5129
5247
 
5130
5248
  # Add provided tools
5131
5249
  if self.tools is not None:
5132
5250
  # If not running in async mode, raise if any tool is async
5133
- if not async_mode:
5134
- self._raise_if_async_tools()
5251
+ self._raise_if_async_tools()
5135
5252
  agent_tools.extend(self.tools)
5136
5253
 
5137
- # If any of the tools has "agent" as parameter, set _rebuild_tools to True
5138
- for tool in agent_tools:
5139
- param_names = {
5140
- "agent",
5141
- "session_state",
5142
- "team",
5143
- "images",
5144
- "videos",
5145
- "audios",
5146
- "files",
5147
- }
5148
-
5149
- if isinstance(tool, Function):
5150
- if param_names & set(tool.parameters):
5151
- self._rebuild_tools = True
5152
- break
5153
- elif isinstance(tool, Toolkit):
5154
- for func in tool.functions.values():
5155
- if param_names & set(func.parameters):
5156
- self._rebuild_tools = True
5157
- break
5158
- elif callable(tool):
5159
- from inspect import signature
5160
-
5161
- if param_names & set(signature(tool).parameters):
5162
- self._rebuild_tools = True
5163
- break
5164
-
5165
5254
  # Add tools for accessing memory
5166
5255
  if self.read_chat_history:
5167
5256
  agent_tools.append(self._get_chat_history_function(session=session))
5168
- self._rebuild_tools = True
5169
5257
  if self.read_tool_call_history:
5170
5258
  agent_tools.append(self._get_tool_call_history_function(session=session))
5171
- self._rebuild_tools = True
5172
5259
  if self.search_session_history:
5173
5260
  agent_tools.append(
5174
5261
  self._get_previous_sessions_messages_function(
5175
5262
  num_history_sessions=self.num_history_sessions, user_id=user_id
5176
5263
  )
5177
5264
  )
5178
- self._rebuild_tools = True
5179
5265
 
5180
5266
  if self.enable_agentic_memory:
5181
- agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=async_mode))
5182
- self._rebuild_tools = True
5267
+ agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=False))
5183
5268
 
5184
5269
  if self.enable_agentic_culture:
5185
- agent_tools.append(self._get_update_cultural_knowledge_function(async_mode=async_mode))
5186
- self._rebuild_tools = True
5270
+ agent_tools.append(self._get_update_cultural_knowledge_function(async_mode=False))
5187
5271
 
5188
5272
  if self.enable_agentic_state:
5189
5273
  agent_tools.append(Function(name="update_session_state", entrypoint=self._update_session_state_tool))
@@ -5193,7 +5277,7 @@ class Agent:
5193
5277
  # Check if knowledge retriever is an async function but used in sync mode
5194
5278
  from inspect import iscoroutinefunction
5195
5279
 
5196
- if not async_mode and self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
5280
+ if self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
5197
5281
  log_warning(
5198
5282
  "Async knowledge retriever function is being used with synchronous agent.run() or agent.print_response(). "
5199
5283
  "It is recommended to use agent.arun() or agent.aprint_response() instead."
@@ -5205,7 +5289,7 @@ class Agent:
5205
5289
  agent_tools.append(
5206
5290
  self._search_knowledge_base_with_agentic_filters_function(
5207
5291
  run_response=run_response,
5208
- async_mode=async_mode,
5292
+ async_mode=False,
5209
5293
  knowledge_filters=knowledge_filters,
5210
5294
  )
5211
5295
  )
@@ -5213,11 +5297,10 @@ class Agent:
5213
5297
  agent_tools.append(
5214
5298
  self._get_search_knowledge_base_function(
5215
5299
  run_response=run_response,
5216
- async_mode=async_mode,
5300
+ async_mode=False,
5217
5301
  knowledge_filters=knowledge_filters,
5218
5302
  )
5219
5303
  )
5220
- self._rebuild_tools = True
5221
5304
 
5222
5305
  if self.update_knowledge:
5223
5306
  agent_tools.append(self.add_to_knowledge)
@@ -5228,82 +5311,69 @@ class Agent:
5228
5311
  self,
5229
5312
  run_response: RunOutput,
5230
5313
  session: AgentSession,
5231
- async_mode: bool = False,
5232
5314
  user_id: Optional[str] = None,
5233
5315
  knowledge_filters: Optional[Dict[str, Any]] = None,
5234
- ) -> Optional[List[Union[Toolkit, Callable, Function, Dict]]]:
5316
+ check_mcp_tools: bool = True,
5317
+ ) -> List[Union[Toolkit, Callable, Function, Dict]]:
5235
5318
  agent_tools: List[Union[Toolkit, Callable, Function, Dict]] = []
5236
5319
 
5320
+ # Connect MCP tools
5321
+ await self._connect_mcp_tools()
5322
+
5237
5323
  # Add provided tools
5238
5324
  if self.tools is not None:
5239
- 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
5240
5335
 
5241
- # If any of the tools has "agent" as parameter, set _rebuild_tools to True
5242
- for tool in agent_tools:
5243
- if isinstance(tool, Function):
5244
- if "agent" in tool.parameters:
5245
- self._rebuild_tools = True
5246
- break
5247
- if "team" in tool.parameters:
5248
- self._rebuild_tools = True
5249
- break
5250
- if isinstance(tool, Toolkit):
5251
- for func in tool.functions.values():
5252
- if "agent" in func.parameters:
5253
- self._rebuild_tools = True
5254
- break
5255
- if "team" in func.parameters:
5256
- self._rebuild_tools = True
5257
- break
5258
- if callable(tool):
5259
- from inspect import signature
5260
-
5261
- sig = signature(tool)
5262
- if "agent" in sig.parameters:
5263
- self._rebuild_tools = True
5264
- break
5265
- if "team" in sig.parameters:
5266
- self._rebuild_tools = True
5267
- 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)
5268
5349
 
5269
5350
  # Add tools for accessing memory
5270
5351
  if self.read_chat_history:
5271
5352
  agent_tools.append(self._get_chat_history_function(session=session))
5272
- self._rebuild_tools = True
5273
5353
  if self.read_tool_call_history:
5274
5354
  agent_tools.append(self._get_tool_call_history_function(session=session))
5275
- self._rebuild_tools = True
5276
5355
  if self.search_session_history:
5277
5356
  agent_tools.append(
5278
- 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
+ )
5279
5360
  )
5280
- self._rebuild_tools = True
5281
5361
 
5282
5362
  if self.enable_agentic_memory:
5283
- agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=async_mode))
5284
- self._rebuild_tools = True
5363
+ agent_tools.append(self._get_update_user_memory_function(user_id=user_id, async_mode=True))
5285
5364
 
5286
5365
  if self.enable_agentic_state:
5287
5366
  agent_tools.append(Function(name="update_session_state", entrypoint=self._update_session_state_tool))
5288
5367
 
5289
5368
  # Add tools for accessing knowledge
5290
5369
  if self.knowledge is not None or self.knowledge_retriever is not None:
5291
- # Check if knowledge retriever is an async function but used in sync mode
5292
- from inspect import iscoroutinefunction
5293
-
5294
- if not async_mode and self.knowledge_retriever and iscoroutinefunction(self.knowledge_retriever):
5295
- log_warning(
5296
- "Async knowledge retriever function is being used with synchronous agent.run() or agent.print_response(). "
5297
- "It is recommended to use agent.arun() or agent.aprint_response() instead."
5298
- )
5299
-
5300
5370
  if self.search_knowledge:
5301
5371
  # Use async or sync search based on async_mode
5302
5372
  if self.enable_agentic_knowledge_filters:
5303
5373
  agent_tools.append(
5304
5374
  self._search_knowledge_base_with_agentic_filters_function(
5305
5375
  run_response=run_response,
5306
- async_mode=async_mode,
5376
+ async_mode=True,
5307
5377
  knowledge_filters=knowledge_filters,
5308
5378
  )
5309
5379
  )
@@ -5311,11 +5381,10 @@ class Agent:
5311
5381
  agent_tools.append(
5312
5382
  self._get_search_knowledge_base_function(
5313
5383
  run_response=run_response,
5314
- async_mode=async_mode,
5384
+ async_mode=True,
5315
5385
  knowledge_filters=knowledge_filters,
5316
5386
  )
5317
5387
  )
5318
- self._rebuild_tools = True
5319
5388
 
5320
5389
  if self.update_knowledge:
5321
5390
  agent_tools.append(self.add_to_knowledge)
@@ -5325,231 +5394,105 @@ class Agent:
5325
5394
  def _determine_tools_for_model(
5326
5395
  self,
5327
5396
  model: Model,
5397
+ processed_tools: List[Union[Toolkit, Callable, Function, Dict]],
5328
5398
  run_response: RunOutput,
5329
5399
  session: AgentSession,
5330
5400
  session_state: Optional[Dict[str, Any]] = None,
5331
5401
  dependencies: Optional[Dict[str, Any]] = None,
5332
- user_id: Optional[str] = None,
5333
- async_mode: bool = False,
5334
- knowledge_filters: Optional[Dict[str, Any]] = None,
5335
- ) -> None:
5336
- if self._rebuild_tools:
5337
- self._rebuild_tools = False
5402
+ ) -> List[Union[Function, dict]]:
5403
+ _function_names = []
5404
+ _functions: List[Union[Function, dict]] = []
5405
+ self._tool_instructions = []
5338
5406
 
5339
- agent_tools = self.get_tools(
5340
- run_response=run_response,
5341
- session=session,
5342
- async_mode=async_mode,
5343
- user_id=user_id,
5344
- knowledge_filters=knowledge_filters,
5345
- )
5346
-
5347
- self._tools_for_model = []
5348
- self._functions_for_model = {}
5349
- self._tool_instructions = []
5350
-
5351
- # Get Agent tools
5352
- if agent_tools is not None and len(agent_tools) > 0:
5353
- log_debug("Processing tools for model")
5407
+ # Get Agent tools
5408
+ if processed_tools is not None and len(processed_tools) > 0:
5409
+ log_debug("Processing tools for model")
5354
5410
 
5355
- # Check if we need strict mode for the functions for the model
5356
- strict = False
5357
- if (
5358
- self.output_schema is not None
5359
- and (self.structured_outputs or (not self.use_json_mode))
5360
- and model.supports_native_structured_outputs
5361
- ):
5362
- strict = True
5363
-
5364
- for tool in agent_tools:
5365
- if isinstance(tool, Dict):
5366
- # If a dict is passed, it is a builtin tool
5367
- # that is run by the model provider and not the Agent
5368
- self._tools_for_model.append(tool)
5369
- log_debug(f"Included builtin tool {tool}")
5370
-
5371
- elif isinstance(tool, Toolkit):
5372
- # For each function in the toolkit and process entrypoint
5373
- for name, func in tool.functions.items():
5374
- # If the function does not exist in self.functions
5375
- if name not in self._functions_for_model:
5376
- func._agent = self
5377
- func.process_entrypoint(strict=strict)
5378
- if strict and func.strict is None:
5379
- func.strict = True
5380
- if self.tool_hooks is not None:
5381
- func.tool_hooks = self.tool_hooks
5382
- self._functions_for_model[name] = func
5383
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5384
- log_debug(f"Added tool {name} from {tool.name}")
5385
-
5386
- # Add instructions from the toolkit
5387
- if tool.add_instructions and tool.instructions is not None:
5388
- self._tool_instructions.append(tool.instructions)
5389
-
5390
- elif isinstance(tool, Function):
5391
- if tool.name not in self._functions_for_model:
5392
- tool._agent = self
5393
- tool.process_entrypoint(strict=strict)
5394
- if strict and tool.strict is None:
5395
- tool.strict = True
5396
- if self.tool_hooks is not None:
5397
- tool.tool_hooks = self.tool_hooks
5398
- self._functions_for_model[tool.name] = tool
5399
- self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
5400
- log_debug(f"Added tool {tool.name}")
5401
-
5402
- # Add instructions from the Function
5403
- if tool.add_instructions and tool.instructions is not None:
5404
- self._tool_instructions.append(tool.instructions)
5405
-
5406
- elif callable(tool):
5407
- try:
5408
- function_name = tool.__name__
5409
- if function_name not in self._functions_for_model:
5410
- func = Function.from_callable(tool, strict=strict)
5411
- func._agent = self
5412
- if strict:
5413
- func.strict = True
5414
- if self.tool_hooks is not None:
5415
- func.tool_hooks = self.tool_hooks
5416
- self._functions_for_model[func.name] = func
5417
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5418
- log_debug(f"Added tool {func.name}")
5419
- except Exception as e:
5420
- log_warning(f"Could not add tool {tool}: {e}")
5421
-
5422
- # Update the session state for the functions
5423
- if self._functions_for_model:
5424
- from inspect import signature
5425
-
5426
- # Check if any functions need media before collecting
5427
- needs_media = any(
5428
- any(param in signature(func.entrypoint).parameters for param in ["images", "videos", "audios", "files"])
5429
- for func in self._functions_for_model.values()
5430
- if func.entrypoint is not None
5431
- )
5432
-
5433
- # Only collect media if functions actually need them
5434
- joint_images = collect_joint_images(run_response.input, session) if needs_media else None
5435
- joint_files = collect_joint_files(run_response.input) if needs_media else None
5436
- joint_audios = collect_joint_audios(run_response.input, session) if needs_media else None
5437
- joint_videos = collect_joint_videos(run_response.input, session) if needs_media else None
5438
-
5439
- for func in self._functions_for_model.values():
5440
- func._session_state = session_state
5441
- func._dependencies = dependencies
5442
- func._images = joint_images
5443
- func._files = joint_files
5444
- func._audios = joint_audios
5445
- func._videos = joint_videos
5446
-
5447
- async def _adetermine_tools_for_model(
5448
- self,
5449
- model: Model,
5450
- run_response: RunOutput,
5451
- session: AgentSession,
5452
- session_state: Optional[Dict[str, Any]] = None,
5453
- dependencies: Optional[Dict[str, Any]] = None,
5454
- user_id: Optional[str] = None,
5455
- async_mode: bool = False,
5456
- knowledge_filters: Optional[Dict[str, Any]] = None,
5457
- ) -> None:
5458
- if self._rebuild_tools:
5459
- self._rebuild_tools = False
5460
-
5461
- agent_tools = await self.aget_tools(
5462
- run_response=run_response,
5463
- session=session,
5464
- async_mode=async_mode,
5465
- user_id=user_id,
5466
- knowledge_filters=knowledge_filters,
5467
- )
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
5468
5419
 
5469
- self._tools_for_model = []
5470
- self._functions_for_model = {}
5471
- 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}")
5472
5426
 
5473
- # Get Agent tools
5474
- if agent_tools is not None and len(agent_tools) > 0:
5475
- 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)
5476
5466
 
5477
- # Check if we need strict mode for the functions for the model
5478
- strict = False
5479
- if (
5480
- self.output_schema is not None
5481
- and (self.structured_outputs or (not self.use_json_mode))
5482
- and model.supports_native_structured_outputs
5483
- ):
5484
- strict = True
5485
-
5486
- for tool in agent_tools:
5487
- if isinstance(tool, Dict):
5488
- # If a dict is passed, it is a builtin tool
5489
- # that is run by the model provider and not the Agent
5490
- self._tools_for_model.append(tool)
5491
- log_debug(f"Included builtin tool {tool}")
5492
-
5493
- elif isinstance(tool, Toolkit):
5494
- # For each function in the toolkit and process entrypoint
5495
- for name, func in tool.functions.items():
5496
- # If the function does not exist in self.functions
5497
- if name not in self._functions_for_model:
5498
- func._agent = self
5499
- func.process_entrypoint(strict=strict)
5500
- if strict and func.strict is None:
5501
- func.strict = True
5502
- if self.tool_hooks is not None:
5503
- func.tool_hooks = self.tool_hooks
5504
- self._functions_for_model[name] = func
5505
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5506
- log_debug(f"Added tool {name} from {tool.name}")
5507
-
5508
- # Add instructions from the toolkit
5509
- if tool.add_instructions and tool.instructions is not None:
5510
- self._tool_instructions.append(tool.instructions)
5511
-
5512
- elif isinstance(tool, Function):
5513
- if tool.name not in self._functions_for_model:
5514
- tool._agent = self
5515
- tool.process_entrypoint(strict=strict)
5516
- if strict and tool.strict is None:
5517
- tool.strict = True
5518
- if self.tool_hooks is not None:
5519
- tool.tool_hooks = self.tool_hooks
5520
- self._functions_for_model[tool.name] = tool
5521
- self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
5522
- log_debug(f"Added tool {tool.name}")
5523
-
5524
- # Add instructions from the Function
5525
- if tool.add_instructions and tool.instructions is not None:
5526
- self._tool_instructions.append(tool.instructions)
5527
-
5528
- elif callable(tool):
5529
- try:
5530
- function_name = tool.__name__
5531
- if function_name not in self._functions_for_model:
5532
- func = Function.from_callable(tool, strict=strict)
5533
- func._agent = self
5534
- if strict:
5535
- func.strict = True
5536
- if self.tool_hooks is not None:
5537
- func.tool_hooks = self.tool_hooks
5538
- self._functions_for_model[func.name] = func
5539
- self._tools_for_model.append({"type": "function", "function": func.to_dict()})
5540
- log_debug(f"Added tool {func.name}")
5541
- except Exception as e:
5542
- 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}")
5543
5486
 
5544
5487
  # Update the session state for the functions
5545
- if self._functions_for_model:
5488
+ if _functions:
5546
5489
  from inspect import signature
5547
5490
 
5548
5491
  # Check if any functions need media before collecting
5549
5492
  needs_media = any(
5550
5493
  any(param in signature(func.entrypoint).parameters for param in ["images", "videos", "audios", "files"])
5551
- for func in self._functions_for_model.values()
5552
- if func.entrypoint is not None
5494
+ for func in _functions
5495
+ if isinstance(func, Function) and func.entrypoint is not None
5553
5496
  )
5554
5497
 
5555
5498
  # Only collect media if functions actually need them
@@ -5558,13 +5501,16 @@ class Agent:
5558
5501
  joint_audios = collect_joint_audios(run_response.input, session) if needs_media else None
5559
5502
  joint_videos = collect_joint_videos(run_response.input, session) if needs_media else None
5560
5503
 
5561
- for func in self._functions_for_model.values():
5562
- func._session_state = session_state
5563
- func._dependencies = dependencies
5564
- func._images = joint_images
5565
- func._files = joint_files
5566
- func._audios = joint_audios
5567
- 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
5568
5514
 
5569
5515
  def _model_should_return_structured_output(self):
5570
5516
  self.model = cast(Model, self.model)
@@ -5664,6 +5610,18 @@ class Agent:
5664
5610
  agent_data["model"] = self.model.to_dict()
5665
5611
  return agent_data
5666
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
+
5667
5625
  # -*- Session Database Functions
5668
5626
  def _read_session(
5669
5627
  self, session_id: str, session_type: SessionType = SessionType.AGENT
@@ -5767,8 +5725,8 @@ class Agent:
5767
5725
  from time import time
5768
5726
 
5769
5727
  # Returning cached session if we have one
5770
- if self._agent_session is not None and self._agent_session.session_id == session_id:
5771
- 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
5772
5730
 
5773
5731
  # Try to load from database
5774
5732
  agent_session = None
@@ -5796,7 +5754,7 @@ class Agent:
5796
5754
  )
5797
5755
 
5798
5756
  if self.cache_session:
5799
- self._agent_session = agent_session
5757
+ self._cached_session = agent_session
5800
5758
 
5801
5759
  return agent_session
5802
5760
 
@@ -5808,8 +5766,8 @@ class Agent:
5808
5766
  from time import time
5809
5767
 
5810
5768
  # Returning cached session if we have one
5811
- if self._agent_session is not None and self._agent_session.session_id == session_id:
5812
- 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
5813
5771
 
5814
5772
  # Try to load from database
5815
5773
  agent_session = None
@@ -5839,10 +5797,11 @@ class Agent:
5839
5797
  )
5840
5798
 
5841
5799
  if self.cache_session:
5842
- self._agent_session = agent_session
5800
+ self._cached_session = agent_session
5843
5801
 
5844
5802
  return agent_session
5845
5803
 
5804
+ # -*- Public Convenience Functions
5846
5805
  def get_run_output(self, run_id: str, session_id: Optional[str] = None) -> Optional[RunOutput]:
5847
5806
  """
5848
5807
  Get a RunOutput from the database.
@@ -5850,23 +5809,30 @@ class Agent:
5850
5809
  Args:
5851
5810
  run_id (str): The run_id to load from storage.
5852
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.
5853
5814
  """
5854
- if self._agent_session is not None:
5855
- run_response = self._agent_session.get_run(run_id=run_id)
5856
- if run_response is not None:
5857
- return run_response
5858
- else:
5859
- log_warning(f"RunOutput {run_id} not found in AgentSession {self._agent_session.session_id}")
5860
- return None
5861
- else:
5862
- session = self.get_session(session_id=session_id)
5863
- if session is not None:
5864
- run_response = session.get_run(run_id=run_id)
5865
- if run_response is not None:
5866
- return run_response
5867
- else:
5868
- log_warning(f"RunOutput {run_id} not found in Session {session_id}")
5869
- 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))
5870
5836
 
5871
5837
  def get_last_run_output(self, session_id: Optional[str] = None) -> Optional[RunOutput]:
5872
5838
  """
@@ -5876,36 +5842,29 @@ class Agent:
5876
5842
  session_id (Optional[str]): The session_id to load from storage.
5877
5843
 
5878
5844
  Returns:
5879
- RunOutput: The last run response from the database.
5845
+ Optional[RunOutput]: The last run response from the database or None if not found.
5880
5846
  """
5881
- if (
5882
- self._agent_session is not None
5883
- and self._agent_session.runs is not None
5884
- and len(self._agent_session.runs) > 0
5885
- ):
5886
- for run_output in reversed(self._agent_session.runs):
5887
- if hasattr(run_output, "agent_id") and run_output.agent_id == self.id:
5888
- return run_output
5889
- else:
5890
- session = self.get_session(session_id=session_id)
5891
- if session is not None and session.runs is not None and len(session.runs) > 0:
5892
- for run_output in reversed(session.runs):
5893
- if hasattr(run_output, "agent_id") and run_output.agent_id == self.id:
5894
- return run_output
5895
- else:
5896
- log_warning(f"No run responses found in Session {session_id}")
5897
- return None
5847
+ if not session_id and not self.session_id:
5848
+ raise Exception("No session_id provided")
5898
5849
 
5899
- def cancel_run(self, run_id: str) -> bool:
5900
- """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.
5901
5856
 
5902
5857
  Args:
5903
- run_id (str): The run_id to cancel.
5858
+ session_id (Optional[str]): The session_id to load from storage.
5904
5859
 
5905
5860
  Returns:
5906
- 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.
5907
5862
  """
5908
- 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))
5909
5868
 
5910
5869
  def get_session(
5911
5870
  self,
@@ -5925,9 +5884,12 @@ class Agent:
5925
5884
  session_id_to_load = session_id or self.session_id
5926
5885
 
5927
5886
  # If there is a cached session, return it
5928
- if self.cache_session and hasattr(self, "_agent_session") and self._agent_session is not None:
5929
- if self._agent_session.session_id == session_id_to_load:
5930
- 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")
5931
5893
 
5932
5894
  # Load and return the session from the database
5933
5895
  if self.db is not None:
@@ -5958,7 +5920,7 @@ class Agent:
5958
5920
 
5959
5921
  # Cache the session if relevant
5960
5922
  if loaded_session is not None and self.cache_session:
5961
- self._agent_session = loaded_session
5923
+ self._cached_session = loaded_session
5962
5924
 
5963
5925
  return loaded_session
5964
5926
 
@@ -5983,29 +5945,53 @@ class Agent:
5983
5945
  session_id_to_load = session_id or self.session_id
5984
5946
 
5985
5947
  # If there is a cached session, return it
5986
- if self.cache_session and hasattr(self, "_agent_session") and self._agent_session is not None:
5987
- if self._agent_session.session_id == session_id_to_load:
5988
- 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
5989
5951
 
5990
5952
  # Load and return the session from the database
5991
5953
  if self.db is not None:
5992
- 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
+ )
5993
5978
 
5994
5979
  # Cache the session if relevant
5995
- if agent_session is not None and self.cache_session:
5996
- self._agent_session = agent_session
5980
+ if loaded_session is not None and self.cache_session:
5981
+ self._cached_session = loaded_session
5997
5982
 
5998
- return agent_session
5983
+ return loaded_session
5999
5984
 
6000
5985
  log_debug(f"AgentSession {session_id_to_load} not found in db")
6001
5986
  return None
6002
5987
 
6003
5988
  def save_session(self, session: AgentSession) -> None:
6004
- """Save the AgentSession to storage
6005
-
6006
- Returns:
6007
- Optional[AgentSession]: The saved AgentSession or None if not saved.
6008
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
+
6009
5995
  # If the agent is a member of a team, do not save the session to the database
6010
5996
  if (
6011
5997
  self.db is not None
@@ -6022,10 +6008,8 @@ class Agent:
6022
6008
  log_debug(f"Created or updated AgentSession record: {session.session_id}")
6023
6009
 
6024
6010
  async def asave_session(self, session: AgentSession) -> None:
6025
- """Save the AgentSession to storage
6026
-
6027
- Returns:
6028
- Optional[AgentSession]: The saved AgentSession or None if not saved.
6011
+ """
6012
+ Save the AgentSession to storage
6029
6013
  """
6030
6014
  # If the agent is a member of a team, do not save the session to the database
6031
6015
  if (
@@ -6045,27 +6029,56 @@ class Agent:
6045
6029
  log_debug(f"Created or updated AgentSession record: {session.session_id}")
6046
6030
 
6047
6031
  def get_chat_history(self, session_id: Optional[str] = None) -> List[Message]:
6048
- """Read the chat history from the session"""
6049
- if not session_id and not self.session_id:
6050
- raise Exception("No session_id provided")
6032
+ """Read the chat history from the session
6051
6033
 
6052
- session_id_to_load = session_id or self.session_id
6053
- 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 []
6054
6043
 
6055
- if session is None:
6056
- 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 []
6057
6058
 
6058
- return session.get_chat_history()
6059
+ return await aget_chat_history_util(self, session_id=session_id)
6059
6060
 
6061
+ # -*- Session Management Functions
6060
6062
  def rename(self, name: str, session_id: Optional[str] = None) -> None:
6061
- """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
+ """
6062
6070
 
6063
6071
  session_id = session_id or self.session_id
6064
6072
 
6065
6073
  if session_id is None:
6066
6074
  raise Exception("Session ID is not set")
6067
6075
 
6068
- 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)
6069
6082
 
6070
6083
  if session is None:
6071
6084
  raise Exception("Session not found")
@@ -6078,7 +6091,12 @@ class Agent:
6078
6091
  session.agent_data = {"name": name}
6079
6092
 
6080
6093
  # -*- Save to storage
6081
- 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)
6082
6100
 
6083
6101
  def set_session_name(
6084
6102
  self,
@@ -6086,38 +6104,63 @@ class Agent:
6086
6104
  autogenerate: bool = False,
6087
6105
  session_name: Optional[str] = None,
6088
6106
  ) -> AgentSession:
6089
- """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
+ """
6090
6117
  session_id = session_id or self.session_id
6091
6118
 
6092
6119
  if session_id is None:
6093
6120
  raise Exception("Session ID is not set")
6094
6121
 
6095
- # -*- Read from storage
6096
- 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
+ )
6097
6126
 
6098
- if session is None:
6099
- 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
6100
6135
 
6101
- # -*- Generate name for session
6102
- if autogenerate:
6103
- session_name = self._generate_session_name(session=session)
6104
- log_debug(f"Generated Session Name: {session_name}")
6105
- elif session_name is None:
6106
- 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
6107
6144
 
6108
- # -*- Rename session
6109
- if session.session_data is not None:
6110
- session.session_data["session_name"] = session_name
6111
- else:
6112
- session.session_data = {"session_name": session_name}
6145
+ if session_id is None:
6146
+ raise Exception("Session ID is not set")
6113
6147
 
6114
- # -*- Save to storage
6115
- 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
+ )
6116
6154
 
6117
- 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
6118
6158
 
6119
- def _generate_session_name(self, session: AgentSession) -> str:
6120
- """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
+ """
6121
6164
 
6122
6165
  if self.model is None:
6123
6166
  raise Exception("Model not set")
@@ -6144,32 +6187,68 @@ class Agent:
6144
6187
  content = generated_name.content
6145
6188
  if content is None:
6146
6189
  log_error("Generated name is None. Trying again.")
6147
- return self._generate_session_name(session=session)
6190
+ return self.generate_session_name(session=session)
6148
6191
 
6149
6192
  if len(content.split()) > 5:
6150
6193
  log_error("Generated name is too long. It should be less than 5 words. Trying again.")
6151
- return self._generate_session_name(session=session)
6194
+ return self.generate_session_name(session=session)
6152
6195
  return content.replace('"', "").strip()
6153
6196
 
6154
6197
  def get_session_name(self, session_id: Optional[str] = None) -> str:
6155
- """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
+ """
6156
6206
  session_id = session_id or self.session_id
6157
6207
  if session_id is None:
6158
6208
  raise Exception("Session ID is not set")
6159
- session = self.get_session(session_id=session_id) # type: ignore
6160
- if session is None:
6161
- raise Exception("Session not found")
6162
- 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)
6163
6224
 
6164
6225
  def get_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
6165
- """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
+ """
6166
6234
  session_id = session_id or self.session_id
6167
6235
  if session_id is None:
6168
6236
  raise Exception("Session ID is not set")
6169
- session = self.get_session(session_id=session_id) # type: ignore
6170
- if session is None:
6171
- raise Exception("Session not found")
6172
- 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)
6173
6252
 
6174
6253
  def update_session_state(self, session_state_updates: Dict[str, Any], session_id: Optional[str] = None) -> str:
6175
6254
  """
@@ -6183,20 +6262,7 @@ class Agent:
6183
6262
  session_id = session_id or self.session_id
6184
6263
  if session_id is None:
6185
6264
  raise Exception("Session ID is not set")
6186
- session = self.get_session(session_id=session_id) # type: ignore
6187
- if session is None:
6188
- raise Exception("Session not found")
6189
-
6190
- if session.session_data is not None and "session_state" not in session.session_data:
6191
- session.session_data["session_state"] = {}
6192
-
6193
- # Overwrite the loaded DB session state with the new session state
6194
- for key, value in session_state_updates.items():
6195
- session.session_data["session_state"][key] = value # type: ignore
6196
-
6197
- self.save_session(session=session)
6198
-
6199
- 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)
6200
6266
 
6201
6267
  async def aupdate_session_state(
6202
6268
  self, session_state_updates: Dict[str, Any], session_id: Optional[str] = None
@@ -6212,52 +6278,65 @@ class Agent:
6212
6278
  session_id = session_id or self.session_id
6213
6279
  if session_id is None:
6214
6280
  raise Exception("Session ID is not set")
6215
- session = await self.aget_session(session_id=session_id) # type: ignore
6216
- if session is None:
6217
- 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
+ )
6218
6284
 
6219
- if session.session_data is not None and "session_state" not in session.session_data:
6220
- 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.
6221
6287
 
6222
- for key, value in session_state_updates.items():
6223
- 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")
6224
6296
 
6225
- await self.asave_session(session=session)
6297
+ return get_session_metrics_util(self, session_id=session_id)
6226
6298
 
6227
- 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.
6228
6301
 
6229
- def get_session_metrics(self, session_id: Optional[str] = None) -> Optional[Metrics]:
6230
- """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
+ """
6231
6307
  session_id = session_id or self.session_id
6232
6308
  if session_id is None:
6233
6309
  raise Exception("Session ID is not set")
6234
6310
 
6235
- session = self.get_session(session_id=session_id) # type: ignore
6236
- if session is None:
6237
- raise Exception("Session not found")
6238
-
6239
- if session.session_data is not None and session.session_data.get("session_metrics") is not None:
6240
- if isinstance(session.session_data.get("session_metrics"), dict):
6241
- return Metrics(**session.session_data.get("session_metrics", {}))
6242
- elif isinstance(session.session_data.get("session_metrics"), Metrics):
6243
- return session.session_data.get("session_metrics", None)
6244
- return None
6311
+ return await aget_session_metrics_util(self, session_id=session_id)
6245
6312
 
6246
6313
  def delete_session(self, session_id: str):
6247
6314
  """Delete the current session and save to storage"""
6248
6315
  if self.db is None:
6249
6316
  return
6250
- # -*- Delete session
6317
+
6251
6318
  self.db.delete_session(session_id=session_id)
6252
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
+
6253
6326
  def get_messages_for_session(self, session_id: Optional[str] = None) -> List[Message]:
6254
- """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
+ """
6255
6334
  session_id = session_id or self.session_id
6256
6335
  if session_id is None:
6257
6336
  log_warning("Session ID is not set, cannot get messages for session")
6258
6337
  return []
6259
6338
 
6260
- session = self.get_session(session_id=session_id) # type: ignore
6339
+ session = self.get_session(session_id=session_id)
6261
6340
 
6262
6341
  if session is None:
6263
6342
  raise Exception("Session not found")
@@ -6267,8 +6346,37 @@ class Agent:
6267
6346
  agent_id=self.id if self.team_id is not None else None,
6268
6347
  )
6269
6348
 
6270
- def get_session_summary(self, session_id: Optional[str] = None):
6271
- """Get the session summary for the given session ID and user ID."""
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
+ """
6357
+ session_id = session_id or self.session_id
6358
+ if session_id is None:
6359
+ log_warning("Session ID is not set, cannot get messages for session")
6360
+ return []
6361
+
6362
+ session = await self.aget_session(session_id=session_id)
6363
+
6364
+ if session is None:
6365
+ raise Exception("Session not found")
6366
+
6367
+ # Only filter by agent_id if this is part of a team
6368
+ return session.get_messages_from_last_n_runs(
6369
+ agent_id=self.id if self.team_id is not None else None,
6370
+ )
6371
+
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
+ """
6272
6380
  session_id = session_id if session_id is not None else self.session_id
6273
6381
  if session_id is None:
6274
6382
  raise ValueError("Session ID is required")
@@ -6280,8 +6388,33 @@ class Agent:
6280
6388
 
6281
6389
  return session.get_session_summary()
6282
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
+
6283
6410
  def get_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
6284
- """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
+ """
6285
6418
  if self.memory_manager is None:
6286
6419
  return None
6287
6420
  user_id = user_id if user_id is not None else self.user_id
@@ -6291,7 +6424,13 @@ class Agent:
6291
6424
  return self.memory_manager.get_user_memories(user_id=user_id)
6292
6425
 
6293
6426
  async def aget_user_memories(self, user_id: Optional[str] = None) -> Optional[List[UserMemory]]:
6294
- """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
+ """
6295
6434
  if self.memory_manager is None:
6296
6435
  return None
6297
6436
  user_id = user_id if user_id is not None else self.user_id
@@ -6301,19 +6440,28 @@ class Agent:
6301
6440
  return await self.memory_manager.aget_user_memories(user_id=user_id)
6302
6441
 
6303
6442
  def get_culture_knowledge(self) -> Optional[List[CulturalKnowledge]]:
6304
- """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
+ """
6305
6448
  if self.culture_manager is None:
6306
6449
  return None
6307
6450
 
6308
6451
  return self.culture_manager.get_all_knowledge()
6309
6452
 
6310
6453
  async def aget_culture_knowledge(self) -> Optional[List[CulturalKnowledge]]:
6311
- """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
+ """
6312
6459
  if self.culture_manager is None:
6313
6460
  return None
6314
6461
 
6315
6462
  return await self.culture_manager.aget_all_knowledge()
6316
6463
 
6464
+ # -*- System & User Message Functions
6317
6465
  def _format_message_with_state_variables(
6318
6466
  self,
6319
6467
  message: Any,
@@ -6358,6 +6506,7 @@ class Agent:
6358
6506
  session: AgentSession,
6359
6507
  session_state: Optional[Dict[str, Any]] = None,
6360
6508
  user_id: Optional[str] = None,
6509
+ tools: Optional[List[Union[Function, dict]]] = None,
6361
6510
  dependencies: Optional[Dict[str, Any]] = None,
6362
6511
  metadata: Optional[Dict[str, Any]] = None,
6363
6512
  add_session_state_to_context: Optional[bool] = None,
@@ -6429,7 +6578,7 @@ class Agent:
6429
6578
  instructions.extend(_instructions)
6430
6579
 
6431
6580
  # 3.1.1 Add instructions from the Model
6432
- _model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
6581
+ _model_instructions = self.model.get_instructions_for_model(tools)
6433
6582
  if _model_instructions is not None:
6434
6583
  instructions.extend(_model_instructions)
6435
6584
 
@@ -6664,7 +6813,7 @@ class Agent:
6664
6813
  )
6665
6814
 
6666
6815
  # 3.3.12 Add the system message from the Model
6667
- 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)
6668
6817
  if system_message_from_model is not None:
6669
6818
  system_message_content += system_message_from_model
6670
6819
 
@@ -6700,6 +6849,7 @@ class Agent:
6700
6849
  session: AgentSession,
6701
6850
  session_state: Optional[Dict[str, Any]] = None,
6702
6851
  user_id: Optional[str] = None,
6852
+ tools: Optional[List[Union[Function, dict]]] = None,
6703
6853
  dependencies: Optional[Dict[str, Any]] = None,
6704
6854
  metadata: Optional[Dict[str, Any]] = None,
6705
6855
  ) -> Optional[Message]:
@@ -6770,7 +6920,7 @@ class Agent:
6770
6920
  instructions.extend(_instructions)
6771
6921
 
6772
6922
  # 3.1.1 Add instructions from the Model
6773
- _model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
6923
+ _model_instructions = self.model.get_instructions_for_model(tools)
6774
6924
  if _model_instructions is not None:
6775
6925
  instructions.extend(_model_instructions)
6776
6926
 
@@ -7008,7 +7158,7 @@ class Agent:
7008
7158
  )
7009
7159
 
7010
7160
  # 3.3.12 Add the system message from the Model
7011
- 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)
7012
7162
  if system_message_from_model is not None:
7013
7163
  system_message_content += system_message_from_model
7014
7164
 
@@ -7227,6 +7377,7 @@ class Agent:
7227
7377
  add_dependencies_to_context: Optional[bool] = None,
7228
7378
  add_session_state_to_context: Optional[bool] = None,
7229
7379
  metadata: Optional[Dict[str, Any]] = None,
7380
+ tools: Optional[List[Union[Function, dict]]] = None,
7230
7381
  **kwargs: Any,
7231
7382
  ) -> RunMessages:
7232
7383
  """This function returns a RunMessages object with the following attributes:
@@ -7261,6 +7412,7 @@ class Agent:
7261
7412
  session=session,
7262
7413
  session_state=session_state,
7263
7414
  user_id=user_id,
7415
+ tools=tools,
7264
7416
  dependencies=dependencies,
7265
7417
  metadata=metadata,
7266
7418
  add_session_state_to_context=add_session_state_to_context,
@@ -7321,6 +7473,10 @@ class Agent:
7321
7473
  for _msg in history_copy:
7322
7474
  _msg.from_history = True
7323
7475
 
7476
+ # Filter tool calls from history if limit is set (before adding to run_messages)
7477
+ if self.max_tool_calls_from_history is not None:
7478
+ filter_tool_calls(history_copy, self.max_tool_calls_from_history)
7479
+
7324
7480
  log_debug(f"Adding {len(history_copy)} messages from history")
7325
7481
 
7326
7482
  run_messages.messages += history_copy
@@ -7428,6 +7584,7 @@ class Agent:
7428
7584
  add_dependencies_to_context: Optional[bool] = None,
7429
7585
  add_session_state_to_context: Optional[bool] = None,
7430
7586
  metadata: Optional[Dict[str, Any]] = None,
7587
+ tools: Optional[List[Union[Function, dict]]] = None,
7431
7588
  **kwargs: Any,
7432
7589
  ) -> RunMessages:
7433
7590
  """This function returns a RunMessages object with the following attributes:
@@ -7462,6 +7619,7 @@ class Agent:
7462
7619
  session=session,
7463
7620
  session_state=session_state,
7464
7621
  user_id=user_id,
7622
+ tools=tools,
7465
7623
  dependencies=dependencies,
7466
7624
  metadata=metadata,
7467
7625
  )
@@ -7514,6 +7672,10 @@ class Agent:
7514
7672
  for _msg in history_copy:
7515
7673
  _msg.from_history = True
7516
7674
 
7675
+ # Filter tool calls from history if limit is set (before adding to run_messages)
7676
+ if self.max_tool_calls_from_history is not None:
7677
+ filter_tool_calls(history_copy, self.max_tool_calls_from_history)
7678
+
7517
7679
  log_debug(f"Adding {len(history_copy)} messages from history")
7518
7680
 
7519
7681
  run_messages.messages += history_copy
@@ -9325,6 +9487,8 @@ class Agent:
9325
9487
  document_name = query.replace(" ", "_").replace("?", "").replace("!", "").replace(".", "")
9326
9488
  document_content = json.dumps({"query": query, "result": result})
9327
9489
  log_info(f"Adding document to Knowledge: {document_name}: {document_content}")
9490
+ import asyncio
9491
+
9328
9492
  from agno.knowledge.reader.text_reader import TextReader
9329
9493
 
9330
9494
  asyncio.run(
@@ -9399,12 +9563,14 @@ class Agent:
9399
9563
 
9400
9564
  return get_previous_session_messages
9401
9565
 
9402
- 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:
9403
9569
  """Factory function to create a get_previous_session_messages function.
9404
9570
 
9405
9571
  Args:
9406
9572
  num_history_sessions: The last n sessions to be taken from db
9407
-
9573
+ user_id: The user ID to filter sessions by
9408
9574
  Returns:
9409
9575
  Callable: A function that retrieves messages from previous sessions
9410
9576
  """
@@ -9422,12 +9588,22 @@ class Agent:
9422
9588
  if self.db is None:
9423
9589
  return "Previous session messages not available"
9424
9590
 
9425
- if isinstance(self.db, AsyncBaseDb):
9426
- selected_sessions = await self.db.get_sessions(
9427
- 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",
9428
9598
  )
9429
9599
  else:
9430
- 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
+ )
9431
9607
 
9432
9608
  all_messages = []
9433
9609
  seen_message_pairs = set()
@@ -9460,7 +9636,7 @@ class Agent:
9460
9636
 
9461
9637
  return json.dumps([msg.to_dict() for msg in all_messages]) if all_messages else "No history found"
9462
9638
 
9463
- return aget_previous_session_messages
9639
+ return Function.from_callable(aget_previous_session_messages, name="get_previous_session_messages")
9464
9640
 
9465
9641
  ###########################################################################
9466
9642
  # Print Response