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
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Optional
4
+
5
+ from agno.models.base import Model
6
+ from agno.models.message import Message
7
+ from agno.utils.log import logger
8
+
9
+
10
+ def is_anthropic_reasoning_model(reasoning_model: Model) -> bool:
11
+ """Check if the model is an Anthropic Claude model with thinking support."""
12
+ is_claude = reasoning_model.__class__.__name__ == "Claude"
13
+ if not is_claude:
14
+ return False
15
+
16
+ # Check if provider is Anthropic (not VertexAI)
17
+ is_anthropic_provider = hasattr(reasoning_model, "provider") and reasoning_model.provider == "Anthropic"
18
+
19
+ # Check if thinking parameter is set
20
+ has_thinking = hasattr(reasoning_model, "thinking") and reasoning_model.thinking is not None
21
+
22
+ return is_claude and is_anthropic_provider and has_thinking
23
+
24
+
25
+ def get_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
26
+ """Get reasoning from an Anthropic Claude model."""
27
+ from agno.run.agent import RunOutput
28
+
29
+ try:
30
+ reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
31
+ except Exception as e:
32
+ logger.warning(f"Reasoning error: {e}")
33
+ return None
34
+
35
+ reasoning_content: str = ""
36
+ redacted_reasoning_content: Optional[str] = None
37
+
38
+ if reasoning_agent_response.messages is not None:
39
+ for msg in reasoning_agent_response.messages:
40
+ if msg.reasoning_content is not None:
41
+ reasoning_content = msg.reasoning_content
42
+ if hasattr(msg, "redacted_reasoning_content") and msg.redacted_reasoning_content is not None:
43
+ redacted_reasoning_content = msg.redacted_reasoning_content
44
+ break
45
+
46
+ return Message(
47
+ role="assistant",
48
+ content=f"<thinking>\n{reasoning_content}\n</thinking>",
49
+ reasoning_content=reasoning_content,
50
+ redacted_reasoning_content=redacted_reasoning_content,
51
+ )
52
+
53
+
54
+ async def aget_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
55
+ """Get reasoning from an Anthropic Claude model asynchronously."""
56
+ from agno.run.agent import RunOutput
57
+
58
+ try:
59
+ reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
60
+ except Exception as e:
61
+ logger.warning(f"Reasoning error: {e}")
62
+ return None
63
+
64
+ reasoning_content: str = ""
65
+ redacted_reasoning_content: Optional[str] = None
66
+
67
+ if reasoning_agent_response.messages is not None:
68
+ for msg in reasoning_agent_response.messages:
69
+ if msg.reasoning_content is not None:
70
+ reasoning_content = msg.reasoning_content
71
+ if hasattr(msg, "redacted_reasoning_content") and msg.redacted_reasoning_content is not None:
72
+ redacted_reasoning_content = msg.redacted_reasoning_content
73
+ break
74
+
75
+ return Message(
76
+ role="assistant",
77
+ content=f"<thinking>\n{reasoning_content}\n</thinking>",
78
+ reasoning_content=reasoning_content,
79
+ redacted_reasoning_content=redacted_reasoning_content,
80
+ )
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Optional
4
+
5
+ from agno.models.base import Model
6
+ from agno.models.message import Message
7
+ from agno.utils.log import logger
8
+
9
+
10
+ def is_gemini_reasoning_model(reasoning_model: Model) -> bool:
11
+ """Check if the model is a Gemini model with thinking support."""
12
+ is_gemini_class = reasoning_model.__class__.__name__ == "Gemini"
13
+ if not is_gemini_class:
14
+ return False
15
+
16
+ # Check if it's a Gemini 2.5+ model (supports thinking)
17
+ model_id = reasoning_model.id.lower()
18
+ has_thinking_support = "2.5" in model_id
19
+
20
+ # Also check if thinking parameters are set
21
+ # Note: thinking_budget=0 explicitly disables thinking mode per Google's API docs
22
+ has_thinking_budget = (
23
+ hasattr(reasoning_model, "thinking_budget")
24
+ and reasoning_model.thinking_budget is not None
25
+ and reasoning_model.thinking_budget > 0
26
+ )
27
+ has_include_thoughts = hasattr(reasoning_model, "include_thoughts") and reasoning_model.include_thoughts is not None
28
+
29
+ return is_gemini_class and (has_thinking_support or has_thinking_budget or has_include_thoughts)
30
+
31
+
32
+ def get_gemini_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
33
+ """Get reasoning from a Gemini model."""
34
+ from agno.run.agent import RunOutput
35
+
36
+ try:
37
+ reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
38
+ except Exception as e:
39
+ logger.warning(f"Reasoning error: {e}")
40
+ return None
41
+
42
+ reasoning_content: str = ""
43
+ if reasoning_agent_response.messages is not None:
44
+ for msg in reasoning_agent_response.messages:
45
+ if msg.reasoning_content is not None:
46
+ reasoning_content = msg.reasoning_content
47
+ break
48
+
49
+ return Message(
50
+ role="assistant", content=f"<thinking>\n{reasoning_content}\n</thinking>", reasoning_content=reasoning_content
51
+ )
52
+
53
+
54
+ async def aget_gemini_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
55
+ """Get reasoning from a Gemini model asynchronously."""
56
+ from agno.run.agent import RunOutput
57
+
58
+ try:
59
+ reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
60
+ except Exception as e:
61
+ logger.warning(f"Reasoning error: {e}")
62
+ return None
63
+
64
+ reasoning_content: str = ""
65
+ if reasoning_agent_response.messages is not None:
66
+ for msg in reasoning_agent_response.messages:
67
+ if msg.reasoning_content is not None:
68
+ reasoning_content = msg.reasoning_content
69
+ break
70
+
71
+ return Message(
72
+ role="assistant", content=f"<thinking>\n{reasoning_content}\n</thinking>", reasoning_content=reasoning_content
73
+ )
agno/reasoning/openai.py CHANGED
@@ -28,6 +28,11 @@ def is_openai_reasoning_model(reasoning_model: Model) -> bool:
28
28
  def get_openai_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
