agno 2.0.0rc2__py3-none-any.whl → 2.3.0__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 (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/utils/agent.py ADDED
@@ -0,0 +1,820 @@
1
+ from asyncio import Future, Task
2
+ from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Iterator, List, Optional, Sequence, Union
3
+
4
+ from agno.media import Audio, File, Image, Video
5
+ from agno.models.message import Message
6
+ from agno.models.metrics import Metrics
7
+ from agno.models.response import ModelResponse
8
+ from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
9
+ from agno.run.team import RunOutputEvent as TeamRunOutputEvent
10
+ from agno.run.team import TeamRunOutput
11
+ from agno.session import AgentSession, TeamSession, WorkflowSession
12
+ from agno.utils.events import (
13
+ create_memory_update_completed_event,
14
+ create_memory_update_started_event,
15
+ create_team_memory_update_completed_event,
16
+ create_team_memory_update_started_event,
17
+ handle_event,
18
+ )
19
+ from agno.utils.log import log_debug, log_warning
20
+
21
+ if TYPE_CHECKING:
22
+ from agno.agent.agent import Agent
23
+ from agno.team.team import Team
24
+
25
+
26
+ async def await_for_background_tasks(
27
+ memory_task: Optional[Task] = None,
28
+ cultural_knowledge_task: Optional[Task] = None,
29
+ ) -> None:
30
+ if memory_task is not None:
31
+ try:
32
+ await memory_task
33
+ except Exception as e:
34
+ log_warning(f"Error in memory creation: {str(e)}")
35
+
36
+ if cultural_knowledge_task is not None:
37
+ try:
38
+ await cultural_knowledge_task
39
+ except Exception as e:
40
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
41
+
42
+
43
+ def wait_for_background_tasks(
44
+ memory_future: Optional[Future] = None, cultural_knowledge_future: Optional[Future] = None
45
+ ) -> None:
46
+ if memory_future is not None:
47
+ try:
48
+ memory_future.result()
49
+ except Exception as e:
50
+ log_warning(f"Error in memory creation: {str(e)}")
51
+
52
+ # Wait for cultural knowledge creation
53
+ if cultural_knowledge_future is not None:
54
+ try:
55
+ cultural_knowledge_future.result()
56
+ except Exception as e:
57
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
58
+
59
+
60
+ async def await_for_background_tasks_stream(
61
+ run_response: Union[RunOutput, TeamRunOutput],
62
+ memory_task: Optional[Task] = None,
63
+ cultural_knowledge_task: Optional[Task] = None,
64
+ stream_events: bool = False,
65
+ events_to_skip: Optional[List[RunEvent]] = None,
66
+ store_events: bool = False,
67
+ ) -> AsyncIterator[RunOutputEvent]:
68
+ if memory_task is not None:
69
+ if stream_events:
70
+ if isinstance(run_response, TeamRunOutput):
71
+ yield handle_event( # type: ignore
72
+ create_team_memory_update_started_event(from_run_response=run_response),
73
+ run_response,
74
+ events_to_skip=events_to_skip, # type: ignore
75
+ store_events=store_events,
76
+ )
77
+ else:
78
+ yield handle_event( # type: ignore
79
+ create_memory_update_started_event(from_run_response=run_response),
80
+ run_response,
81
+ events_to_skip=events_to_skip, # type: ignore
82
+ store_events=store_events,
83
+ )
84
+ try:
85
+ await memory_task
86
+ except Exception as e:
87
+ log_warning(f"Error in memory creation: {str(e)}")
88
+ if stream_events:
89
+ if isinstance(run_response, TeamRunOutput):
90
+ yield handle_event( # type: ignore
91
+ create_team_memory_update_completed_event(from_run_response=run_response),
92
+ run_response,
93
+ events_to_skip=events_to_skip, # type: ignore
94
+ store_events=store_events,
95
+ )
96
+ else:
97
+ yield handle_event( # type: ignore
98
+ create_memory_update_completed_event(from_run_response=run_response),
99
+ run_response,
100
+ events_to_skip=events_to_skip, # type: ignore
101
+ store_events=store_events,
102
+ )
103
+
104
+ if cultural_knowledge_task is not None:
105
+ try:
106
+ await cultural_knowledge_task
107
+ except Exception as e:
108
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
109
+
110
+
111
+ def wait_for_background_tasks_stream(
112
+ run_response: Union[TeamRunOutput, RunOutput],
113
+ memory_future: Optional[Future] = None,
114
+ cultural_knowledge_future: Optional[Future] = None,
115
+ stream_events: bool = False,
116
+ events_to_skip: Optional[List[RunEvent]] = None,
117
+ store_events: bool = False,
118
+ ) -> Iterator[Union[RunOutputEvent, TeamRunOutputEvent]]:
119
+ if memory_future is not None:
120
+ if stream_events:
121
+ if isinstance(run_response, TeamRunOutput):
122
+ yield handle_event( # type: ignore
123
+ create_team_memory_update_started_event(from_run_response=run_response),
124
+ run_response,
125
+ events_to_skip=events_to_skip, # type: ignore
126
+ store_events=store_events,
127
+ )
128
+ else:
129
+ yield handle_event( # type: ignore
130
+ create_memory_update_started_event(from_run_response=run_response),
131
+ run_response,
132
+ events_to_skip=events_to_skip, # type: ignore
133
+ store_events=store_events,
134
+ )
135
+ try:
136
+ memory_future.result()
137
+ except Exception as e:
138
+ log_warning(f"Error in memory creation: {str(e)}")
139
+ if stream_events:
140
+ if isinstance(run_response, TeamRunOutput):
141
+ yield handle_event( # type: ignore
142
+ create_team_memory_update_completed_event(from_run_response=run_response),
143
+ run_response,
144
+ events_to_skip=events_to_skip, # type: ignore
145
+ store_events=store_events,
146
+ )
147
+ else:
148
+ yield handle_event( # type: ignore
149
+ create_memory_update_completed_event(from_run_response=run_response),
150
+ run_response,
151
+ events_to_skip=events_to_skip, # type: ignore
152
+ store_events=store_events,
153
+ )
154
+
155
+ # Wait for cultural knowledge creation
156
+ if cultural_knowledge_future is not None:
157
+ # TODO: Add events
158
+ try:
159
+ cultural_knowledge_future.result()
160
+ except Exception as e:
161
+ log_warning(f"Error in cultural knowledge creation: {str(e)}")
162
+
163
+
164
+ def collect_joint_images(
165
+ run_input: Optional[RunInput] = None,
166
+ session: Optional[Union[AgentSession, TeamSession]] = None,
167
+ ) -> Optional[Sequence[Image]]:
168
+ """Collect images from input, session history, and current run response."""
169
+ joint_images: List[Image] = []
170
+
171
+ # 1. Add images from current input
172
+ if run_input and run_input.images:
173
+ joint_images.extend(run_input.images)
174
+ log_debug(f"Added {len(run_input.images)} input images to joint list")
175
+
176
+ # 2. Add images from session history (from both input and generated sources)
177
+ try:
178
+ if session and session.runs:
179
+ for historical_run in session.runs:
180
+ # Add generated images from previous runs
181
+ if historical_run.images:
182
+ joint_images.extend(historical_run.images)
183
+ log_debug(
184
+ f"Added {len(historical_run.images)} generated images from historical run {historical_run.run_id}"
185
+ )
186
+
187
+ # Add input images from previous runs
188
+ if historical_run.input and historical_run.input.images:
189
+ joint_images.extend(historical_run.input.images)
190
+ log_debug(
191
+ f"Added {len(historical_run.input.images)} input images from historical run {historical_run.run_id}"
192
+ )
193
+ except Exception as e:
194
+ log_debug(f"Could not access session history for images: {e}")
195
+
196
+ if joint_images:
197
+ log_debug(f"Images Available to Model: {len(joint_images)} images")
198
+ return joint_images if joint_images else None
199
+
200
+
201
+ def collect_joint_videos(
202
+ run_input: Optional[RunInput] = None,
203
+ session: Optional[Union[AgentSession, TeamSession]] = None,
204
+ ) -> Optional[Sequence[Video]]:
205
+ """Collect videos from input, session history, and current run response."""
206
+ joint_videos: List[Video] = []
207
+
208
+ # 1. Add videos from current input
209
+ if run_input and run_input.videos:
210
+ joint_videos.extend(run_input.videos)
211
+ log_debug(f"Added {len(run_input.videos)} input videos to joint list")
212
+
213
+ # 2. Add videos from session history (from both input and generated sources)
214
+ try:
215
+ if session and session.runs:
216
+ for historical_run in session.runs:
217
+ # Add generated videos from previous runs
218
+ if historical_run.videos:
219
+ joint_videos.extend(historical_run.videos)
220
+ log_debug(
221
+ f"Added {len(historical_run.videos)} generated videos from historical run {historical_run.run_id}"
222
+ )
223
+
224
+ # Add input videos from previous runs
225
+ if historical_run.input and historical_run.input.videos:
226
+ joint_videos.extend(historical_run.input.videos)
227
+ log_debug(
228
+ f"Added {len(historical_run.input.videos)} input videos from historical run {historical_run.run_id}"
229
+ )
230
+ except Exception as e:
231
+ log_debug(f"Could not access session history for videos: {e}")
232
+
233
+ if joint_videos:
234
+ log_debug(f"Videos Available to Model: {len(joint_videos)} videos")
235
+ return joint_videos if joint_videos else None
236
+
237
+
238
+ def collect_joint_audios(
239
+ run_input: Optional[RunInput] = None,
240
+ session: Optional[Union[AgentSession, TeamSession]] = None,
241
+ ) -> Optional[Sequence[Audio]]:
242
+ """Collect audios from input, session history, and current run response."""
243
+ joint_audios: List[Audio] = []
244
+
245
+ # 1. Add audios from current input
246
+ if run_input and run_input.audios:
247
+ joint_audios.extend(run_input.audios)
248
+ log_debug(f"Added {len(run_input.audios)} input audios to joint list")
249
+
250
+ # 2. Add audios from session history (from both input and generated sources)
251
+ try:
252
+ if session and session.runs:
253
+ for historical_run in session.runs:
254
+ # Add generated audios from previous runs
255
+ if historical_run.audio:
256
+ joint_audios.extend(historical_run.audio)
257
+ log_debug(
258
+ f"Added {len(historical_run.audio)} generated audios from historical run {historical_run.run_id}"
259
+ )
260
+
261
+ # Add input audios from previous runs
262
+ if historical_run.input and historical_run.input.audios:
263
+ joint_audios.extend(historical_run.input.audios)
264
+ log_debug(
265
+ f"Added {len(historical_run.input.audios)} input audios from historical run {historical_run.run_id}"
266
+ )
267
+ except Exception as e:
268
+ log_debug(f"Could not access session history for audios: {e}")
269
+
270
+ if joint_audios:
271
+ log_debug(f"Audios Available to Model: {len(joint_audios)} audios")
272
+ return joint_audios if joint_audios else None
273
+
274
+
275
+ def collect_joint_files(
276
+ run_input: Optional[RunInput] = None,
277
+ ) -> Optional[Sequence[File]]:
278
+ """Collect files from input and session history."""
279
+ from agno.utils.log import log_debug
280
+
281
+ joint_files: List[File] = []
282
+
283
+ # 1. Add files from current input
284
+ if run_input and run_input.files:
285
+ joint_files.extend(run_input.files)
286
+
287
+ # TODO: Files aren't stored in session history yet and dont have a FileArtifact
288
+
289
+ if joint_files:
290
+ log_debug(f"Files Available to Model: {len(joint_files)} files")
291
+
292
+ return joint_files if joint_files else None
293
+
294
+
295
+ def store_media_util(run_response: Union[RunOutput, TeamRunOutput], model_response: ModelResponse):
296
+ """Store media from model response in run_response for persistence"""
297
+ # Handle generated media fields from ModelResponse (generated media)
298
+ if model_response.images is not None:
299
+ for image in model_response.images:
300
+ if run_response.images is None:
301
+ run_response.images = []
302
+ run_response.images.append(image) # Generated images go to run_response.images
303
+
304
+ if model_response.videos is not None:
305
+ for video in model_response.videos:
306
+ if run_response.videos is None:
307
+ run_response.videos = []
308
+ run_response.videos.append(video) # Generated videos go to run_response.videos
309
+
310
+ if model_response.audios is not None:
311
+ for audio in model_response.audios:
312
+ if run_response.audio is None:
313
+ run_response.audio = []
314
+ run_response.audio.append(audio) # Generated audio go to run_response.audio
315
+
316
+ if model_response.files is not None:
317
+ for file in model_response.files:
318
+ if run_response.files is None:
319
+ run_response.files = []
320
+ run_response.files.append(file) # Generated files go to run_response.files
321
+
322
+
323
+ def validate_media_object_id(
324
+ images: Optional[Sequence[Image]] = None,
325
+ videos: Optional[Sequence[Video]] = None,
326
+ audios: Optional[Sequence[Audio]] = None,
327
+ files: Optional[Sequence[File]] = None,
328
+ ) -> tuple:
329
+ image_list = None
330
+ if images:
331
+ image_list = []
332
+ for img in images:
333
+ if not img.id:
334
+ from uuid import uuid4
335
+
336
+ img.id = str(uuid4())
337
+ image_list.append(img)
338
+
339
+ video_list = None
340
+ if videos:
341
+ video_list = []
342
+ for vid in videos:
343
+ if not vid.id:
344
+ from uuid import uuid4
345
+
346
+ vid.id = str(uuid4())
347
+ video_list.append(vid)
348
+
349
+ audio_list = None
350
+ if audios:
351
+ audio_list = []
352
+ for aud in audios:
353
+ if not aud.id:
354
+ from uuid import uuid4
355
+
356
+ aud.id = str(uuid4())
357
+ audio_list.append(aud)
358
+
359
+ file_list = None
360
+ if files:
361
+ file_list = []
362
+ for file in files:
363
+ if not file.id:
364
+ from uuid import uuid4
365
+
366
+ file.id = str(uuid4())
367
+ file_list.append(file)
368
+
369
+ return image_list, video_list, audio_list, file_list
370
+
371
+
372
+ def scrub_media_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
373
+ """
374
+ Completely remove all media from RunOutput when store_media=False.
375
+ This includes media in input, output artifacts, and all messages.
376
+ """
377
+ # 1. Scrub RunInput media
378
+ if run_response.input is not None:
379
+ run_response.input.images = []
380
+ run_response.input.videos = []
381
+ run_response.input.audios = []
382
+ run_response.input.files = []
383
+
384
+ # 3. Scrub media from all messages
385
+ if run_response.messages:
386
+ for message in run_response.messages:
387
+ scrub_media_from_message(message)
388
+
389
+ # 4. Scrub media from additional_input messages if any
390
+ if run_response.additional_input:
391
+ for message in run_response.additional_input:
392
+ scrub_media_from_message(message)
393
+
394
+ # 5. Scrub media from reasoning_messages if any
395
+ if run_response.reasoning_messages:
396
+ for message in run_response.reasoning_messages:
397
+ scrub_media_from_message(message)
398
+
399
+
400
+ def scrub_media_from_message(message: Message) -> None:
401
+ """Remove all media from a Message object."""
402
+ # Input media
403
+ message.images = None
404
+ message.videos = None
405
+ message.audio = None
406
+ message.files = None
407
+
408
+ # Output media
409
+ message.audio_output = None
410
+ message.image_output = None
411
+ message.video_output = None
412
+
413
+
414
+ def scrub_tool_results_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
415
+ """
416
+ Remove all tool-related data from RunOutput when store_tool_messages=False.
417
+ This removes both the tool call and its corresponding result to maintain API consistency.
418
+ """
419
+ if not run_response.messages:
420
+ return
421
+
422
+ # Step 1: Collect all tool_call_ids from tool result messages
423
+ tool_call_ids_to_remove = set()
424
+ for message in run_response.messages:
425
+ if message.role == "tool" and message.tool_call_id:
426
+ tool_call_ids_to_remove.add(message.tool_call_id)
427
+
428
+ # Step 2: Remove tool result messages (role="tool")
429
+ run_response.messages = [msg for msg in run_response.messages if msg.role != "tool"]
430
+
431
+ # Step 3: Remove assistant messages that made those tool calls
432
+ filtered_messages = []
433
+ for message in run_response.messages:
434
+ # Check if this assistant message made any of the tool calls we're removing
435
+ should_remove = False
436
+ if message.role == "assistant" and message.tool_calls:
437
+ for tool_call in message.tool_calls:
438
+ if tool_call.get("id") in tool_call_ids_to_remove:
439
+ should_remove = True
440
+ break
441
+
442
+ if not should_remove:
443
+ filtered_messages.append(message)
444
+
445
+ run_response.messages = filtered_messages
446
+
447
+
448
+ def scrub_history_messages_from_run_output(run_response: Union[RunOutput, TeamRunOutput]) -> None:
449
+ """
450
+ Remove all history messages from TeamRunOutput when store_history_messages=False.
451
+ This removes messages that were loaded from the team's memory.
452
+ """
453
+ # Remove messages with from_history=True
454
+ if run_response.messages:
455
+ run_response.messages = [msg for msg in run_response.messages if not msg.from_history]
456
+
457
+
458
+ def get_run_output_util(
459
+ entity: Union["Agent", "Team"], run_id: str, session_id: Optional[str] = None
460
+ ) -> Optional[
461
+ Union[
462
+ RunOutput,
463
+ TeamRunOutput,
464
+ ]
465
+ ]:
466
+ """
467
+ Get a RunOutput from the database.
468
+
469
+ Args:
470
+ run_id (str): The run_id to load from storage.
471
+ session_id (Optional[str]): The session_id to load from storage.
472
+ """
473
+ if session_id is not None:
474
+ if entity._has_async_db():
475
+ raise ValueError("Async database not supported for sync functions")
476
+
477
+ session = entity.get_session(session_id=session_id)
478
+ if session is not None:
479
+ run_response = session.get_run(run_id=run_id)
480
+ if run_response is not None:
481
+ return run_response # type: ignore
482
+ else:
483
+ log_warning(f"RunOutput {run_id} not found in Session {session_id}")
484
+ elif entity.cached_session is not None:
485
+ run_response = entity.cached_session.get_run(run_id=run_id)
486
+ if run_response is not None:
487
+ return run_response # type: ignore
488
+ else:
489
+ log_warning(f"RunOutput {run_id} not found in Session {entity.cached_session.session_id}")
490
+ return None
491
+ return None
492
+
493
+
494
+ async def aget_run_output_util(
495
+ entity: Union["Agent", "Team"], run_id: str, session_id: Optional[str] = None
496
+ ) -> Optional[Union[RunOutput, TeamRunOutput]]:
497
+ """
498
+ Get a RunOutput from the database.
499
+
500
+ Args:
501
+ run_id (str): The run_id to load from storage.
502
+ session_id (Optional[str]): The session_id to load from storage.
503
+ """
504
+ if session_id is not None:
505
+ session = await entity.aget_session(session_id=session_id)
506
+ if session is not None:
507
+ run_response = session.get_run(run_id=run_id)
508
+ if run_response is not None:
509
+ return run_response # type: ignore
510
+ else:
511
+ log_warning(f"RunOutput {run_id} not found in Session {session_id}")
512
+ elif entity.cached_session is not None:
513
+ run_response = entity.cached_session.get_run(run_id=run_id)
514
+ if run_response is not None:
515
+ return run_response
516
+ else:
517
+ log_warning(f"RunOutput {run_id} not found in Session {entity.cached_session.session_id}")
518
+ return None
519
+ return None
520
+
521
+
522
+ def get_last_run_output_util(
523
+ entity: Union["Agent", "Team"], session_id: Optional[str] = None
524
+ ) -> Optional[Union[RunOutput, TeamRunOutput]]:
525
+ """
526
+ Get the last run response from the database.
527
+
528
+ Args:
529
+ session_id (Optional[str]): The session_id to load from storage.
530
+
531
+ Returns:
532
+ RunOutput: The last run response from the database.
533
+ """
534
+ if session_id is not None:
535
+ if entity._has_async_db():
536
+ raise ValueError("Async database not supported for sync functions")
537
+
538
+ session = entity.get_session(session_id=session_id)
539
+ if session is not None and session.runs is not None and len(session.runs) > 0:
540
+ for run_output in reversed(session.runs):
541
+ if entity.__class__.__name__ == "Agent":
542
+ if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
543
+ return run_output # type: ignore
544
+ elif entity.__class__.__name__ == "Team":
545
+ if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
546
+ return run_output # type: ignore
547
+ else:
548
+ log_warning(f"No run responses found in Session {session_id}")
549
+
550
+ elif (
551
+ entity.cached_session is not None
552
+ and entity.cached_session.runs is not None
553
+ and len(entity.cached_session.runs) > 0
554
+ ):
555
+ for run_output in reversed(entity.cached_session.runs):
556
+ if entity.__class__.__name__ == "Agent":
557
+ if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
558
+ return run_output # type: ignore
559
+ elif entity.__class__.__name__ == "Team":
560
+ if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
561
+ return run_output # type: ignore
562
+ return None
563
+
564
+
565
+ async def aget_last_run_output_util(
566
+ entity: Union["Agent", "Team"], session_id: Optional[str] = None
567
+ ) -> Optional[Union[RunOutput, TeamRunOutput]]:
568
+ """
569
+ Get the last run response from the database.
570
+
571
+ Args:
572
+ session_id (Optional[str]): The session_id to load from storage.
573
+
574
+ Returns:
575
+ RunOutput: The last run response from the database.
576
+ """
577
+ if session_id is not None:
578
+ session = await entity.aget_session(session_id=session_id)
579
+ if session is not None and session.runs is not None and len(session.runs) > 0:
580
+ for run_output in reversed(session.runs):
581
+ if entity.__class__.__name__ == "Agent":
582
+ if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
583
+ return run_output # type: ignore
584
+ elif entity.__class__.__name__ == "Team":
585
+ if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
586
+ return run_output # type: ignore
587
+ else:
588
+ log_warning(f"No run responses found in Session {session_id}")
589
+
590
+ elif (
591
+ entity.cached_session is not None
592
+ and entity.cached_session.runs is not None
593
+ and len(entity.cached_session.runs) > 0
594
+ ):
595
+ for run_output in reversed(entity.cached_session.runs):
596
+ if entity.__class__.__name__ == "Agent":
597
+ if hasattr(run_output, "agent_id") and run_output.agent_id == entity.id:
598
+ return run_output # type: ignore
599
+ elif entity.__class__.__name__ == "Team":
600
+ if hasattr(run_output, "team_id") and run_output.team_id == entity.id:
601
+ return run_output # type: ignore
602
+ return None
603
+
604
+
605
+ def set_session_name_util(
606
+ entity: Union["Agent", "Team"], session_id: str, autogenerate: bool = False, session_name: Optional[str] = None
607
+ ) -> Union[AgentSession, TeamSession, WorkflowSession]:
608
+ """Set the session name and save to storage"""
609
+ if entity._has_async_db():
610
+ raise ValueError("Async database not supported for sync functions")
611
+
612
+ session = entity.get_session(session_id=session_id) # type: ignore
613
+
614
+ if session is None:
615
+ raise Exception("No session found")
616
+
617
+ # -*- Generate name for session
618
+ if autogenerate:
619
+ session_name = entity.generate_session_name(session=session) # type: ignore
620
+ log_debug(f"Generated Session Name: {session_name}")
621
+ elif session_name is None:
622
+ raise Exception("No session name provided")
623
+
624
+ # -*- Rename session
625
+ if session.session_data is None:
626
+ session.session_data = {"session_name": session_name}
627
+ else:
628
+ session.session_data["session_name"] = session_name
629
+ # -*- Save to storage
630
+ entity.save_session(session=session) # type: ignore
631
+
632
+ return session
633
+
634
+
635
+ async def aset_session_name_util(
636
+ entity: Union["Agent", "Team"], session_id: str, autogenerate: bool = False, session_name: Optional[str] = None
637
+ ) -> Union[AgentSession, TeamSession, WorkflowSession]:
638
+ """Set the session name and save to storage"""
639
+ session = await entity.aget_session(session_id=session_id) # type: ignore
640
+
641
+ if session is None:
642
+ raise Exception("Session not found")
643
+
644
+ # -*- Generate name for session
645
+ if autogenerate:
646
+ session_name = entity.generate_session_name(session=session) # type: ignore
647
+ log_debug(f"Generated Session Name: {session_name}")
648
+ elif session_name is None:
649
+ raise Exception("No session name provided")
650
+
651
+ # -*- Rename session
652
+ if session.session_data is None:
653
+ session.session_data = {"session_name": session_name}
654
+ else:
655
+ session.session_data["session_name"] = session_name
656
+
657
+ # -*- Save to storage
658
+ await entity.asave_session(session=session) # type: ignore
659
+
660
+ return session
661
+
662
+
663
+ def get_session_name_util(entity: Union["Agent", "Team"], session_id: str) -> str:
664
+ """Get the session name for the given session ID and user ID."""
665
+
666
+ if entity._has_async_db():
667
+ raise ValueError("Async database not supported for sync functions")
668
+
669
+ session = entity.get_session(session_id=session_id) # type: ignore
670
+ if session is None:
671
+ raise Exception("Session not found")
672
+ return session.session_data.get("session_name", "") if session.session_data is not None else "" # type: ignore
673
+
674
+
675
+ async def aget_session_name_util(entity: Union["Agent", "Team"], session_id: str) -> str:
676
+ """Get the session name for the given session ID and user ID."""
677
+ session = await entity.aget_session(session_id=session_id) # type: ignore
678
+ if session is None:
679
+ raise Exception("Session not found")
680
+ return session.session_data.get("session_name", "") if session.session_data is not None else "" # type: ignore
681
+
682
+
683
+ def get_session_state_util(entity: Union["Agent", "Team"], session_id: str) -> Dict[str, Any]:
684
+ """Get the session state for the given session ID and user ID."""
685
+ if entity._has_async_db():
686
+ raise ValueError("Async database not supported for sync functions")
687
+
688
+ session = entity.get_session(session_id=session_id) # type: ignore
689
+ if session is None:
690
+ raise Exception("Session not found")
691
+ return session.session_data.get("session_state", {}) if session.session_data is not None else {} # type: ignore
692
+
693
+
694
+ async def aget_session_state_util(entity: Union["Agent", "Team"], session_id: str) -> Dict[str, Any]:
695
+ """Get the session state for the given session ID and user ID."""
696
+ session = await entity.aget_session(session_id=session_id) # type: ignore
697
+ if session is None:
698
+ raise Exception("Session not found")
699
+ return session.session_data.get("session_state", {}) if session.session_data is not None else {} # type: ignore
700
+
701
+
702
+ def update_session_state_util(
703
+ entity: Union["Agent", "Team"], session_state_updates: Dict[str, Any], session_id: str
704
+ ) -> str:
705
+ """
706
+ Update the session state for the given session ID and user ID.
707
+ Args:
708
+ session_state_updates: The updates to apply to the session state. Should be a dictionary of key-value pairs.
709
+ session_id: The session ID to update. If not provided, the current cached session ID is used.
710
+ Returns:
711
+ dict: The updated session state.
712
+ """
713
+ if entity._has_async_db():
714
+ raise ValueError("Async database not supported for sync functions")
715
+
716
+ session = entity.get_session(session_id=session_id) # type: ignore
717
+ if session is None:
718
+ raise Exception("Session not found")
719
+
720
+ if session.session_data is not None and "session_state" not in session.session_data:
721
+ session.session_data["session_state"] = {}
722
+
723
+ for key, value in session_state_updates.items():
724
+ session.session_data["session_state"][key] = value # type: ignore
725
+
726
+ entity.save_session(session=session) # type: ignore
727
+
728
+ return session.session_data["session_state"] # type: ignore
729
+
730
+
731
+ async def aupdate_session_state_util(
732
+ entity: Union["Agent", "Team"], session_state_updates: Dict[str, Any], session_id: str
733
+ ) -> str:
734
+ """
735
+ Update the session state for the given session ID and user ID.
736
+ Args:
737
+ session_state_updates: The updates to apply to the session state. Should be a dictionary of key-value pairs.
738
+ session_id: The session ID to update. If not provided, the current cached session ID is used.
739
+ Returns:
740
+ dict: The updated session state.
741
+ """
742
+ session = await entity.aget_session(session_id=session_id) # type: ignore
743
+ if session is None:
744
+ raise Exception("Session not found")
745
+
746
+ if session.session_data is not None and "session_state" not in session.session_data:
747
+ session.session_data["session_state"] = {}
748
+
749
+ for key, value in session_state_updates.items():
750
+ session.session_data["session_state"][key] = value # type: ignore
751
+
752
+ await entity.asave_session(session=session) # type: ignore
753
+
754
+ return session.session_data["session_state"] # type: ignore
755
+
756
+
757
+ def get_session_metrics_util(entity: Union["Agent", "Team"], session_id: str) -> Optional[Metrics]:
758
+ """Get the session metrics for the given session ID and user ID."""
759
+ if entity._has_async_db():
760
+ raise ValueError("Async database not supported for sync functions")
761
+
762
+ session = entity.get_session(session_id=session_id) # type: ignore
763
+ if session is None:
764
+ raise Exception("Session not found")
765
+
766
+ if session.session_data is not None:
767
+ if isinstance(session.session_data.get("session_metrics"), dict):
768
+ return Metrics(**session.session_data.get("session_metrics", {}))
769
+ elif isinstance(session.session_data.get("session_metrics"), Metrics):
770
+ return session.session_data.get("session_metrics")
771
+ return None
772
+
773
+
774
+ async def aget_session_metrics_util(entity: Union["Agent", "Team"], session_id: str) -> Optional[Metrics]:
775
+ """Get the session metrics for the given session ID and user ID."""
776
+ session = await entity.aget_session(session_id=session_id) # type: ignore
777
+ if session is None:
778
+ raise Exception("Session not found")
779
+
780
+ if session.session_data is not None:
781
+ if isinstance(session.session_data.get("session_metrics"), dict):
782
+ return Metrics(**session.session_data.get("session_metrics", {}))
783
+ elif isinstance(session.session_data.get("session_metrics"), Metrics):
784
+ return session.session_data.get("session_metrics")
785
+ return None
786
+
787
+
788
+ def get_chat_history_util(entity: Union["Agent", "Team"], session_id: str) -> List[Message]:
789
+ """Read the chat history from the session
790
+
791
+ Args:
792
+ session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
793
+ Returns:
794
+ List[Message]: The chat history from the session.
795
+ """
796
+ if entity._has_async_db():
797
+ raise ValueError("Async database not supported for sync functions")
798
+
799
+ session = entity.get_session(session_id=session_id) # type: ignore
800
+
801
+ if session is None:
802
+ raise Exception("Session not found")
803
+
804
+ return session.get_chat_history() # type: ignore
805
+
806
+
807
+ async def aget_chat_history_util(entity: Union["Agent", "Team"], session_id: str) -> List[Message]:
808
+ """Read the chat history from the session
809
+
810
+ Args:
811
+ session_id: The session ID to get the chat history for. If not provided, the current cached session ID is used.
812
+ Returns:
813
+ List[Message]: The chat history from the session.
814
+ """
815
+ session = await entity.aget_session(session_id=session_id) # type: ignore
816
+
817
+ if session is None:
818
+ raise Exception("Session not found")
819
+
820
+ return session.get_chat_history() # type: ignore