agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/tools/workflow.py CHANGED
@@ -4,6 +4,7 @@ from typing import Any, Dict, Optional
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
+ from agno.run import RunContext
7
8
  from agno.tools import Toolkit
8
9
  from agno.utils.log import log_debug, log_error
9
10
  from agno.workflow.workflow import Workflow, WorkflowRunOutput
@@ -65,7 +66,7 @@ class WorkflowTools(Toolkit):
65
66
  else:
66
67
  self.register(self.analyze, name="analyze")
67
68
 
68
- def think(self, session_state: Dict[str, Any], thought: str) -> str:
69
+ def think(self, run_context: RunContext, thought: str) -> str:
69
70
  """Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
70
71
  Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
71
72
  You should use this tool as frequently as needed.
@@ -76,14 +77,14 @@ class WorkflowTools(Toolkit):
76
77
  log_debug(f"Workflow Thought: {thought}")
77
78
 
78
79
  # Add the thought to the session state
79
- if session_state is None:
80
- session_state = {}
81
- if "workflow_thoughts" not in session_state:
82
- session_state["workflow_thoughts"] = []
83
- session_state["workflow_thoughts"].append(thought)
80
+ if run_context.session_state is None:
81
+ run_context.session_state = {}
82
+ if "workflow_thoughts" not in run_context.session_state:
83
+ run_context.session_state["workflow_thoughts"] = []
84
+ run_context.session_state["workflow_thoughts"].append(thought)
84
85
 
85
86
  # Return the full log of thoughts and the new thought
86
- thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
87
+ thoughts = "\n".join([f"- {t}" for t in run_context.session_state["workflow_thoughts"]])
87
88
  formatted_thoughts = dedent(
88
89
  f"""Workflow Thoughts:
89
90
  {thoughts}
@@ -94,7 +95,7 @@ class WorkflowTools(Toolkit):
94
95
  log_error(f"Error recording workflow thought: {e}")
95
96
  return f"Error recording workflow thought: {e}"
96
97
 
97
- async def async_think(self, session_state: Dict[str, Any], thought: str) -> str:
98
+ async def async_think(self, run_context: RunContext, thought: str) -> str:
98
99
  """Use this tool as a scratchpad to reason about the workflow execution, refine your approach, brainstorm workflow inputs, or revise your plan.
99
100
  Call `Think` whenever you need to figure out what to do next, analyze the user's requirements, plan workflow inputs, or decide on execution strategy.
100
101
  You should use this tool as frequently as needed.
@@ -105,14 +106,14 @@ class WorkflowTools(Toolkit):
105
106
  log_debug(f"Workflow Thought: {thought}")
106
107
 
107
108
  # Add the thought to the session state
108
- if session_state is None:
109
- session_state = {}
110
- if "workflow_thoughts" not in session_state:
111
- session_state["workflow_thoughts"] = []
112
- session_state["workflow_thoughts"].append(thought)
109
+ if run_context.session_state is None:
110
+ run_context.session_state = {}
111
+ if "workflow_thoughts" not in run_context.session_state:
112
+ run_context.session_state["workflow_thoughts"] = []
113
+ run_context.session_state["workflow_thoughts"].append(thought)
113
114
 
114
115
  # Return the full log of thoughts and the new thought
115
- thoughts = "\n".join([f"- {t}" for t in session_state["workflow_thoughts"]])
116
+ thoughts = "\n".join([f"- {t}" for t in run_context.session_state["workflow_thoughts"]])
116
117
  formatted_thoughts = dedent(
117
118
  f"""Workflow Thoughts:
118
119
  {thoughts}
@@ -125,33 +126,37 @@ class WorkflowTools(Toolkit):
125
126
 
126
127
  def run_workflow(
127
128
  self,
128
- session_state: Dict[str, Any],
129
+ run_context: RunContext,
129
130
  input: RunWorkflowInput,
130
131
  ) -> str:
131
132
  """Use this tool to execute the workflow with the specified inputs and parameters.
132
133
  After thinking through the requirements, use this tool to run the workflow with appropriate inputs.
134
+
133
135
  Args:
134
- input_data: The input data for the workflow.
136
+ input: The input data for the workflow.
135
137
  """
138
+ if isinstance(input, dict):
139
+ input = RunWorkflowInput.model_validate(input)
140
+
136
141
  try:
137
142
  log_debug(f"Running workflow with input: {input.input_data}")
138
143
 
139
- user_id = session_state.get("current_user_id")
140
- session_id = session_state.get("current_session_id")
144
+ if run_context.session_state is None:
145
+ run_context.session_state = {}
141
146
 
142
147
  # Execute the workflow
143
148
  result: WorkflowRunOutput = self.workflow.run(
144
149
  input=input.input_data,
145
- user_id=user_id,
146
- session_id=session_id,
147
- session_state=session_state,
150
+ user_id=run_context.user_id,
151
+ session_id=run_context.session_id,
152
+ session_state=run_context.session_state,
148
153
  additional_data=input.additional_data,
149
154
  )
150
155
 
151
- if "workflow_results" not in session_state:
152
- session_state["workflow_results"] = []
156
+ if "workflow_results" not in run_context.session_state:
157
+ run_context.session_state["workflow_results"] = []
153
158
 
154
- session_state["workflow_results"].append(result.to_dict())
159
+ run_context.session_state["workflow_results"].append(result.to_dict())
155
160
 
156
161
  return json.dumps(result.to_dict(), indent=2)
157
162
 
@@ -161,7 +166,7 @@ class WorkflowTools(Toolkit):
161
166
 
162
167
  async def async_run_workflow(
163
168
  self,
164
- session_state: Dict[str, Any],
169
+ run_context: RunContext,
165
170
  input: RunWorkflowInput,
166
171
  ) -> str:
167
172
  """Use this tool to execute the workflow with the specified inputs and parameters.
@@ -170,25 +175,28 @@ class WorkflowTools(Toolkit):
170
175
  input_data: The input data for the workflow (use a `str` for a simple input)
171
176
  additional_data: The additional data for the workflow. This is a dictionary of key-value pairs that will be passed to the workflow. E.g. {"topic": "food", "style": "Humour"}
172
177
  """
178
+ if isinstance(input, dict):
179
+ input = RunWorkflowInput.model_validate(input)
180
+
173
181
  try:
174
182
  log_debug(f"Running workflow with input: {input.input_data}")
175
183
 
176
- user_id = session_state.get("current_user_id")
177
- session_id = session_state.get("current_session_id")
184
+ if run_context.session_state is None:
185
+ run_context.session_state = {}
178
186
 
179
187
  # Execute the workflow
180
188
  result: WorkflowRunOutput = await self.workflow.arun(
181
189
  input=input.input_data,
182
- user_id=user_id,
183
- session_id=session_id,
184
- session_state=session_state,
190
+ user_id=run_context.user_id,
191
+ session_id=run_context.session_id,
192
+ session_state=run_context.session_state,
185
193
  additional_data=input.additional_data,
186
194
  )
187
195
 
188
- if "workflow_results" not in session_state:
189
- session_state["workflow_results"] = []
196
+ if "workflow_results" not in run_context.session_state:
197
+ run_context.session_state["workflow_results"] = []
190
198
 
191
- session_state["workflow_results"].append(result.to_dict())
199
+ run_context.session_state["workflow_results"].append(result.to_dict())
192
200
 
193
201
  return json.dumps(result.to_dict(), indent=2)
194
202
 
@@ -196,7 +204,7 @@ class WorkflowTools(Toolkit):
196
204
  log_error(f"Error running workflow: {e}")
197
205
  return f"Error running workflow: {e}"
198
206
 