29
29
  from agno.run.agent import RunOutput
30
30
 
31
+ # Update system message role to "system"
32
+ for message in messages:
33
+ if message.role == "developer":
34
+ message.role = "system"
35
+
31
36
  try:
32
37
  reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
33
38
  except Exception as e:
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Optional
4
+
5
+ from agno.models.base import Model
6
+ from agno.models.message import Message
7
+ from agno.utils.log import logger
8
+
9
+
10
+ def is_vertexai_reasoning_model(reasoning_model: Model) -> bool:
11
+ """Check if the model is a VertexAI model with thinking support."""
12
+ # Check if provider is VertexAI
13
+ is_vertexai_provider = hasattr(reasoning_model, "provider") and reasoning_model.provider == "VertexAI"
14
+
15
+ # Check if thinking parameter is set
16
+ has_thinking = hasattr(reasoning_model, "thinking") and reasoning_model.thinking is not None
17
+
18
+ return is_vertexai_provider and has_thinking
19
+
20
+
21
+ def get_vertexai_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
22
+ """Get reasoning from a VertexAI Claude model."""
23
+ from agno.run.agent import RunOutput
24
+
25
+ try:
26
+ reasoning_agent_response: RunOutput = reasoning_agent.run(input=messages)
27
+ except Exception as e:
28
+ logger.warning(f"Reasoning error: {e}")
29
+ return None
30
+
31
+ reasoning_content: str = ""
32
+ redacted_reasoning_content: Optional[str] = None
33
+
34
+ if reasoning_agent_response.messages is not None:
35
+ for msg in reasoning_agent_response.messages:
36
+ if msg.reasoning_content is not None:
37
+ reasoning_content = msg.reasoning_content
38
+ if hasattr(msg, "redacted_reasoning_content") and msg.redacted_reasoning_content is not None:
39
+ redacted_reasoning_content = msg.redacted_reasoning_content
40
+ break
41
+
42
+ return Message(
43
+ role="assistant",
44
+ content=f"<thinking>\n{reasoning_content}\n</thinking>",
45
+ reasoning_content=reasoning_content,
46
+ redacted_reasoning_content=redacted_reasoning_content,
47
+ )
48
+
49
+
50
+ async def aget_vertexai_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
51
+ """Get reasoning from a VertexAI Claude model asynchronously."""
52
+ from agno.run.agent import RunOutput
53
+
54
+ try:
55
+ reasoning_agent_response: RunOutput = await reasoning_agent.arun(input=messages)
56
+ except Exception as e:
57
+ logger.warning(f"Reasoning error: {e}")
58
+ return None
59
+
60
+ reasoning_content: str = ""
61
+ redacted_reasoning_content: Optional[str] = None
62
+
63
+ if reasoning_agent_response.messages is not None:
64
+ for msg in reasoning_agent_response.messages:
65
+ if msg.reasoning_content is not None:
66
+ reasoning_content = msg.reasoning_content
67
+ if hasattr(msg, "redacted_reasoning_content") and msg.redacted_reasoning_content is not None:
68
+ redacted_reasoning_content = msg.redacted_reasoning_content
69
+ break
70
+
71
+ return Message(
72
+ role="assistant",
73
+ content=f"<thinking>\n{reasoning_content}\n</thinking>",
74
+ reasoning_content=reasoning_content,
75
+ redacted_reasoning_content=redacted_reasoning_content,
76
+ )
agno/run/__init__.py CHANGED
@@ -0,0 +1,6 @@
1
+ from agno.run.base import RunContext, RunStatus
2
+
3
+ __all__ = [
4
+ "RunContext",
5
+ "RunStatus",
6
+ ]
agno/run/agent.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from dataclasses import asdict, dataclass, field
2
2
  from enum import Enum
