agno 2.1.2__py3-none-any.whl → 2.3.13__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 +5540 -2273
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/compression/__init__.py +3 -0
  5. agno/compression/manager.py +247 -0
  6. agno/culture/__init__.py +3 -0
  7. agno/culture/manager.py +956 -0
  8. agno/db/async_postgres/__init__.py +3 -0
  9. agno/db/base.py +689 -6
  10. agno/db/dynamo/dynamo.py +933 -37
  11. agno/db/dynamo/schemas.py +174 -10
  12. agno/db/dynamo/utils.py +63 -4
  13. agno/db/firestore/firestore.py +831 -9
  14. agno/db/firestore/schemas.py +51 -0
  15. agno/db/firestore/utils.py +102 -4
  16. agno/db/gcs_json/gcs_json_db.py +660 -12
  17. agno/db/gcs_json/utils.py +60 -26
  18. agno/db/in_memory/in_memory_db.py +287 -14
  19. agno/db/in_memory/utils.py +60 -2
  20. agno/db/json/json_db.py +590 -14
  21. agno/db/json/utils.py +60 -26
  22. agno/db/migrations/manager.py +199 -0
  23. agno/db/migrations/v1_to_v2.py +43 -13
  24. agno/db/migrations/versions/__init__.py +0 -0
  25. agno/db/migrations/versions/v2_3_0.py +938 -0
  26. agno/db/mongo/__init__.py +15 -1
  27. agno/db/mongo/async_mongo.py +2760 -0
  28. agno/db/mongo/mongo.py +879 -11
  29. agno/db/mongo/schemas.py +42 -0
  30. agno/db/mongo/utils.py +80 -8
  31. agno/db/mysql/__init__.py +2 -1
  32. agno/db/mysql/async_mysql.py +2912 -0
  33. agno/db/mysql/mysql.py +946 -68
  34. agno/db/mysql/schemas.py +72 -10
  35. agno/db/mysql/utils.py +198 -7
  36. agno/db/postgres/__init__.py +2 -1
  37. agno/db/postgres/async_postgres.py +2579 -0
  38. agno/db/postgres/postgres.py +942 -57
  39. agno/db/postgres/schemas.py +81 -18
  40. agno/db/postgres/utils.py +164 -2
  41. agno/db/redis/redis.py +671 -7
  42. agno/db/redis/schemas.py +50 -0
  43. agno/db/redis/utils.py +65 -7
  44. agno/db/schemas/__init__.py +2 -1
  45. agno/db/schemas/culture.py +120 -0
  46. agno/db/schemas/evals.py +1 -0
  47. agno/db/schemas/memory.py +17 -2
  48. agno/db/singlestore/schemas.py +63 -0
  49. agno/db/singlestore/singlestore.py +949 -83
  50. agno/db/singlestore/utils.py +60 -2
  51. agno/db/sqlite/__init__.py +2 -1
  52. agno/db/sqlite/async_sqlite.py +2911 -0
  53. agno/db/sqlite/schemas.py +62 -0
  54. agno/db/sqlite/sqlite.py +965 -46
  55. agno/db/sqlite/utils.py +169 -8
  56. agno/db/surrealdb/__init__.py +3 -0
  57. agno/db/surrealdb/metrics.py +292 -0
  58. agno/db/surrealdb/models.py +334 -0
  59. agno/db/surrealdb/queries.py +71 -0
  60. agno/db/surrealdb/surrealdb.py +1908 -0
  61. agno/db/surrealdb/utils.py +147 -0
  62. agno/db/utils.py +2 -0
  63. agno/eval/__init__.py +10 -0
  64. agno/eval/accuracy.py +75 -55
  65. agno/eval/agent_as_judge.py +861 -0
  66. agno/eval/base.py +29 -0
  67. agno/eval/performance.py +16 -7
  68. agno/eval/reliability.py +28 -16
  69. agno/eval/utils.py +35 -17
  70. agno/exceptions.py +27 -2
  71. agno/filters.py +354 -0
  72. agno/guardrails/prompt_injection.py +1 -0
  73. agno/hooks/__init__.py +3 -0
  74. agno/hooks/decorator.py +164 -0
  75. agno/integrations/discord/client.py +1 -1
  76. agno/knowledge/chunking/agentic.py +13 -10
  77. agno/knowledge/chunking/fixed.py +4 -1
  78. agno/knowledge/chunking/semantic.py +9 -4
  79. agno/knowledge/chunking/strategy.py +59 -15
  80. agno/knowledge/embedder/fastembed.py +1 -1
  81. agno/knowledge/embedder/nebius.py +1 -1
  82. agno/knowledge/embedder/ollama.py +8 -0
  83. agno/knowledge/embedder/openai.py +8 -8
  84. agno/knowledge/embedder/sentence_transformer.py +6 -2
  85. agno/knowledge/embedder/vllm.py +262 -0
  86. agno/knowledge/knowledge.py +1618 -318
  87. agno/knowledge/reader/base.py +6 -2
  88. agno/knowledge/reader/csv_reader.py +8 -10
  89. agno/knowledge/reader/docx_reader.py +5 -6
  90. agno/knowledge/reader/field_labeled_csv_reader.py +16 -20
  91. agno/knowledge/reader/json_reader.py +5 -4
  92. agno/knowledge/reader/markdown_reader.py +8 -8
  93. agno/knowledge/reader/pdf_reader.py +17 -19
  94. agno/knowledge/reader/pptx_reader.py +101 -0
  95. agno/knowledge/reader/reader_factory.py +32 -3
  96. agno/knowledge/reader/s3_reader.py +3 -3
  97. agno/knowledge/reader/tavily_reader.py +193 -0
  98. agno/knowledge/reader/text_reader.py +22 -10
  99. agno/knowledge/reader/web_search_reader.py +1 -48
  100. agno/knowledge/reader/website_reader.py +10 -10
  101. agno/knowledge/reader/wikipedia_reader.py +33 -1
  102. agno/knowledge/types.py +1 -0
  103. agno/knowledge/utils.py +72 -7
  104. agno/media.py +22 -6
  105. agno/memory/__init__.py +14 -1
  106. agno/memory/manager.py +544 -83
  107. agno/memory/strategies/__init__.py +15 -0
  108. agno/memory/strategies/base.py +66 -0
  109. agno/memory/strategies/summarize.py +196 -0
  110. agno/memory/strategies/types.py +37 -0
  111. agno/models/aimlapi/aimlapi.py +17 -0
  112. agno/models/anthropic/claude.py +515 -40
  113. agno/models/aws/bedrock.py +102 -21
  114. agno/models/aws/claude.py +131 -274
  115. agno/models/azure/ai_foundry.py +41 -19
  116. agno/models/azure/openai_chat.py +39 -8
  117. agno/models/base.py +1249 -525
  118. agno/models/cerebras/cerebras.py +91 -21
  119. agno/models/cerebras/cerebras_openai.py +21 -2
  120. agno/models/cohere/chat.py +40 -6
  121. agno/models/cometapi/cometapi.py +18 -1
  122. agno/models/dashscope/dashscope.py +2 -3
  123. agno/models/deepinfra/deepinfra.py +18 -1
  124. agno/models/deepseek/deepseek.py +69 -3
  125. agno/models/fireworks/fireworks.py +18 -1
  126. agno/models/google/gemini.py +877 -80
  127. agno/models/google/utils.py +22 -0
  128. agno/models/groq/groq.py +51 -18
  129. agno/models/huggingface/huggingface.py +17 -6
  130. agno/models/ibm/watsonx.py +16 -6
  131. agno/models/internlm/internlm.py +18 -1
  132. agno/models/langdb/langdb.py +13 -1
  133. agno/models/litellm/chat.py +44 -9
  134. agno/models/litellm/litellm_openai.py +18 -1
  135. agno/models/message.py +28 -5
  136. agno/models/meta/llama.py +47 -14
  137. agno/models/meta/llama_openai.py +22 -17
  138. agno/models/mistral/mistral.py +8 -4
  139. agno/models/nebius/nebius.py +6 -7
  140. agno/models/nvidia/nvidia.py +20 -3
  141. agno/models/ollama/chat.py +24 -8
  142. agno/models/openai/chat.py +104 -29
  143. agno/models/openai/responses.py +101 -81
  144. agno/models/openrouter/openrouter.py +60 -3
  145. agno/models/perplexity/perplexity.py +17 -1
  146. agno/models/portkey/portkey.py +7 -6
  147. agno/models/requesty/requesty.py +24 -4
  148. agno/models/response.py +73 -2
  149. agno/models/sambanova/sambanova.py +20 -3
  150. agno/models/siliconflow/siliconflow.py +19 -2
  151. agno/models/together/together.py +20 -3
  152. agno/models/utils.py +254 -8
  153. agno/models/vercel/v0.py +20 -3
  154. agno/models/vertexai/__init__.py +0 -0
  155. agno/models/vertexai/claude.py +190 -0
  156. agno/models/vllm/vllm.py +19 -14
  157. agno/models/xai/xai.py +19 -2
  158. agno/os/app.py +549 -152
  159. agno/os/auth.py +190 -3
  160. agno/os/config.py +23 -0
  161. agno/os/interfaces/a2a/router.py +8 -11
  162. agno/os/interfaces/a2a/utils.py +1 -1
  163. agno/os/interfaces/agui/router.py +18 -3
  164. agno/os/interfaces/agui/utils.py +152 -39
  165. agno/os/interfaces/slack/router.py +55 -37
  166. agno/os/interfaces/slack/slack.py +9 -1
  167. agno/os/interfaces/whatsapp/router.py +0 -1
  168. agno/os/interfaces/whatsapp/security.py +3 -1
  169. agno/os/mcp.py +110 -52
  170. agno/os/middleware/__init__.py +2 -0
  171. agno/os/middleware/jwt.py +676 -112
  172. agno/os/router.py +40 -1478
  173. agno/os/routers/agents/__init__.py +3 -0
  174. agno/os/routers/agents/router.py +599 -0
  175. agno/os/routers/agents/schema.py +261 -0
  176. agno/os/routers/evals/evals.py +96 -39
  177. agno/os/routers/evals/schemas.py +65 -33
  178. agno/os/routers/evals/utils.py +80 -10
  179. agno/os/routers/health.py +10 -4
  180. agno/os/routers/knowledge/knowledge.py +196 -38
  181. agno/os/routers/knowledge/schemas.py +82 -22
  182. agno/os/routers/memory/memory.py +279 -52
  183. agno/os/routers/memory/schemas.py +46 -17
  184. agno/os/routers/metrics/metrics.py +20 -8
  185. agno/os/routers/metrics/schemas.py +16 -16
  186. agno/os/routers/session/session.py +462 -34
  187. agno/os/routers/teams/__init__.py +3 -0
  188. agno/os/routers/teams/router.py +512 -0
  189. agno/os/routers/teams/schema.py +257 -0
  190. agno/os/routers/traces/__init__.py +3 -0
  191. agno/os/routers/traces/schemas.py +414 -0
  192. agno/os/routers/traces/traces.py +499 -0
  193. agno/os/routers/workflows/__init__.py +3 -0
  194. agno/os/routers/workflows/router.py +624 -0
  195. agno/os/routers/workflows/schema.py +75 -0
  196. agno/os/schema.py +256 -693
  197. agno/os/scopes.py +469 -0
  198. agno/os/utils.py +514 -36
  199. agno/reasoning/anthropic.py +80 -0
  200. agno/reasoning/gemini.py +73 -0
  201. agno/reasoning/openai.py +5 -0
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +155 -32
  205. agno/run/base.py +55 -3
  206. agno/run/requirement.py +181 -0
  207. agno/run/team.py +125 -38
  208. agno/run/workflow.py +72 -18
  209. agno/session/agent.py +102 -89
  210. agno/session/summary.py +56 -15
  211. agno/session/team.py +164 -90
  212. agno/session/workflow.py +405 -40
  213. agno/table.py +10 -0
  214. agno/team/team.py +3974 -1903
  215. agno/tools/dalle.py +2 -4
  216. agno/tools/eleven_labs.py +23 -25
  217. agno/tools/exa.py +21 -16
  218. agno/tools/file.py +153 -23
  219. agno/tools/file_generation.py +16 -10
  220. agno/tools/firecrawl.py +15 -7
  221. agno/tools/function.py +193 -38
  222. agno/tools/gmail.py +238 -14
  223. agno/tools/google_drive.py +271 -0
  224. agno/tools/googlecalendar.py +36 -8
  225. agno/tools/googlesheets.py +20 -5
  226. agno/tools/jira.py +20 -0
  227. agno/tools/mcp/__init__.py +10 -0
  228. agno/tools/mcp/mcp.py +331 -0
  229. agno/tools/mcp/multi_mcp.py +347 -0
  230. agno/tools/mcp/params.py +24 -0
  231. agno/tools/mcp_toolbox.py +3 -3
  232. agno/tools/models/nebius.py +5 -5
  233. agno/tools/models_labs.py +20 -10
  234. agno/tools/nano_banana.py +151 -0
  235. agno/tools/notion.py +204 -0
  236. agno/tools/parallel.py +314 -0
  237. agno/tools/postgres.py +76 -36
  238. agno/tools/redshift.py +406 -0
  239. agno/tools/scrapegraph.py +1 -1
  240. agno/tools/shopify.py +1519 -0
  241. agno/tools/slack.py +18 -3
  242. agno/tools/spotify.py +919 -0
  243. agno/tools/tavily.py +146 -0
  244. agno/tools/toolkit.py +25 -0
  245. agno/tools/workflow.py +8 -1
  246. agno/tools/yfinance.py +12 -11
  247. agno/tracing/__init__.py +12 -0
  248. agno/tracing/exporter.py +157 -0
  249. agno/tracing/schemas.py +276 -0
  250. agno/tracing/setup.py +111 -0
  251. agno/utils/agent.py +938 -0
  252. agno/utils/cryptography.py +22 -0
  253. agno/utils/dttm.py +33 -0
  254. agno/utils/events.py +151 -3
  255. agno/utils/gemini.py +15 -5
  256. agno/utils/hooks.py +118 -4
  257. agno/utils/http.py +113 -2
  258. agno/utils/knowledge.py +12 -5
  259. agno/utils/log.py +1 -0
  260. agno/utils/mcp.py +92 -2
  261. agno/utils/media.py +187 -1
  262. agno/utils/merge_dict.py +3 -3
  263. agno/utils/message.py +60 -0
  264. agno/utils/models/ai_foundry.py +9 -2
  265. agno/utils/models/claude.py +49 -14
  266. agno/utils/models/cohere.py +9 -2
  267. agno/utils/models/llama.py +9 -2
  268. agno/utils/models/mistral.py +4 -2
  269. agno/utils/print_response/agent.py +109 -16
  270. agno/utils/print_response/team.py +223 -30
  271. agno/utils/print_response/workflow.py +251 -34
  272. agno/utils/streamlit.py +1 -1
  273. agno/utils/team.py +98 -9
  274. agno/utils/tokens.py +657 -0
  275. agno/vectordb/base.py +39 -7
  276. agno/vectordb/cassandra/cassandra.py +21 -5
  277. agno/vectordb/chroma/chromadb.py +43 -12
  278. agno/vectordb/clickhouse/clickhousedb.py +21 -5
  279. agno/vectordb/couchbase/couchbase.py +29 -5
  280. agno/vectordb/lancedb/lance_db.py +92 -181
  281. agno/vectordb/langchaindb/langchaindb.py +24 -4
  282. agno/vectordb/lightrag/lightrag.py +17 -3
  283. agno/vectordb/llamaindex/llamaindexdb.py +25 -5
  284. agno/vectordb/milvus/milvus.py +50 -37
  285. agno/vectordb/mongodb/__init__.py +7 -1
  286. agno/vectordb/mongodb/mongodb.py +36 -30
  287. agno/vectordb/pgvector/pgvector.py +201 -77
  288. agno/vectordb/pineconedb/pineconedb.py +41 -23
  289. agno/vectordb/qdrant/qdrant.py +67 -54
  290. agno/vectordb/redis/__init__.py +9 -0
  291. agno/vectordb/redis/redisdb.py +682 -0
  292. agno/vectordb/singlestore/singlestore.py +50 -29
  293. agno/vectordb/surrealdb/surrealdb.py +31 -41
  294. agno/vectordb/upstashdb/upstashdb.py +34 -6
  295. agno/vectordb/weaviate/weaviate.py +53 -14
  296. agno/workflow/__init__.py +2 -0
  297. agno/workflow/agent.py +299 -0
  298. agno/workflow/condition.py +120 -18
  299. agno/workflow/loop.py +77 -10
  300. agno/workflow/parallel.py +231 -143
  301. agno/workflow/router.py +118 -17
  302. agno/workflow/step.py +609 -170
  303. agno/workflow/steps.py +73 -6
  304. agno/workflow/types.py +96 -21
  305. agno/workflow/workflow.py +2039 -262
  306. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
  307. agno-2.3.13.dist-info/RECORD +613 -0
  308. agno/tools/googlesearch.py +0 -98
  309. agno/tools/mcp.py +0 -679
  310. agno/tools/memori.py +0 -339
  311. agno-2.1.2.dist-info/RECORD +0 -543
  312. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
  313. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
  314. {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/run/workflow.py CHANGED
@@ -6,9 +6,15 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from agno.media import Audio, Image, Video
9
- from agno.run.agent import RunOutput
9
+ from agno.run.agent import RunEvent, RunOutput, run_output_event_from_dict
10
10
  from agno.run.base import BaseRunOutputEvent, RunStatus
11
- from agno.run.team import TeamRunOutput
11
+ from agno.run.team import TeamRunEvent, TeamRunOutput, team_run_output_event_from_dict
12
+ from agno.utils.media import (
13
+ reconstruct_audio_list,
14
+ reconstruct_images,
15
+ reconstruct_response_audio,
16
+ reconstruct_videos,
17
+ )
12
18
 
13
19
  if TYPE_CHECKING:
14
20
  from agno.workflow.types import StepOutput, WorkflowMetrics
@@ -25,6 +31,9 @@ class WorkflowRunEvent(str, Enum):
25
31
  workflow_cancelled = "WorkflowCancelled"
26
32
  workflow_error = "WorkflowError"
27
33
 
34
+ workflow_agent_started = "WorkflowAgentStarted"
35
+ workflow_agent_completed = "WorkflowAgentCompleted"
36
+
28
37
  step_started = "StepStarted"
29
38
  step_completed = "StepCompleted"
30
39
  step_error = "StepError"
@@ -120,6 +129,21 @@ class WorkflowStartedEvent(BaseWorkflowRunOutputEvent):
120
129
  event: str = WorkflowRunEvent.workflow_started.value
121
130
 
122
131
 
132
+ @dataclass
133
+ class WorkflowAgentStartedEvent(BaseWorkflowRunOutputEvent):
134
+ """Event sent when workflow agent starts (before deciding to run workflow or answer directly)"""
135
+
136
+ event: str = WorkflowRunEvent.workflow_agent_started.value
137
+
138
+
139
+ @dataclass
140
+ class WorkflowAgentCompletedEvent(BaseWorkflowRunOutputEvent):
141
+ """Event sent when workflow agent completes (after running workflow or answering directly)"""
142
+
143
+ event: str = WorkflowRunEvent.workflow_agent_completed.value
144
+ content: Optional[Any] = None
145
+
146
+
123
147
  @dataclass
124
148
  class WorkflowCompletedEvent(BaseWorkflowRunOutputEvent):
125
149
  """Event sent when workflow execution completes"""
@@ -388,10 +412,17 @@ class CustomEvent(BaseWorkflowRunOutputEvent):
388
412
 
389
413
  event: str = WorkflowRunEvent.custom_event.value
390
414
 
415
+ def __init__(self, **kwargs):
416
+ # Store arbitrary attributes directly on the instance
417
+ for key, value in kwargs.items():
418
+ setattr(self, key, value)
419
+
391
420
 
392
421
  # Union type for all workflow run response events
393
422
  WorkflowRunOutputEvent = Union[
394
423
  WorkflowStartedEvent,
424
+ WorkflowAgentStartedEvent,
425
+ WorkflowAgentCompletedEvent,
395
426
  WorkflowCompletedEvent,
396
427
  WorkflowErrorEvent,
397
428
  WorkflowCancelledEvent,
@@ -417,6 +448,8 @@ WorkflowRunOutputEvent = Union[
417
448
  # Map event string to dataclass for workflow events
418
449
  WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
419
450
  WorkflowRunEvent.workflow_started.value: WorkflowStartedEvent,
451
+ WorkflowRunEvent.workflow_agent_started.value: WorkflowAgentStartedEvent,
452
+ WorkflowRunEvent.workflow_agent_completed.value: WorkflowAgentCompletedEvent,
420
453
  WorkflowRunEvent.workflow_completed.value: WorkflowCompletedEvent,
421
454
  WorkflowRunEvent.workflow_cancelled.value: WorkflowCancelledEvent,
422
455
  WorkflowRunEvent.workflow_error.value: WorkflowErrorEvent,
@@ -442,10 +475,15 @@ WORKFLOW_RUN_EVENT_TYPE_REGISTRY = {
442
475
 
443
476
  def workflow_run_output_event_from_dict(data: dict) -> BaseWorkflowRunOutputEvent:
444
477
  event_type = data.get("event", "")
445
- cls = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
446
- if not cls:
478
+ if event_type in {e.value for e in RunEvent}:
479
+ return run_output_event_from_dict(data) # type: ignore
480
+ elif event_type in {e.value for e in TeamRunEvent}:
481
+ return team_run_output_event_from_dict(data) # type: ignore
482
+ else:
483
+ event_class = WORKFLOW_RUN_EVENT_TYPE_REGISTRY.get(event_type)
484
+ if not event_class:
447
485
  raise ValueError(f"Unknown workflow event type: {event_type}")
448
- return cls.from_dict(data) # type: ignore
486
+ return event_class.from_dict(data) # type: ignore
449
487
 
450
488
 
451
489
  @dataclass
@@ -475,6 +513,10 @@ class WorkflowRunOutput:
475
513
  # Store agent/team responses separately with parent_run_id references
476
514
  step_executor_runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
477
515
 
516
+ # Workflow agent run - stores the full agent RunOutput when workflow agent is used
517
+ # The agent's parent_run_id will point to this workflow run's run_id to establish the relationship
518
+ workflow_agent_run: Optional[RunOutput] = None
519
+
478
520
  # Store events from workflow execution
479
521
  events: Optional[List[WorkflowRunOutputEvent]] = None
480
522
 
@@ -506,6 +548,7 @@ class WorkflowRunOutput:
506
548
  "step_executor_runs",
507
549
  "events",
508
550
  "metrics",
551
+ "workflow_agent_run",
509
552
  ]
510
553
  }
511
554
 
@@ -541,6 +584,9 @@ class WorkflowRunOutput:
541
584
  if self.step_executor_runs:
542
585
  _dict["step_executor_runs"] = [run.to_dict() for run in self.step_executor_runs]
543
586
 
587
+ if self.workflow_agent_run is not None:
588
+ _dict["workflow_agent_run"] = self.workflow_agent_run.to_dict()
589
+
544
590
  if self.metrics is not None:
545
591
  _dict["metrics"] = self.metrics.to_dict()
546
592
 
@@ -551,7 +597,7 @@ class WorkflowRunOutput:
551
597
  _dict["input"] = self.input
552
598
 
553
599
  if self.content and isinstance(self.content, BaseModel):
554
- _dict["content"] = self.content.model_dump(exclude_none=True)
600
+ _dict["content"] = self.content.model_dump(exclude_none=True, mode="json")
555
601
 
556
602
  if self.events is not None:
557
603
  _dict["events"] = [e.to_dict() for e in self.events]
@@ -588,19 +634,20 @@ class WorkflowRunOutput:
588
634
  else:
589
635
  step_executor_runs.append(RunOutput.from_dict(run_data))
590
636
 
591
- metadata = data.pop("metadata", None)
592
-
593
- images = data.pop("images", [])
594
- images = [Image.model_validate(image) for image in images] if images else None
595
-
596
- videos = data.pop("videos", [])
597
- videos = [Video.model_validate(video) for video in videos] if videos else None
637
+ workflow_agent_run_data = data.pop("workflow_agent_run", None)
638
+ workflow_agent_run = None
639
+ if workflow_agent_run_data:
640
+ if isinstance(workflow_agent_run_data, dict):
641
+ workflow_agent_run = RunOutput.from_dict(workflow_agent_run_data)
642
+ elif isinstance(workflow_agent_run_data, RunOutput):
643
+ workflow_agent_run = workflow_agent_run_data
598
644
 
599
- audio = data.pop("audio", [])
600
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
645
+ metadata = data.pop("metadata", None)
601
646
 
602
- response_audio = data.pop("response_audio", None)
603
- response_audio = Audio.model_validate(response_audio) if response_audio else None
647
+ images = reconstruct_images(data.pop("images", []))
648
+ videos = reconstruct_videos(data.pop("videos", []))
649
+ audio = reconstruct_audio_list(data.pop("audio", []))
650
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
604
651
 
605
652
  events_data = data.pop("events", [])
606
653
  final_events = []
@@ -623,8 +670,15 @@ class WorkflowRunOutput:
623
670
 
624
671
  input_data = data.pop("input", None)
625
672
 
673
+ # Filter data to only include fields that are actually defined in the WorkflowRunOutput dataclass
674
+ from dataclasses import fields
675
+
676
+ supported_fields = {f.name for f in fields(cls)}
677
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
678
+
626
679
  return cls(
627
680
  step_results=parsed_step_results,
681
+ workflow_agent_run=workflow_agent_run,
628
682
  metadata=metadata,
629
683
  images=images,
630
684
  videos=videos,
@@ -634,7 +688,7 @@ class WorkflowRunOutput:
634
688
  metrics=workflow_metrics,
635
689
  step_executor_runs=step_executor_runs,
636
690
  input=input_data,
637
- **data,
691
+ **filtered_data,
638
692
  )
639
693
 
640
694
  def get_content_as_string(self, **kwargs) -> str:
agno/session/agent.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import asdict, dataclass
4
- from typing import Any, Dict, List, Mapping, Optional
4
+ from typing import Any, Dict, List, Mapping, Optional, Union
5
5
 
6
6
  from agno.models.message import Message
7
7
  from agno.run.agent import RunOutput
8
8
  from agno.run.base import RunStatus
9
+ from agno.run.team import TeamRunOutput
9
10
  from agno.session.summary import SessionSummary
10
11
  from agno.utils.log import log_debug, log_warning
11
12
 
@@ -33,7 +34,7 @@ class AgentSession:
33
34
  # Agent Data: agent_id, name and model
34
35
  agent_data: Optional[Dict[str, Any]] = None
35
36
  # List of all runs in the session
36
- runs: Optional[List[RunOutput]] = None
37
+ runs: Optional[List[Union[RunOutput, TeamRunOutput]]] = None
37
38
  # Summary of the session
38
39
  summary: Optional["SessionSummary"] = None
39
40
 
@@ -57,9 +58,13 @@ class AgentSession:
57
58
  return None
58
59
 
59
60
  runs = data.get("runs")
60
- serialized_runs: List[RunOutput] = []
61
+ serialized_runs: List[Union[RunOutput, TeamRunOutput]] = []
61
62
  if runs is not None and isinstance(runs[0], dict):
62
- serialized_runs = [RunOutput.from_dict(run) for run in runs]
63
+ for run in runs:
64
+ if "agent_id" in run:
65
+ serialized_runs.append(RunOutput.from_dict(run))
66
+ elif "team_id" in run:
67
+ serialized_runs.append(TeamRunOutput.from_dict(run))
63
68
 
64
69
  summary = data.get("summary")
65
70
  if summary is not None and isinstance(summary, dict):
@@ -101,76 +106,136 @@ class AgentSession:
101
106
 
102
107
  log_debug("Added RunOutput to Agent Session")
103
108
 
104
- def get_run(self, run_id: str) -> Optional[RunOutput]:
109
+ def get_run(self, run_id: str) -> Optional[Union[RunOutput, TeamRunOutput]]:
105
110
  for run in self.runs or []:
106
111
  if run.run_id == run_id:
107
112
  return run
108
113
  return None
109
114
 
110
- def get_messages_from_last_n_runs(
115
+ def get_messages(
111
116
  self,
112
117
  agent_id: Optional[str] = None,
113
118
  team_id: Optional[str] = None,
114
- last_n: Optional[int] = None,
115
- skip_role: Optional[str] = None,
116
- skip_status: Optional[List[RunStatus]] = None,
119
+ last_n_runs: Optional[int] = None,
120
+ limit: Optional[int] = None,
121
+ skip_roles: Optional[List[str]] = None,
122
+ skip_statuses: Optional[List[RunStatus]] = None,
117
123
  skip_history_messages: bool = True,
118
124
  ) -> List[Message]:
119
- """Returns the messages from the last_n runs, excluding previously tagged history messages.
125
+ """Returns the messages belonging to the session that fit the given criteria.
126
+
120
127
  Args:
121
128
  agent_id: The id of the agent to get the messages from.
122
129
  team_id: The id of the team to get the messages from.
123
- last_n: The number of runs to return from the end of the conversation. Defaults to all runs.
124
- skip_role: Skip messages with this role.
125
- skip_status: Skip messages with this status.
130
+ last_n_runs: The number of runs to return messages from, counting from the latest. Defaults to all runs.
131
+ last_n_messages: The number of messages to return, counting from the latest. Defaults to all messages.
132
+ skip_roles: Skip messages with these roles.
133
+ skip_statuses: Skip messages with these statuses.
126
134
  skip_history_messages: Skip messages that were tagged as history in previous runs.
135
+
127
136
  Returns:
128
- A list of Messages from the specified runs, excluding history messages.
137
+ A list of Messages belonging to the session.
129
138
  """
139
+
140
+ def _should_skip_message(
141
+ message: Message, skip_roles: Optional[List[str]] = None, skip_history_messages: bool = True
142
+ ) -> bool:
143
+ """Logic to determine if a message should be skipped"""
144
+ # Skip messages that were tagged as history in previous runs
145
+ if hasattr(message, "from_history") and message.from_history and skip_history_messages:
146
+ return True
147
+
148
+ # Skip messages with specified role
149
+ if skip_roles and message.role in skip_roles:
150
+ return True
151
+
152
+ return False
153
+
130
154
  if not self.runs:
131
155
  return []
132
156
 
133
- if skip_status is None:
134
- skip_status = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
157
+ if skip_statuses is None:
158
+ skip_statuses = [RunStatus.paused, RunStatus.cancelled, RunStatus.error]
159
+
160
+ runs = self.runs
135
161
 
136
- session_runs = self.runs
137
162
  # Filter by agent_id and team_id
138
163
  if agent_id:
139
- session_runs = [run for run in session_runs if hasattr(run, "agent_id") and run.agent_id == agent_id] # type: ignore
164
+ runs = [run for run in runs if hasattr(run, "agent_id") and run.agent_id == agent_id] # type: ignore
140
165
  if team_id:
141
- session_runs = [run for run in session_runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
166
+ runs = [run for run in runs if hasattr(run, "team_id") and run.team_id == team_id] # type: ignore
167
+
168
+ # Skip any messages that might be part of members of teams (for session re-use)
169
+ runs = [run for run in runs if run.parent_run_id is None] # type: ignore
142
170
 
143
171
  # Filter by status
144
- session_runs = [run for run in session_runs if hasattr(run, "status") and run.status not in skip_status] # type: ignore
172
+ runs = [run for run in runs if hasattr(run, "status") and run.status not in skip_statuses] # type: ignore
145
173
 
146
- # Filter by last_n
147
- runs_to_process = session_runs[-last_n:] if last_n is not None else session_runs
148
174
  messages_from_history = []
149
175
  system_message = None
150
- for run_response in runs_to_process:
151
- if not (run_response and run_response.messages):
152
- continue
153
176
 
154
- for message in run_response.messages or []:
155
- # Skip messages that were tagged as history in previous runs
156
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
177
+ # Limit the number of messages returned if limit is set
178
+ if limit is not None:
179
+ for run_response in runs:
180
+ if not run_response or not run_response.messages:
157
181
  continue
158
182
 
159
- # Skip messages with specified role
160
- if skip_role and message.role == skip_role:
183
+ for message in run_response.messages or []:
184
+ if _should_skip_message(message, skip_roles, skip_history_messages):
185
+ continue
186
+
187
+ if message.role == "system":
188
+ # Only add the system message once
189
+ if system_message is None:
190
+ system_message = message
191
+ else:
192
+ messages_from_history.append(message)
193
+
194
+ if system_message:
195
+ messages_from_history = [system_message] + messages_from_history[
196
+ -(limit - 1) :
197
+ ] # Grab one less message then add the system message
198
+ else:
199
+ messages_from_history = messages_from_history[-limit:]
200
+
201
+ # Remove tool result messages that don't have an associated assistant message with tool calls
202
+ while len(messages_from_history) > 0 and messages_from_history[0].role == "tool":
203
+ messages_from_history.pop(0)
204
+
205
+ # If limit is not set, return all messages
206
+ else:
207
+ runs_to_process = runs[-last_n_runs:] if last_n_runs is not None else runs
208
+ for run_response in runs_to_process:
209
+ if not run_response or not run_response.messages:
161
210
  continue
162
211
 
163
- if message.role == "system":
164
- # Only add the system message once
165
- if system_message is None:
166
- system_message = message
167
- messages_from_history.append(system_message)
168
- else:
169
- messages_from_history.append(message)
212
+ for message in run_response.messages or []:
213
+ if _should_skip_message(message, skip_roles, skip_history_messages):
214
+ continue
215
+
216
+ if message.role == "system":
217
+ # Only add the system message once
218
+ if system_message is None:
219
+ system_message = message
220
+ messages_from_history.append(system_message)
221
+ else:
222
+ messages_from_history.append(message)
170
223
 
171
224
  log_debug(f"Getting messages from previous runs: {len(messages_from_history)}")
172
225
  return messages_from_history
173
226
 
227
+ def get_chat_history(self, last_n_runs: Optional[int] = None) -> List[Message]:
228
+ """Return the chat history (user and assistant messages) for the session.
229
+ Use get_messages() for more filtering options.
230
+
231
+ Args:
232
+ last_n_runs: Number of recent runs to include. If None, all runs will be considered.
233
+
234
+ Returns:
235
+ A list of user and assistant Messages belonging to the session.
236
+ """
237
+ return self.get_messages(skip_roles=["system", "tool"], last_n_runs=last_n_runs)
238
+
174
239
  def get_tool_calls(self, num_calls: Optional[int] = None) -> List[Dict[str, Any]]:
175
240
  """Returns a list of tool calls from the messages"""
176
241
 
@@ -187,61 +252,9 @@ class AgentSession:
187
252
  return tool_calls
188
253
  return tool_calls
189
254
 
190
- def get_messages_for_session(
191
- self,
192
- user_role: str = "user",
193
- assistant_role: Optional[List[str]] = None,
194
- skip_history_messages: bool = True,
195
- ) -> List[Message]:
196
- """Returns a list of messages for the session that iterate through user message and assistant response."""
197
-
198
- if assistant_role is None:
199
- # TODO: Check if we still need CHATBOT as a role
200
- assistant_role = ["assistant", "model", "CHATBOT"]
201
-
202
- final_messages: List[Message] = []
203
- session_runs = self.runs
204
- if not session_runs:
205
- return []
206
-
207
- for run_response in session_runs:
208
- if run_response and run_response.messages:
209
- user_message_from_run = None
210
- assistant_message_from_run = None
211
-
212
- # Start from the beginning to look for the user message
213
- for message in run_response.messages or []:
214
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
215
- continue
216
- if message.role == user_role:
217
- user_message_from_run = message
218
- break
219
-
220
- # Start from the end to look for the assistant response
221
- for message in run_response.messages[::-1]:
222
- if hasattr(message, "from_history") and message.from_history and skip_history_messages:
223
- continue
224
- if message.role in assistant_role:
225
- assistant_message_from_run = message
226
- break
227
-
228
- if user_message_from_run and assistant_message_from_run:
229
- final_messages.append(user_message_from_run)
230
- final_messages.append(assistant_message_from_run)
231
- return final_messages
232
-
233
255
  def get_session_summary(self) -> Optional[SessionSummary]:
234
256
  """Get the session summary for the session"""
235
257
 
236
258
  if self.summary is None:
237
259
  return None
238
260
  return self.summary
239
-
240
- # Chat History functions
241
- def get_chat_history(self) -> List[Message]:
242
- """Get the chat history for the session"""
243
-
244
- messages = []
245
- for run in self.runs or []:
246
- messages.extend([msg for msg in run.messages or [] if not msg.from_history])
247
- return messages
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
 
@@ -102,7 +106,23 @@ class SessionSummaryManager:
102
106
  system_prompt += "<conversation>"
103
107
  for message in conversation:
104
108
  if message.role == "user":
105
- 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}")
106
126
  elif message.role in ["assistant", "model"]:
107
127
  conversation_messages.append(f"Assistant: {message.content}\n")
108
128
  system_prompt += "\n".join(conversation_messages)
@@ -118,23 +138,30 @@ class SessionSummaryManager:
118
138
  def _prepare_summary_messages(
119
139
  self,
120
140
  session: Optional["Session"] = None,
121
- ) -> List[Message]:
122
- """Prepare messages for session summary generation"""
123
- 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
+
124
150
  response_format = self.get_response_format(self.model)
125
151
 
126
- return (
127
- [
128
- self.get_system_message(
129
- conversation=session.get_messages_for_session(), # type: ignore
130
- response_format=response_format,
131
- ),
132
- Message(role="user", content="Provide the summary of the conversation."),
133
- ]
134
- if session
135
- else []
152
+ system_message = self.get_system_message(
153
+ conversation=session.get_messages(), # type: ignore
154
+ response_format=response_format,
136
155
  )
137
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
+
138
165
  def _process_summary_response(self, summary_response, session_summary_model: "Model") -> Optional[SessionSummary]: # type: ignore
139
166
  """Process the model response into a SessionSummary"""
140
167
  from datetime import datetime
@@ -187,10 +214,17 @@ class SessionSummaryManager:
187
214
  ) -> Optional[SessionSummary]:
188
215
  """Creates a summary of the session"""
189
216
  log_debug("Creating session summary", center=True)
217
+ self.model = get_model(self.model)
190
218
  if self.model is None:
191
219
  return None
192
220
 
193
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
+
194
228
  response_format = self.get_response_format(self.model)
195
229
 
196
230
  summary_response = self.model.response(messages=messages, response_format=response_format)
@@ -208,10 +242,17 @@ class SessionSummaryManager:
208
242
  ) -> Optional[SessionSummary]:
209
243
  """Creates a summary of the session"""
210
244
  log_debug("Creating session summary", center=True)
245
+ self.model = get_model(self.model)
211
246
  if self.model is None:
212
247
  return None
213
248
 
214
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
+
215
256
  response_format = self.get_response_format(self.model)
216
257
 
217
258
  summary_response = await self.model.aresponse(messages=messages, response_format=response_format)