199
- def analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
207
+ def analyze(self, run_context: RunContext, analysis: str) -> str:
200
208
  """Use this tool to evaluate whether the workflow execution results are correct and sufficient.
201
209
  If not, go back to "Think" or "Run" with refined inputs or parameters.
202
210
  Args:
@@ -206,14 +214,14 @@ class WorkflowTools(Toolkit):
206
214
  log_debug(f"Workflow Analysis: {analysis}")
207
215
 
208
216
  # Add the analysis to the session state
209
- if session_state is None:
210
- session_state = {}
211
- if "workflow_analysis" not in session_state:
212
- session_state["workflow_analysis"] = []
213
- session_state["workflow_analysis"].append(analysis)
217
+ if run_context.session_state is None:
218
+ run_context.session_state = {}
219
+ if "workflow_analysis" not in run_context.session_state:
220
+ run_context.session_state["workflow_analysis"] = []
221
+ run_context.session_state["workflow_analysis"].append(analysis)
214
222
 
215
223
  # Return the full log of analysis and the new analysis
216
- analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
224
+ analysis_log = "\n".join([f"- {a}" for a in run_context.session_state["workflow_analysis"]])
217
225
  formatted_analysis = dedent(
218
226
  f"""Workflow Analysis:
219
227
  {analysis_log}
@@ -224,7 +232,7 @@ class WorkflowTools(Toolkit):
224
232
  log_error(f"Error recording workflow analysis: {e}")
225
233
  return f"Error recording workflow analysis: {e}"
226
234
 
227
- async def async_analyze(self, session_state: Dict[str, Any], analysis: str) -> str:
235
+ async def async_analyze(self, run_context: RunContext, analysis: str) -> str:
228
236
  """Use this tool to evaluate whether the workflow execution results are correct and sufficient.
229
237
  If not, go back to "Think" or "Run" with refined inputs or parameters.
230
238
  Args:
@@ -234,14 +242,14 @@ class WorkflowTools(Toolkit):
234
242
  log_debug(f"Workflow Analysis: {analysis}")
235
243
 
236
244
  # Add the analysis to the session state
237
- if session_state is None:
238
- session_state = {}
239
- if "workflow_analysis" not in session_state:
240
- session_state["workflow_analysis"] = []
241
- session_state["workflow_analysis"].append(analysis)
245
+ if run_context.session_state is None:
246
+ run_context.session_state = {}
247
+ if "workflow_analysis" not in run_context.session_state:
248
+ run_context.session_state["workflow_analysis"] = []
249
+ run_context.session_state["workflow_analysis"].append(analysis)
242
250
 
243
251
  # Return the full log of analysis and the new analysis
244
- analysis_log = "\n".join([f"- {a}" for a in session_state["workflow_analysis"]])
252
+ analysis_log = "\n".join([f"- {a}" for a in run_context.session_state["workflow_analysis"]])
245
253
  formatted_analysis = dedent(
246
254
  f"""Workflow Analysis:
247
255
  {analysis_log}