3
3
  from time import time
4
- from typing import Any, Dict, List, Optional, Sequence, Union
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union
5
5
 
6
6
  from pydantic import BaseModel
7
7
 
@@ -11,7 +11,18 @@ from agno.models.metrics import Metrics
11
11
  from agno.models.response import ToolExecution
12
12
  from agno.reasoning.step import ReasoningStep
13
13
  from agno.run.base import BaseRunOutputEvent, MessageReferences, RunStatus
14
+ from agno.run.requirement import RunRequirement
14
15
  from agno.utils.log import logger
16
+ from agno.utils.media import (
17
+ reconstruct_audio_list,
18
+ reconstruct_files,
19
+ reconstruct_images,
20
+ reconstruct_response_audio,
21
+ reconstruct_videos,
22
+ )
23
+
24
+ if TYPE_CHECKING:
25
+ from agno.session.summary import SessionSummary
15
26
 
16
27
 
17
28
  @dataclass
@@ -60,12 +71,39 @@ class RunInput:
60
71
  result["input_content"] = self.input_content.model_dump(exclude_none=True)
61
72
  elif isinstance(self.input_content, Message):
62
73
  result["input_content"] = self.input_content.to_dict()
74
+
75
+ # Handle input_content provided as a list of Message objects
63
76
  elif (
64
77
  isinstance(self.input_content, list)
65
78
  and self.input_content
66
79
  and isinstance(self.input_content[0], Message)
67
80
  ):
68
81
  result["input_content"] = [m.to_dict() for m in self.input_content]
82
+
83
+ # Handle input_content provided as a list of dicts
84
+ elif (
85
+ isinstance(self.input_content, list) and self.input_content and isinstance(self.input_content[0], dict)
86
+ ):
87
+ for content in self.input_content:
88
+ # Handle media input
89
+ if isinstance(content, dict):
90
+ if content.get("images"):
91
+ content["images"] = [
92
+ img.to_dict() if isinstance(img, Image) else img for img in content["images"]
93
+ ]
94
+ if content.get("videos"):
95
+ content["videos"] = [
96
+ vid.to_dict() if isinstance(vid, Video) else vid for vid in content["videos"]
97
+ ]
98
+ if content.get("audios"):
99
+ content["audios"] = [
100
+ aud.to_dict() if isinstance(aud, Audio) else aud for aud in content["audios"]
101
+ ]
102
+ if content.get("files"):
103
+ content["files"] = [
104
+ file.to_dict() if isinstance(file, File) else file for file in content["files"]
105
+ ]
106
+ result["input_content"] = self.input_content
69
107
  else:
70
108
  result["input_content"] = self.input_content
71
109
 
@@ -83,21 +121,10 @@ class RunInput:
83
121
  @classmethod
84
122
  def from_dict(cls, data: Dict[str, Any]) -> "RunInput":
85
123
  """Create RunInput from dictionary"""
86
- images = None
87
- if data.get("images"):
88
- images = [Image.model_validate(img_data) for img_data in data["images"]]
89
-
90
- videos = None
91
- if data.get("videos"):
92
- videos = [Video.model_validate(vid_data) for vid_data in data["videos"]]
93
-
94
- audios = None
95
- if data.get("audios"):
96
- audios = [Audio.model_validate(aud_data) for aud_data in data["audios"]]
97
-
98
- files = None
99
- if data.get("files"):
100
- files = [File.model_validate(file_data) for file_data in data["files"]]
124
+ images = reconstruct_images(data.get("images"))
125
+ videos = reconstruct_videos(data.get("videos"))
126
+ audios = reconstruct_audio_list(data.get("audios"))
127
+ files = reconstruct_files(data.get("files"))
101
128
 
102
129
  return cls(
103
130
  input_content=data.get("input_content", ""), images=images, videos=videos, audios=audios, files=files
@@ -109,6 +136,7 @@ class RunEvent(str, Enum):
109
136
 
110
137
  run_started = "RunStarted"
111
138
  run_content = "RunContent"
139
+ run_content_completed = "RunContentCompleted"
112
140
  run_intermediate_content = "RunIntermediateContent"
113
141
  run_completed = "RunCompleted"
114
142
  run_error = "RunError"
@@ -120,6 +148,9 @@ class RunEvent(str, Enum):
120
148
  pre_hook_started = "PreHookStarted"
121
149
  pre_hook_completed = "PreHookCompleted"
122
150
 
151
+ post_hook_started = "PostHookStarted"
152
+ post_hook_completed = "PostHookCompleted"
153
+
123
154
  tool_call_started = "ToolCallStarted"
124
155
  tool_call_completed = "ToolCallCompleted"
125
156
 
@@ -130,6 +161,9 @@ class RunEvent(str, Enum):
130
161
  memory_update_started = "MemoryUpdateStarted"
131
162
  memory_update_completed = "MemoryUpdateCompleted"
132
163
 
164
+ session_summary_started = "SessionSummaryStarted"
165
+ session_summary_completed = "SessionSummaryCompleted"
166
+
133
167
  parser_model_response_started = "ParserModelResponseStarted"
134
168
  parser_model_response_completed = "ParserModelResponseCompleted"
135
169
 
@@ -188,6 +222,9 @@ class RunContentEvent(BaseAgentRunEvent):
188
222
 
189
223
  event: str = RunEvent.run_content.value
190
224
  content: Optional[Any] = None
225
+ workflow_agent: bool = (
226
+ False # Used by consumers of the events to distinguish between workflow agent and regular agent
227
+ )
191
228
  content_type: str = "str"
192
229
  reasoning_content: Optional[str] = None
193
230
  model_provider_data: Optional[Dict[str, Any]] = None
@@ -200,6 +237,11 @@ class RunContentEvent(BaseAgentRunEvent):
200
237
  reasoning_messages: Optional[List[Message]] = None
201
238
 
202
239
 
240
+ @dataclass
241
+ class RunContentCompletedEvent(BaseAgentRunEvent):
242
+ event: str = RunEvent.run_content_completed.value
243
+
244
+
203
245
  @dataclass
204
246
  class IntermediateRunContentEvent(BaseAgentRunEvent):
205
247
  event: str = RunEvent.run_intermediate_content.value
@@ -225,17 +267,25 @@ class RunCompletedEvent(BaseAgentRunEvent):
225
267
  reasoning_messages: Optional[List[Message]] = None
226
268
  metadata: Optional[Dict[str, Any]] = None
227
269
  metrics: Optional[Metrics] = None
270
+ session_state: Optional[Dict[str, Any]] = None
228
271
 
229
272
 
230
273
  @dataclass
231
274
  class RunPausedEvent(BaseAgentRunEvent):
232
275
  event: str = RunEvent.run_paused.value
233
276
  tools: Optional[List[ToolExecution]] = None
277
+ requirements: Optional[List[RunRequirement]] = None
234
278
 
235
279
  @property
236
280
  def is_paused(self):
237
281
  return True
238
282
 
283
+ @property
284
+ def active_requirements(self) -> List[RunRequirement]:
285
+ if not self.requirements:
286
+ return []
287
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
288
+
239
289
 
240
290
  @dataclass
241
291
  class RunContinuedEvent(BaseAgentRunEvent):
@@ -277,6 +327,18 @@ class PreHookCompletedEvent(BaseAgentRunEvent):
277
327
  run_input: Optional[RunInput] = None
278
328
 
279
329
 
330
+ @dataclass
331
+ class PostHookStartedEvent(BaseAgentRunEvent):
332
+ event: str = RunEvent.post_hook_started.value
333
+ post_hook_name: Optional[str] = None
334
+
335
+
336
+ @dataclass
337
+ class PostHookCompletedEvent(BaseAgentRunEvent):
338
+ event: str = RunEvent.post_hook_completed.value
339
+ post_hook_name: Optional[str] = None
340
+
341
+
280
342
  @dataclass
281
343
  class MemoryUpdateStartedEvent(BaseAgentRunEvent):
282
344
  event: str = RunEvent.memory_update_started.value
@@ -287,6 +349,17 @@ class MemoryUpdateCompletedEvent(BaseAgentRunEvent):
287
349
  event: str = RunEvent.memory_update_completed.value
288
350
 
289
351
 
352
+ @dataclass
353
+ class SessionSummaryStartedEvent(BaseAgentRunEvent):
354
+ event: str = RunEvent.session_summary_started.value
355
+
356
+
357
+ @dataclass
358
+ class SessionSummaryCompletedEvent(BaseAgentRunEvent):
359
+ event: str = RunEvent.session_summary_completed.value
360
+ session_summary: Optional["SessionSummary"] = None
361
+
362
+
290
363
  @dataclass
291
364
  class ReasoningStartedEvent(BaseAgentRunEvent):
292
365
  event: str = RunEvent.reasoning_started.value
@@ -347,11 +420,17 @@ class OutputModelResponseCompletedEvent(BaseAgentRunEvent):
347
420
  class CustomEvent(BaseAgentRunEvent):
348
421
  event: str = RunEvent.custom_event.value
349
422
 
423
+ def __init__(self, **kwargs):
424
+ # Store arbitrary attributes directly on the instance
425
+ for key, value in kwargs.items():
426
+ setattr(self, key, value)
427
+
350
428
 
351
429
  RunOutputEvent = Union[
352
430
  RunStartedEvent,
353
431
  RunContentEvent,
354
432
  IntermediateRunContentEvent,
433
+ RunContentCompletedEvent,
355
434
  RunCompletedEvent,
356
435
  RunErrorEvent,
357
436
  RunCancelledEvent,
@@ -359,11 +438,15 @@ RunOutputEvent = Union[
359
438
  RunContinuedEvent,
360
439
  PreHookStartedEvent,
361
440
  PreHookCompletedEvent,
441
+ PostHookStartedEvent,
442
+ PostHookCompletedEvent,
362
443
  ReasoningStartedEvent,
363
444
  ReasoningStepEvent,
364
445
  ReasoningCompletedEvent,
365
446
  MemoryUpdateStartedEvent,
366
447
  MemoryUpdateCompletedEvent,
448
+ SessionSummaryStartedEvent,
449
+ SessionSummaryCompletedEvent,
367
450
  ToolCallStartedEvent,
368
451
  ToolCallCompletedEvent,
369
452
  ParserModelResponseStartedEvent,
@@ -378,6 +461,7 @@ RunOutputEvent = Union[
378
461
  RUN_EVENT_TYPE_REGISTRY = {
379
462
  RunEvent.run_started.value: RunStartedEvent,
380
463
  RunEvent.run_content.value: RunContentEvent,
464
+ RunEvent.run_content_completed.value: RunContentCompletedEvent,
381
465
  RunEvent.run_intermediate_content.value: IntermediateRunContentEvent,
382
466
  RunEvent.run_completed.value: RunCompletedEvent,
383
467
  RunEvent.run_error.value: RunErrorEvent,
@@ -386,11 +470,15 @@ RUN_EVENT_TYPE_REGISTRY = {
386
470
  RunEvent.run_continued.value: RunContinuedEvent,
387
471
  RunEvent.pre_hook_started.value: PreHookStartedEvent,
388
472
  RunEvent.pre_hook_completed.value: PreHookCompletedEvent,
473
+ RunEvent.post_hook_started.value: PostHookStartedEvent,
474
+ RunEvent.post_hook_completed.value: PostHookCompletedEvent,
389
475
  RunEvent.reasoning_started.value: ReasoningStartedEvent,
390
476
  RunEvent.reasoning_step.value: ReasoningStepEvent,
391
477
  RunEvent.reasoning_completed.value: ReasoningCompletedEvent,
392
478
  RunEvent.memory_update_started.value: MemoryUpdateStartedEvent,
393
479
  RunEvent.memory_update_completed.value: MemoryUpdateCompletedEvent,
480
+ RunEvent.session_summary_started.value: SessionSummaryStartedEvent,
481
+ RunEvent.session_summary_completed.value: SessionSummaryCompletedEvent,
394
482
  RunEvent.tool_call_started.value: ToolCallStartedEvent,
395
483
  RunEvent.tool_call_completed.value: ToolCallCompletedEvent,
396
484
  RunEvent.parser_model_response_started.value: ParserModelResponseStartedEvent,
@@ -451,6 +539,7 @@ class RunOutput:
451
539
  references: Optional[List[MessageReferences]] = None
452
540
 
453
541
  metadata: Optional[Dict[str, Any]] = None
542
+ session_state: Optional[Dict[str, Any]] = None
454
543
 
455
544
  created_at: int = field(default_factory=lambda: int(time()))
456
545
 
@@ -458,11 +547,20 @@ class RunOutput:
458
547
 
459
548
  status: RunStatus = RunStatus.running
460
549
 
550
+ # User control flow (HITL) requirements to continue a run when paused, in order of arrival
551
+ requirements: Optional[list[RunRequirement]] = None
552
+
461
553
  # === FOREIGN KEY RELATIONSHIPS ===
462
554
  # These fields establish relationships to parent workflow/step structures
463
555
  # and should be treated as foreign keys for data integrity
464
556
  workflow_step_id: Optional[str] = None # FK: Points to StepOutput.step_id
465
557
 
558
+ @property
559
+ def active_requirements(self) -> list[RunRequirement]:
560
+ if not self.requirements:
561
+ return []
562
+ return [requirement for requirement in self.requirements if not requirement.is_resolved()]
563
+
466
564
  @property
467
565
  def is_paused(self):
468
566
  return self.status == RunStatus.paused
@@ -491,6 +589,7 @@ class RunOutput:
491
589
  and k
492
590
  not in [
493
591
  "messages",
592
+ "metrics",
494
593
  "tools",
495
594
  "metadata",
496
595
  "images",
@@ -505,6 +604,7 @@ class RunOutput:
505
604
  "reasoning_steps",
506
605
  "reasoning_messages",
507
606
  "references",
607
+ "requirements",
508
608
  ]
509
609
  }
510
610
 
@@ -590,6 +690,9 @@ class RunOutput:
590
690
  else:
591
691
  _dict["tools"].append(tool)
592
692
 
693
+ if self.requirements is not None:
694
+ _dict["requirements"] = [req.to_dict() if hasattr(req, "to_dict") else req for req in self.requirements]
695
+
593
696
  if self.input is not None:
594
697
  _dict["input"] = self.input.to_dict()
595
698
 
@@ -615,7 +718,17 @@ class RunOutput:
615
718
  data = data.pop("run")
616
719
 
617
720
  events = data.pop("events", None)
618
- events = [run_output_event_from_dict(event) for event in events] if events else None
721
+ final_events = []
722
+ for event in events or []:
723
+ if "agent_id" in event:
724
+ event = run_output_event_from_dict(event)
725
+ else:
726
+ # Use the factory from response.py for agent events
727
+ from agno.run.team import team_run_output_event_from_dict
728
+
729
+ event = team_run_output_event_from_dict(event)
730
+ final_events.append(event)
731
+ events = final_events
619
732
 
620
733
  messages = data.pop("messages", None)
621
734
  messages = [Message.from_dict(message) for message in messages] if messages else None
@@ -626,20 +739,23 @@ class RunOutput:
626
739
  tools = data.pop("tools", [])
627
740
  tools = [ToolExecution.from_dict(tool) for tool in tools] if tools else None
628
741
 
629
- images = data.pop("images", [])
630
- images = [Image.model_validate(image) for image in images] if images else None
631
-
632
- videos = data.pop("videos", [])
633
- videos = [Video.model_validate(video) for video in videos] if videos else None
634
-
635
- audio = data.pop("audio", [])
636
- audio = [Audio.model_validate(audio) for audio in audio] if audio else None
637
-
638
- files = data.pop("files", [])
639
- files = [File.model_validate(file) for file in files] if files else None
640
-
641
- response_audio = data.pop("response_audio", None)
642
- response_audio = Audio.model_validate(response_audio) if response_audio else None
742
+ # Handle requirements
743
+ requirements_data = data.pop("requirements", None)
744
+ requirements: Optional[List[RunRequirement]] = None
745
+ if requirements_data is not None:
746
+ requirements_list: List[RunRequirement] = []
747
+ for item in requirements_data:
748
+ if isinstance(item, RunRequirement):
749
+ requirements_list.append(item)
750
+ elif isinstance(item, dict):
751
+ requirements_list.append(RunRequirement.from_dict(item))
752
+ requirements = requirements_list if requirements_list else None
753
+
754
+ images = reconstruct_images(data.pop("images", []))
755
+ videos = reconstruct_videos(data.pop("videos", []))
756
+ audio = reconstruct_audio_list(data.pop("audio", []))
757
+ files = reconstruct_files(data.pop("files", []))
758
+ response_audio = reconstruct_response_audio(data.pop("response_audio", None))
643
759
 
644
760
  input_data = data.pop("input", None)
645
761
  input_obj = None
@@ -667,6 +783,12 @@ class RunOutput:
667
783
  if references is not None:
668
784
  references = [MessageReferences.model_validate(reference) for reference in references]
669
785
 
786
+ # Filter data to only include fields that are actually defined in the RunOutput dataclass
787
+ from dataclasses import fields
788
+
789
+ supported_fields = {f.name for f in fields(cls)}
790
+ filtered_data = {k: v for k, v in data.items() if k in supported_fields}
791
+
670
792
  return cls(
671
793
  messages=messages,
672
794
  metrics=metrics,
@@ -683,7 +805,8 @@ class RunOutput:
683
805
  reasoning_steps=reasoning_steps,
684
806
  reasoning_messages=reasoning_messages,
685
807
  references=references,
686
- **data,
808
+ requirements=requirements,
809
+ **filtered_data,
687
810
  )
688
811
 
689
812
  def get_content_as_string(self, **kwargs) -> str: