agno 2.0.1__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 (314) hide show
  1. agno/agent/agent.py +6015 -2823
  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 +594 -186
  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 +2 -8
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +72 -0
  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 +999 -519
  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 +103 -31
  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 +139 -0
  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 +59 -5
  142. agno/models/openai/chat.py +69 -29
  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 +77 -1
  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 -178
  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 +248 -94
  205. agno/run/base.py +44 -5
  206. agno/run/team.py +238 -97
  207. agno/run/workflow.py +144 -33
  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 -1610
  213. agno/tools/dalle.py +2 -4
  214. agno/tools/decorator.py +4 -2
  215. agno/tools/duckduckgo.py +15 -11
  216. agno/tools/e2b.py +14 -7
  217. agno/tools/eleven_labs.py +23 -25
  218. agno/tools/exa.py +21 -16
  219. agno/tools/file.py +153 -23
  220. agno/tools/file_generation.py +350 -0
  221. agno/tools/firecrawl.py +4 -4
  222. agno/tools/function.py +250 -30
  223. agno/tools/gmail.py +238 -14
  224. agno/tools/google_drive.py +270 -0
  225. agno/tools/googlecalendar.py +36 -8
  226. agno/tools/googlesheets.py +20 -5
  227. agno/tools/jira.py +20 -0
  228. agno/tools/knowledge.py +3 -3
  229. agno/tools/mcp/__init__.py +10 -0
  230. agno/tools/mcp/mcp.py +331 -0
  231. agno/tools/mcp/multi_mcp.py +347 -0
  232. agno/tools/mcp/params.py +24 -0
  233. agno/tools/mcp_toolbox.py +284 -0
  234. agno/tools/mem0.py +11 -17
  235. agno/tools/memori.py +1 -53
  236. agno/tools/memory.py +419 -0
  237. agno/tools/models/nebius.py +5 -5
  238. agno/tools/models_labs.py +20 -10
  239. agno/tools/notion.py +204 -0
  240. agno/tools/parallel.py +314 -0
  241. agno/tools/scrapegraph.py +58 -31
  242. agno/tools/searxng.py +2 -2
  243. agno/tools/serper.py +2 -2
  244. agno/tools/slack.py +18 -3
  245. agno/tools/spider.py +2 -2
  246. agno/tools/tavily.py +146 -0
  247. agno/tools/whatsapp.py +1 -1
  248. agno/tools/workflow.py +278 -0
  249. agno/tools/yfinance.py +12 -11
  250. agno/utils/agent.py +820 -0
  251. agno/utils/audio.py +27 -0
  252. agno/utils/common.py +90 -1
  253. agno/utils/events.py +217 -2
  254. agno/utils/gemini.py +180 -22
  255. agno/utils/hooks.py +57 -0
  256. agno/utils/http.py +111 -0
  257. agno/utils/knowledge.py +12 -5
  258. agno/utils/log.py +1 -0
  259. agno/utils/mcp.py +92 -2
  260. agno/utils/media.py +188 -10
  261. agno/utils/merge_dict.py +22 -1
  262. agno/utils/message.py +60 -0
  263. agno/utils/models/claude.py +40 -11
  264. agno/utils/print_response/agent.py +105 -21
  265. agno/utils/print_response/team.py +103 -38
  266. agno/utils/print_response/workflow.py +251 -34
  267. agno/utils/reasoning.py +22 -1
  268. agno/utils/serialize.py +32 -0
  269. agno/utils/streamlit.py +16 -10
  270. agno/utils/string.py +41 -0
  271. agno/utils/team.py +98 -9
  272. agno/utils/tools.py +1 -1
  273. agno/vectordb/base.py +23 -4
  274. agno/vectordb/cassandra/cassandra.py +65 -9
  275. agno/vectordb/chroma/chromadb.py +182 -38
  276. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  277. agno/vectordb/couchbase/couchbase.py +105 -10
  278. agno/vectordb/lancedb/lance_db.py +124 -133
  279. agno/vectordb/langchaindb/langchaindb.py +25 -7
  280. agno/vectordb/lightrag/lightrag.py +17 -3
  281. agno/vectordb/llamaindex/__init__.py +3 -0
  282. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  283. agno/vectordb/milvus/milvus.py +126 -9
  284. agno/vectordb/mongodb/__init__.py +7 -1
  285. agno/vectordb/mongodb/mongodb.py +112 -7
  286. agno/vectordb/pgvector/pgvector.py +142 -21
  287. agno/vectordb/pineconedb/pineconedb.py +80 -8
  288. agno/vectordb/qdrant/qdrant.py +125 -39
  289. agno/vectordb/redis/__init__.py +9 -0
  290. agno/vectordb/redis/redisdb.py +694 -0
  291. agno/vectordb/singlestore/singlestore.py +111 -25
  292. agno/vectordb/surrealdb/surrealdb.py +31 -5
  293. agno/vectordb/upstashdb/upstashdb.py +76 -8
  294. agno/vectordb/weaviate/weaviate.py +86 -15
  295. agno/workflow/__init__.py +2 -0
  296. agno/workflow/agent.py +299 -0
  297. agno/workflow/condition.py +112 -18
  298. agno/workflow/loop.py +69 -10
  299. agno/workflow/parallel.py +266 -118
  300. agno/workflow/router.py +110 -17
  301. agno/workflow/step.py +638 -129
  302. agno/workflow/steps.py +65 -6
  303. agno/workflow/types.py +61 -23
  304. agno/workflow/workflow.py +2085 -272
  305. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/METADATA +182 -58
  306. agno-2.3.0.dist-info/RECORD +577 -0
  307. agno/knowledge/reader/url_reader.py +0 -128
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -610
  310. agno/utils/models/aws_claude.py +0 -170
  311. agno-2.0.1.dist-info/RECORD +0 -515
  312. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  313. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.0.1.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