@@ -255,7 +263,7 @@ class WorkflowTools(Toolkit):
255
263
  DEFAULT_INSTRUCTIONS = dedent("""\
256
264
  You have access to the Think, Run Workflow, and Analyze tools that will help you execute workflows and analyze their results. Use these tools as frequently as needed to successfully complete workflow-based tasks.
257
265
  ## How to use the Think, Run Workflow, and Analyze tools:
258
-
266
+
259
267
  1. **Think**
260
268
  - Purpose: A scratchpad for planning workflow execution, brainstorming inputs, and refining your approach. You never reveal your "Think" content to the user.
261
269
  - Usage: Call `think` whenever you need to figure out what workflow inputs to use, analyze requirements, or decide on execution strategy before (or after) you run the workflow.
agno/tools/yfinance.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, List
2
+ from typing import Any, List, Optional
3
3
 
4
4
  from agno.tools import Toolkit
5
5
  from agno.utils.log import log_debug
@@ -18,6 +18,7 @@ class YFinanceTools(Toolkit):
18
18
 
19
19
  def __init__(
20
20
  self,
21
+ session: Optional[Any] = None,
21
22
  **kwargs,
22
23
  ):
23
24
  tools: List[Any] = [
@@ -31,7 +32,7 @@ class YFinanceTools(Toolkit):
31
32
  self.get_technical_indicators,
32
33
  self.get_historical_stock_prices,
33
34
  ]
34
-
35
+ self.session = session
35
36
  super().__init__(name="yfinance_tools", tools=tools, **kwargs)
36
37
 
37
38
  def get_current_stock_price(self, symbol: str) -> str:
@@ -46,7 +47,7 @@ class YFinanceTools(Toolkit):
46
47
  """
47
48
  try:
48
49
  log_debug(f"Fetching current price for {symbol}")
49
- stock = yf.Ticker(symbol)
50
+ stock = yf.Ticker(symbol, session=self.session)
50
51
  # Use "regularMarketPrice" for regular market hours, or "currentPrice" for pre/post market
51
52
  current_price = stock.info.get("regularMarketPrice", stock.info.get("currentPrice"))
52
53
  return f"{current_price:.4f}" if current_price else f"Could not fetch current price for {symbol}"
@@ -63,7 +64,7 @@ class YFinanceTools(Toolkit):
63
64
  str: JSON containing company profile and overview.
64
65
  """
65
66
  try:
66
- company_info_full = yf.Ticker(symbol).info
67
+ company_info_full = yf.Ticker(symbol, session=self.session).info
67
68
  if company_info_full is None:
68
69
  return f"Could not fetch company info for {symbol}"
69
70
 
@@ -120,7 +121,7 @@ class YFinanceTools(Toolkit):
120
121
  """
121
122
  try:
122
123
  log_debug(f"Fetching historical prices for {symbol}")
123
- stock = yf.Ticker(symbol)
124
+ stock = yf.Ticker(symbol, session=self.session)
124
125
  historical_price = stock.history(period=period, interval=interval)
125
126
  return historical_price.to_json(orient="index")
126
127
  except Exception as e:
@@ -150,7 +151,7 @@ class YFinanceTools(Toolkit):
150
151
  """
151
152
  try:
152
153
  log_debug(f"Fetching fundamentals for {symbol}")
153
- stock = yf.Ticker(symbol)
154
+ stock = yf.Ticker(symbol, session=self.session)
154
155
  info = stock.info
155
156
  fundamentals = {
156
157
  "symbol": symbol,
@@ -181,7 +182,7 @@ class YFinanceTools(Toolkit):
181
182
  """
182
183
  try:
183
184
  log_debug(f"Fetching income statements for {symbol}")
184
- stock = yf.Ticker(symbol)
185
+ stock = yf.Ticker(symbol, session=self.session)
185
186
  financials = stock.financials
186
187
  return financials.to_json(orient="index")
187
188
  except Exception as e:
@@ -198,7 +199,7 @@ class YFinanceTools(Toolkit):
198
199
  """
199
200
  try:
200
201
  log_debug(f"Fetching key financial ratios for {symbol}")
201
- stock = yf.Ticker(symbol)
202
+ stock = yf.Ticker(symbol, session=self.session)
202
203
  key_ratios = stock.info
203
204
  return json.dumps(key_ratios, indent=2)
204
205
  except Exception as e:
@@ -215,7 +216,7 @@ class YFinanceTools(Toolkit):
215
216
  """
216
217
  try:
217
218
  log_debug(f"Fetching analyst recommendations for {symbol}")
218
- stock = yf.Ticker(symbol)
219
+ stock = yf.Ticker(symbol, session=self.session)
219
220
  recommendations = stock.recommendations
220
221
  return recommendations.to_json(orient="index")
221
222
  except Exception as e:
@@ -233,7 +234,7 @@ class YFinanceTools(Toolkit):
233
234
  """
234
235
  try:
235
236
  log_debug(f"Fetching company news for {symbol}")
236
- news = yf.Ticker(symbol).news
237
+ news = yf.Ticker(symbol, session=self.session).news
237
238
  return json.dumps(news[:num_stories], indent=2)
238
239
  except Exception as e:
239
240
  return f"Error fetching company news for {symbol}: {e}"
@@ -251,7 +252,7 @@ class YFinanceTools(Toolkit):
251
252
  """
252
253
  try:
253
254
  log_debug(f"Fetching technical indicators for {symbol}")
254
- indicators = yf.Ticker(symbol).history(period=period)
255
+ indicators = yf.Ticker(symbol, session=self.session).history(period=period)
255
256
  return indicators.to_json(orient="index")
256
257
  except Exception as e:
257
258
  return f"Error fetching technical indicators for {symbol}: {e}"
@@ -0,0 +1,12 @@
1
+ """
2
+ Agno Tracing Module
3
+
4
+ This module provides OpenTelemetry-based tracing capabilities for Agno agents.
5
+ It uses the openinference-instrumentation-agno package for automatic instrumentation
6
+ and provides a custom DatabaseSpanExporter to store traces in the Agno database.
7
+ """
8
+
9
+ from agno.tracing.exporter import DatabaseSpanExporter
10
+ from agno.tracing.setup import setup_tracing
11
+
12
+ __all__ = ["DatabaseSpanExporter", "setup_tracing"]
@@ -0,0 +1,161 @@
1
+ """
2
+ Custom OpenTelemetry SpanExporter that writes traces to Agno database.
3
+ """
4
+
5
+ import asyncio
6
+ from collections import defaultdict
7
+ from typing import Dict, List, Sequence, Union
8
+
9
+ from opentelemetry.sdk.trace import ReadableSpan # type: ignore
10
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult # type: ignore
11
+
12
+ from agno.db.base import AsyncBaseDb, BaseDb
13
+ from agno.remote.base import RemoteDb
14
+ from agno.tracing.schemas import Span, create_trace_from_spans
15
+ from agno.utils.log import logger
16
+
17
+
18
+ class DatabaseSpanExporter(SpanExporter):
19
+ """Custom OpenTelemetry SpanExporter that writes to Agno database"""
20
+
21
+ def __init__(self, db: Union[BaseDb, AsyncBaseDb, RemoteDb]):
22
+ """
23
+ Initialize the DatabaseSpanExporter.
24
+
25
+ Args:
26
+ db: Database instance (sync or async) to store traces
27
+ """
28
+ self.db = db
29
+ self._shutdown = False
30
+
31
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
32
+ """
33
+ Export spans to the database.
34
+
35
+ This method:
36
+ 1. Converts OpenTelemetry spans to Span objects
37
+ 2. Groups spans by trace_id
38
+ 3. Creates Trace records (one per trace_id)
39
+ 4. Creates Span records (multiple per trace_id)
40
+
41
+ Args:
42
+ spans: Sequence of OpenTelemetry ReadableSpan objects
43
+
44
+ Returns:
45
+ SpanExportResult indicating success or failure
46
+ """
47
+ if self._shutdown:
48
+ logger.warning("DatabaseSpanExporter is shutdown, cannot export spans")
49
+ return SpanExportResult.FAILURE
50
+
51
+ if not spans:
52
+ return SpanExportResult.SUCCESS
53
+
54
+ try:
55
+ # Convert OpenTelemetry spans to Span objects
56
+ converted_spans: List[Span] = []
57
+ for span in spans:
58
+ try:
59
+ converted_span = Span.from_otel_span(span)
60
+ converted_spans.append(converted_span)
61
+ except Exception as e:
62
+ logger.error(f"Failed to convert span {span.name}: {e}")
63
+ # Continue processing other spans
64
+ continue
65
+
66
+ if not converted_spans:
67
+ return SpanExportResult.SUCCESS
68
+
69
+ # Group spans by trace_id
70
+ spans_by_trace: Dict[str, List[Span]] = defaultdict(list)
71
+ for converted_span in converted_spans:
72
+ spans_by_trace[converted_span.trace_id].append(converted_span)
73
+
74
+ # Handle async DB
75
+ if isinstance(self.db, RemoteDb):
76
+ # Skipping remote database because it handles its own tracing
77
+ pass
78
+ elif isinstance(self.db, AsyncBaseDb):
79
+ self._export_async(spans_by_trace)
80
+ else:
81
+ # Synchronous database
82
+ self._export_sync(spans_by_trace)
83
+
84
+ return SpanExportResult.SUCCESS
85
+ except Exception as e:
86
+ logger.error(f"Failed to export spans to database: {e}", exc_info=True)
87
+ return SpanExportResult.FAILURE
88
+
89
+ def _export_sync(self, spans_by_trace: Dict[str, List[Span]]) -> None:
90
+ """Export traces and spans to synchronous database"""
91
+ try:
92
+ # Create trace and span records for each trace
93
+ for trace_id, spans in spans_by_trace.items():
94
+ # Create trace record (aggregate of all spans)
95
+ trace = create_trace_from_spans(spans)
96
+ if trace:
97
+ self.db.upsert_trace(trace) # type: ignore
98
+
99
+ # Create span records
100
+ self.db.create_spans(spans) # type: ignore
101
+
102
+ except Exception as e:
103
+ logger.error(f"Failed to export sync traces: {e}", exc_info=True)
104
+ raise
105
+
106
+ def _export_async(self, spans_by_trace: Dict[str, List[Span]]) -> None:
107
+ """Handle async database export"""
108
+ try:
109
+ loop = asyncio.get_event_loop()
110
+ if loop.is_running():
111
+ # We're in an async context, schedule the coroutine
112
+ asyncio.create_task(self._do_async_export(spans_by_trace))
113
+ else:
114
+ # No running loop, run in new loop
115
+ loop.run_until_complete(self._do_async_export(spans_by_trace))
116
+ except RuntimeError:
117
+ # No event loop, create new one
118
+ try:
119
+ asyncio.run(self._do_async_export(spans_by_trace))
120
+ except Exception as e:
121
+ logger.error(f"Failed to export async traces: {e}", exc_info=True)
122
+
123
+ async def _do_async_export(self, spans_by_trace: Dict[str, List[Span]]) -> None:
124
+ """Actually perform the async export"""
125
+ try:
126
+ # Create trace and span records for each trace
127
+ for trace_id, spans in spans_by_trace.items():
128
+ # Create trace record (aggregate of all spans)
129
+ trace = create_trace_from_spans(spans)
130
+ if trace:
131
+ create_trace_result = self.db.upsert_trace(trace) # type: ignore
132
+ if create_trace_result is not None:
133
+ await create_trace_result
134
+
135
+ # Create span records
136
+ create_spans_result = self.db.create_spans(spans) # type: ignore
137
+ if create_spans_result is not None:
138
+ await create_spans_result
139
+
140
+ except Exception as e:
141
+ logger.error(f"Failed to do async export: {e}", exc_info=True)
142
+ raise
143
+
144
+ def shutdown(self) -> None:
145
+ """Shutdown the exporter"""
146
+ self._shutdown = True
147
+ logger.debug("DatabaseSpanExporter shutdown")
148
+
149
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
150
+ """
151
+ Force flush any pending spans.
152
+
153
+ Since we write immediately to the database, this is a no-op.
154
+
155
+ Args:
156
+ timeout_millis: Timeout in milliseconds
157
+
158
+ Returns:
159
+ True if flush was successful
160
+ """
161
+ return True