agno/session/summary.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import datetime
3
3
  from textwrap import dedent
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union, cast
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
5
5
 
6
6
  from pydantic import BaseModel, Field
7
7
 
8
8
  from agno.models.base import Model
9
+ from agno.models.utils import get_model
9
10
  from agno.run.agent import Message
10
11
  from agno.utils.log import log_debug, log_warning
11
12
 
@@ -66,6 +67,9 @@ class SessionSummaryManager:
66
67
  # Prompt used for session summary generation
67
68
  session_summary_prompt: Optional[str] = None
68
69
 
70
+ # User message prompt for requesting the summary
71
+ summary_request_message: str = "Provide the summary of the conversation."
72
+
69
73
  # Whether session summaries were created in the last run
70
74
  summaries_updated: bool = False
71
75
 
@@ -90,20 +94,35 @@ class SessionSummaryManager:
90
94
  response_format: Union[Dict[str, Any], Type[BaseModel]],
91
95
  ) -> Message:
92
96
  if self.session_summary_prompt is not None:
93
- return Message(role="system", content=self.session_summary_prompt)
94
-
95
- system_prompt = dedent("""\
96
- Analyze the following conversation between a user and an assistant, and extract the following details:
97
- - Summary (str): Provide a concise summary of the session, focusing on important information that would be helpful for future interactions.
98
- - Topics (Optional[List[str]]): List the topics discussed in the session.
99
- Keep the summary concise and to the point. Only include relevant information.
100
-
101
- <conversation>
102
- """)
97
+ system_prompt = self.session_summary_prompt
98
+ else:
99
+ system_prompt = dedent("""\
100
+ Analyze the following conversation between a user and an assistant, and extract the following details:
101
+ - Summary (str): Provide a concise summary of the session, focusing on important information that would be helpful for future interactions.
102
+ - Topics (Optional[List[str]]): List the topics discussed in the session.
103
+ Keep the summary concise and to the point. Only include relevant information.
104
+ """)
103
105
  conversation_messages = []
106
+ system_prompt += "<conversation>"
104
107
  for message in conversation:
105
108
  if message.role == "user":
106
- conversation_messages.append(f"User: {message.content}")
109
+ # Handle empty user messages with media - note what media was provided
110
+ if not message.content or (isinstance(message.content, str) and message.content.strip() == ""):
111
+ media_types = []
112
+ if hasattr(message, "images") and message.images:
113
+ media_types.append(f"{len(message.images)} image(s)")
114
+ if hasattr(message, "videos") and message.videos:
115
+ media_types.append(f"{len(message.videos)} video(s)")
116
+ if hasattr(message, "audio") and message.audio:
117
+ media_types.append(f"{len(message.audio)} audio file(s)")
118
+ if hasattr(message, "files") and message.files:
119
+ media_types.append(f"{len(message.files)} file(s)")
120
+
121
+ if media_types:
122
+ conversation_messages.append(f"User: [Provided {', '.join(media_types)}]")
123
+ # Skip empty messages with no media
124
+ else:
125
+ conversation_messages.append(f"User: {message.content}")
107
126
  elif message.role in ["assistant", "model"]:
108
127
  conversation_messages.append(f"Assistant: {message.content}\n")
109
128
  system_prompt += "\n".join(conversation_messages)
@@ -119,23 +138,30 @@ class SessionSummaryManager:
119
138
  def _prepare_summary_messages(
120
139
  self,
121
140
  session: Optional["Session"] = None,
122
- ) -> List[Message]:
123
- """Prepare messages for session summary generation"""
124
- self.model = cast(Model, self.model)
141
+ ) -> Optional[List[Message]]:
142
+ """Prepare messages for session summary generation. Returns None if no meaningful messages to summarize."""
143
+ if not session:
144
+ return None
145
+
146
+ self.model = get_model(self.model)
147
+ if self.model is None:
148
+ return None
149
+
125
150
  response_format = self.get_response_format(self.model)
126
151
 
127
- return (
128
- [
129
- self.get_system_message(
130
- conversation=session.get_messages_for_session(), # type: ignore
131
- response_format=response_format,
132
- ),
133
- Message(role="user", content="Provide the summary of the conversation."),
134
- ]
135
- if session
136
- else []
152
+ system_message = self.get_system_message(
153
+ conversation=session.get_messages(), # type: ignore
154
+ response_format=response_format,
137
155
  )
138
156
 
157
+ if system_message is None:
158
+ return None
159
+
160
+ return [
161
+ system_message,
162
+ Message(role="user", content=self.summary_request_message),
163
+ ]
164
+
139
165
  def _process_summary_response(self, summary_response, session_summary_model: "Model") -> Optional[SessionSummary]: # type: ignore
140
166
  """Process the model response into a SessionSummary"""
141
167
  from datetime import datetime
@@ -188,10 +214,17 @@ class SessionSummaryManager:
188
214
  ) -> Optional[SessionSummary]:
189
215
  """Creates a summary of the session"""
190
216
  log_debug("Creating session summary", center=True)
217
+ self.model = get_model(self.model)
191
218
  if self.model is None:
192
219
  return None
193
220
 
194
221
  messages = self._prepare_summary_messages(session)
222
+
223
+ # Skip summary generation if there are no meaningful messages
224
+ if messages is None:
225
+ log_debug("No meaningful messages to summarize, skipping session summary")
226
+ return None
227
+
195
228
  response_format = self.get_response_format(self.model)
196
229
 
197
230
  summary_response = self.model.response(messages=messages, response_format=response_format)
@@ -209,10 +242,17 @@ class SessionSummaryManager:
209
242
  ) -> Optional[SessionSummary]:
210
243
  """Creates a summary of the session"""
211
244
  log_debug("Creating session summary", center=True)
245
+ self.model = get_model(self.model)
212
246
  if self.model is None:
213
247
  return None
214
248
 
215
249
  messages = self._prepare_summary_messages(session)
250
+
251
+ # Skip summary generation if there are no meaningful messages
252
+ if messages is None:
253
+ log_debug("No meaningful messages to summarize, skipping session summary")
254
+ return None
255
+
216
256
  response_format = self.get_response_format(self.model)
217
257
 
218
258
  summary_response = await self.model.aresponse(messages=messages, response_format=response_format)
agno/session/team.py CHANGED
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import asdict, dataclass
4
- from typing import Any, Dict, List, Mapping, Optional, Union
4
+ from typing import Any, Dict, List, Mapping, Optional, Tuple, Union
5
+
6
+ from pydantic import BaseModel
5
7
 
6
8
  from agno.models.message import Message
7
9
  from agno.run.agent import RunOutput, RunStatus
@@ -54,16 +56,18 @@ class TeamSession:
54
56
  log_warning("TeamSession is missing session_id")
55
57
  return None
56
58
 
57
- if data.get("summary") is not None:
59
+ summary = data.get("summary")
60
+ if summary is not None and isinstance(summary, dict):
58
61
  data["summary"] = SessionSummary.from_dict(data["summary"]) # type: ignore
59
62
 
60
- runs = data.get("runs", [])
63
+ runs = data.get("runs")
61
64
  serialized_runs: List[Union[TeamRunOutput, RunOutput]] = []
62
- for run in runs:
63
- if "agent_id" in run:
64
- serialized_runs.append(RunOutput.from_dict(run))
65
- elif "team_id" in run:
66
- serialized_runs.append(TeamRunOutput.from_dict(run))
65
+ if runs is not None and isinstance(runs[0], dict):
66
+ for run in runs:
67
+ if "agent_id" in run:
68
+ serialized_runs.append(RunOutput.from_dict(run))
69
+ elif "team_id" in run:
70
+ serialized_runs.append(TeamRunOutput.from_dict(run))
67
71
 
68
72
  return cls(
69
73
  session_id=data.get("session_id"), # type: ignore
@@ -92,6 +96,7 @@ class TeamSession:
92
96
  if messages is None:
93
97
  return
94
98
 
99
+ # Make message duration None
95
100
  for m in messages or []:
96
101
  if m.metrics is not None:
97
102
  m.metrics.duration = None
@@ -108,74 +113,136 @@ class TeamSession:
108
113
 
109
114
  log_debug("Added RunOutput to Team Session")
110
115
 
111
- def get_messages_from_last_n_runs(
116
+ def get_messages(
112
117
  self,
113
- agent_id: Optional[str] = None,
114
118
  team_id: Optional[str] = None,
115
- last_n: Optional[int] = None,
116
- skip_role: Optional[str] = None,
117
- skip_status: Optional[List[RunStatus]] = None,
119
+ member_ids: Optional[List[str]] = None,
120
+ last_n_runs: Optional[int] = None,
121
+ limit: Optional[int] = None,
122
+ skip_roles: Optional[List[str]] = None,
123
+ skip_statuses: Optional[List[RunStatus]] = None,
118
124
  skip_history_messages: bool = True,
119
- member_runs: bool = False,
125
+ skip_member_messages: bool = True,
120
126
  ) -> List[Message]:
121
- """Returns the messages from the last_n runs, excluding previously tagged history messages.
122
- Args:
127
+ """Returns the messages belonging to the session that fit the given criteria.
123
128
 
124
- agent_id: The id of the agent to get the messages from.
129
+ Args:
125
130
  team_id: The id of the team to get the messages from.
126
- last_n: The number of runs to return from the end of the conversation. Defaults to all runs.
127
- skip_role: Skip messages with this role.
128
- skip_status: Skip messages with this status.
131
+ member_ids: The ids of the members to get the messages from.
132
+ last_n_runs: The number of runs to return messages from, counting from the latest. Defaults to all runs.
133
+ limit: The number of messages to return, counting from the latest. Defaults to all messages.
134
+ skip_roles: Skip messages with these roles.
135
+ skip_statuses: Skip messages with these statuses.
129
136
  skip_history_messages: Skip messages that were tagged as history in previous runs.
137
+ skip_member_messages: Skip messages created by members of the team.
138
+
130
139
  Returns:
131
- A list of Messages from the specified runs, excluding history messages.
140
+ A list of Messages belonging to the session.
132
141
  """
142
+
143
+ def _should_skip_message(
144
+ message: Message, skip_roles: Optional[List[str]] = None, skip_history_messages: bool = True
145
+ ) -> bool:
146
+ """Processes a message for history"""
147
+ # Skip messages that were tagged as history in previous runs
148
+ if hasattr(message, "from_history") and message.from_history and skip_history_messages:
149
+ return True
150
+
151
+ # Skip messages with specified role
152
+ if skip_roles and message.role in skip_roles:
153
+ return True
154
+ return False
155
+
156
+ if member_ids is not None and skip_member_messages:
157
+ log_debug("Member IDs to filter by were provided. The skip_member_messages flag will be ignored.")
158
+ skip_member_messages = False
159
+
133
160
  if not self.runs:
134
161
  return []
135
162
 
136
- if skip_status is None:
137
- skip_status = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
163
+ if skip_statuses is None:
164
+ skip_statuses = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
138
165
 
139
166
  session_runs = self.runs
140
- # Filter by agent_id and team_id
141
- if agent_id:
142
- session_runs = [run for run in session_runs if hasattr(run, "agent_id") and run.agent_id == agent_id] # type: ignore
167
+
168
+ # Filter by team_id and member_ids
143
169
  if team_id:
144
170
  session_runs = [run for run in session_runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
171
+ if member_ids:
172
+ session_runs = [run for run in session_runs if hasattr(run, "agent_id") and run.agent_id in member_ids] # type: ignore
145
173
 
146
- if not member_runs:
147
- # Filter for the main team runs
174
+ if skip_member_messages:
175
+ # Filter for the top-level runs (main team runs or agent runs when sharing session)
148
176
  session_runs = [run for run in session_runs if run.parent_run_id is None] # type: ignore
149
177
 
150
178
  # Filter by status
151
- session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_status] # type: ignore
179
+ session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_statuses] # type: ignore
152
180
 
153
- # Filter by last_n
154
- runs_to_process = session_runs[-last_n:] if last_n is not None else session_runs
155
181
  messages_from_history = []
156
182
  system_message = None
157
- for run_response in runs_to_process:
158
- if not (run_response and run_response.messages):
159
- continue
160
183
 
161
- for message in run_response.messages or []:
162
- # Skip messages with specified role
163
- if skip_role and message.role == skip_role:
184
+ # Limit the number of messages returned if limit is set
185
+ if limit is not None:
186
+ for run_response in session_runs:
187
+ if not run_response or not run_response.messages:
164
188
  continue
165
- # Skip messages that were tagged as history in previous runs
166
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
189
+
190
+ for message in run_response.messages or []:
191
+ if _should_skip_message(message, skip_roles, skip_history_messages):
192
+ continue
193
+
194
+ if message.role == "system":
195
+ # Only add the system message once
196
+ if system_message is None:
197
+ system_message = message
198
+ else:
199
+ messages_from_history.append(message)
200
+
201
+ if system_message:
202
+ messages_from_history = [system_message] + messages_from_history[
203
+ -(limit - 1) :
204
+ ] # Grab one less message then add the system message
205
+ else:
206
+ messages_from_history = messages_from_history[-limit:]
207
+
208
+ # Remove tool result messages that don't have an associated assistant message with tool calls
209
+ while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
210
+ messages_from_history.pop(0)
211
+ else:
212
+ # Filter by last_n runs
213
+ runs_to_process = session_runs[-last_n_runs:] if last_n_runs is not None else session_runs
214
+
215
+ for run_response in runs_to_process:
216
+ if not (run_response and run_response.messages):
167
217
  continue
168
- if message.role == "system":
169
- # Only add the system message once
170
- if system_message is None:
171
- system_message = message
172
- messages_from_history.append(system_message)
173
- else:
174
- messages_from_history.append(message)
218
+
219
+ for message in run_response.messages or []:
220
+ if _should_skip_message(message, skip_roles, skip_history_messages):
221
+ continue
222
+
223
+ if message.role == "system":
224
+ # Only add the system message once
225
+ if system_message is None:
226
+ system_message = message
227
+ messages_from_history.append(system_message)
228
+ else:
229
+ messages_from_history.append(message)
175
230
 
176
231
  log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
177
232
  return messages_from_history
178
233
 
234
+ def get_chat_history(self, last_n_runs: Optional[int] = None) -> List[Message]:
235
+ """Return the chat history (user and assistant messages) for the session.
236
+ Use get_messages() for more filtering options.
237
+
238
+ Args:
239
+ last_n_runs: Number of recent runs to include. If None, all runs will be considered.
240
+
241
+ Returns:
242
+ A list of user and assistant Messages belonging to the session.
243
+ """
244
+ return self.get_messages(skip_roles=["system", "tool"], skip_member_messages=True, last_n_runs=last_n_runs)
245
+
179
246
  def get_tool_calls(self, num_calls: Optional[int] = None) -> List[Dict[str, Any]]:
180
247
  """Returns a list of tool calls from the messages"""
181
248
 
@@ -194,69 +261,82 @@ class TeamSession:
194
261
  return tool_calls
195
262
  return tool_calls
196
263
 
197
- def get_messages_for_session(
198
- self,
199
- user_role: str = "user",
200
- assistant_role: Optional[List[str]] = None,
201
- skip_history_messages: bool = True,
202
- ) -> List[Message]:
203
- """Returns a list of messages for the session that iterate through user message and assistant response."""
264
+ def get_team_history(self, num_runs: Optional[int] = None) -> List[Tuple[str, str]]:
265
+ """Get team history as structured data (input, response pairs) -> This is the history of the team leader, not the members.
204
266
 
205
- if assistant_role is None:
206
- # TODO: Check if we still need CHATBOT as a role
207
- assistant_role = ["assistant", "model", "CHATBOT"]
267
+ Args:
268
+ num_runs: Number of recent runs to include. If None, returns all available history.
269
+ """
270
+ if not self.runs:
271
+ return []
208
272
 
209
- final_messages: List[Message] = []
210
- session_runs = self.runs
211
- if session_runs is None:
273
+ from agno.run.base import RunStatus
274
+
275
+ # Get completed runs only (exclude current/pending run)
276
+ completed_runs = [run for run in self.runs if run.status == RunStatus.completed and run.parent_run_id is None]
277
+
278
+ if num_runs is not None and len(completed_runs) > num_runs:
279
+ recent_runs = completed_runs[-num_runs:]
280
+ else:
281
+ recent_runs = completed_runs
282
+
283
+ if not recent_runs:
212
284
  return []
213
285
 
214
- for run_response in session_runs:
215
- if run_response and run_response.messages:
216
- user_message_from_run = None
217
- assistant_message_from_run = None
286
+ # Return structured data as list of (input, response) tuples
287
+ history_data = []
288
+ for run in recent_runs:
289
+ # Get input
290
+ input_str = ""
291
+ if run.input:
292
+ input_str = run.input.input_content_string()
218
293
 
219
- # Start from the beginning to look for the user message
220
- for message in run_response.messages or []:
221
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
222
- continue
223
- if message.role == user_role:
224
- user_message_from_run = message
225
- break
294
+ # Get response
295
+ response_str = ""
296
+ if run.content:
297
+ response_str = (
298
+ run.content.model_dump_json(indent=2, exclude_none=True)
299
+ if isinstance(run.content, BaseModel)
300
+ else str(run.content)
301
+ )
226
302
 
227
- # Start from the end to look for the assistant response
228
- for message in run_response.messages[::-1]:
229
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
230
- continue
231
- if message.role in assistant_role:
232
- assistant_message_from_run = message
233
- break
303
+ history_data.append((input_str, response_str))
234
304
 
235
- if user_message_from_run and assistant_message_from_run:
236
- final_messages.append(user_message_from_run)
237
- final_messages.append(assistant_message_from_run)
238
- return final_messages
305
+ return history_data
239
306
 
240
- def get_session_summary(self) -> Optional[SessionSummary]:
241
- """Get the session summary for the session"""
307
+ def get_team_history_context(self, num_runs: Optional[int] = None) -> Optional[str]:
308
+ """Get formatted team history context for steps
242
309
 
243
- if self.summary is None:
310
+ Args:
311
+ num_runs: Number of recent runs to include. If None, returns all available history.
312
+ """
313
+ history_data = self.get_team_history(num_runs)
314
+
315
+ if not history_data:
244
316
  return None
245
317
 
246
- return self.summary # type: ignore
318
+ # Format as team history context using the structured data
319
+ context_parts = ["<team_history_context>"]
247
320
 
248
- # Chat History functions
249
- def get_chat_history(self) -> List[Message]:
250
- """Get the chat history for the session"""
321
+ for i, (input_str, response_str) in enumerate(history_data, 1):
322
+ context_parts.append(f"[run-{i}]")
251
323
 
252
- messages = []
253
- if self.runs is None:
254
- return []
324
+ if input_str:
325
+ context_parts.append(f"input: {input_str}")
326
+ if response_str:
327
+ context_parts.append(f"response: {response_str}")
255
328
 
256
- for run in self.runs or []:
257
- if run.messages is None:
258
- continue
329
+ context_parts.append("") # Empty line between runs
330
+
331
+ context_parts.append("</team_history_context>")
332
+ context_parts.append("") # Empty line before current input
259
333
 
260
- messages.extend([msg for msg in run.messages or [] if not msg.from_history])
334
+ return "\n".join(context_parts)
261
335
 
262
- return messages
336
+ def get_session_summary(self) -> Optional[SessionSummary]:
337
+ """Get the session summary for the session"""
338
+
339
+ if self.summary is None:
340
+ return None
341
+
342
+ return self.summary # type: ignore