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/postgres.py CHANGED
@@ -14,6 +14,21 @@ from agno.utils.log import log_debug, log_error
14
14
 
15
15
 
16
16
  class PostgresTools(Toolkit):
17
+ """
18
+ A toolkit for interacting with PostgreSQL databases.
19
+
20
+ Args:
21
+ connection (Optional[PgConnection[DictRow]]): Existing database connection to reuse.
22
+ db_name (Optional[str]): Database name to connect to.
23
+ user (Optional[str]): Username for authentication.
24
+ password (Optional[str]): Password for authentication.
25
+ host (Optional[str]): PostgreSQL server hostname.
26
+ port (Optional[int]): PostgreSQL server port number.
27
+ table_schema (str): Default schema for table operations. Default is "public".
28
+ """
29
+
30
+ _requires_connect: bool = True
31
+
17
32
  def __init__(
18
33
  self,
19
34
  connection: Optional[PgConnection[DictRow]] = None,
@@ -44,50 +59,71 @@ class PostgresTools(Toolkit):
44
59
 
45
60
  super().__init__(name="postgres_tools", tools=tools, **kwargs)
46
61
 
47
- @property
48
- def connection(self) -> PgConnection[DictRow]:
49
- """
50
- Returns the Postgres psycopg connection.
51
- :return psycopg.connection.Connection: psycopg connection
62
+ def connect(self) -> PgConnection[DictRow]:
52
63
  """
53
- if self._connection is None or self._connection.closed:
54
- log_debug("Establishing new PostgreSQL connection.")
55
- connection_kwargs: Dict[str, Any] = {"row_factory": dict_row}
56
- if self.db_name:
57
- connection_kwargs["dbname"] = self.db_name
58
- if self.user:
59
- connection_kwargs["user"] = self.user
60
- if self.password:
61
- connection_kwargs["password"] = self.password
62
- if self.host:
63
- connection_kwargs["host"] = self.host
64
- if self.port:
65
- connection_kwargs["port"] = self.port
66
-
67
- connection_kwargs["options"] = f"-c search_path={self.table_schema}"
68
-
69
- self._connection = psycopg.connect(**connection_kwargs)
70
- self._connection.read_only = True
64
+ Establish a connection to the PostgreSQL database.
71
65
 
66
+ Returns:
67
+ The database connection object.
68
+ """
69
+ if self._connection is not None and not self._connection.closed:
70
+ log_debug("Connection already established, reusing existing connection")
71
+ return self._connection
72
+
73
+ log_debug("Establishing new PostgreSQL connection.")
74
+ connection_kwargs: Dict[str, Any] = {"row_factory": dict_row}
75
+ if self.db_name:
76
+ connection_kwargs["dbname"] = self.db_name
77
+ if self.user:
78
+ connection_kwargs["user"] = self.user
79
+ if self.password:
80
+ connection_kwargs["password"] = self.password
81
+ if self.host:
82
+ connection_kwargs["host"] = self.host
83
+ if self.port:
84
+ connection_kwargs["port"] = self.port
85
+
86
+ connection_kwargs["options"] = f"-c search_path={self.table_schema}"
87
+
88
+ self._connection = psycopg.connect(**connection_kwargs)
89
+ self._connection.read_only = True
72
90
  return self._connection
73
91
 
74
- def __enter__(self):
75
- return self
76
-
77
- def __exit__(self, exc_type, exc_val, exc_tb):
78
- self.close()
79
-
80
- def close(self):
92
+ def close(self) -> None:
81
93
  """Closes the database connection if it's open."""
82
94
  if self._connection and not self._connection.closed:
83
95
  log_debug("Closing PostgreSQL connection.")
84
96
  self._connection.close()
85
97
  self._connection = None
86
98
 
99
+ @property
100
+ def is_connected(self) -> bool:
101
+ """Check if a connection is currently established."""
102
+ return self._connection is not None and not self._connection.closed
103
+
104
+ def _ensure_connection(self) -> PgConnection[DictRow]:
105
+ """
106
+ Ensure a connection exists, creating one if necessary.
107
+
108
+ Returns:
109
+ The database connection object.
110
+ """
111
+ if not self.is_connected:
112
+ return self.connect()
113
+ return self._connection # type: ignore
114
+
115
+ def __enter__(self):
116
+ return self.connect()
117
+
118
+ def __exit__(self, exc_type, exc_val, exc_tb):
119
+ if self.is_connected:
120
+ self.close()
121
+
87
122
  def _execute_query(self, query: str, params: Optional[tuple] = None) -> str:
88
123
  try:
89
- with self.connection.cursor() as cursor:
90
- log_debug(f"Running PostgreSQL Query: {query} with Params: {params}")
124
+ connection = self._ensure_connection()
125
+ with connection.cursor() as cursor:
126
+ log_debug("Running PostgreSQL query")
91
127
  cursor.execute(query, params)
92
128
 
93
129
  if cursor.description is None:
@@ -105,8 +141,8 @@ class PostgresTools(Toolkit):
105
141
 
106
142
  except psycopg.Error as e:
107
143
  log_error(f"Database error: {e}")
108
- if self.connection and not self.connection.closed:
109
- self.connection.rollback()
144
+ if self._connection and not self._connection.closed:
145
+ self._connection.rollback()
110
146
  return f"Error executing query: {e}"
111
147
  except Exception as e:
112
148
  log_error(f"An unexpected error occurred: {e}")
@@ -146,7 +182,8 @@ class PostgresTools(Toolkit):
146
182
  A string containing a summary of the table.
147
183
  """
148
184
  try:
149
- with self.connection.cursor() as cursor:
185
+ connection = self._ensure_connection()
186
+ with connection.cursor() as cursor:
150
187
  # First, get column information using a parameterized query
151
188
  schema_query = """
152
189
  SELECT column_name, data_type
@@ -230,7 +267,8 @@ class PostgresTools(Toolkit):
230
267
  stmt = sql.SQL("SELECT * FROM {tbl};").format(tbl=table_identifier)
231
268
 
232
269
  try:
233
- with self.connection.cursor() as cursor:
270
+ connection = self._ensure_connection()
271
+ with connection.cursor() as cursor:
234
272
  cursor.execute(stmt)
235
273
 
236
274
  if cursor.description is None:
@@ -245,6 +283,8 @@ class PostgresTools(Toolkit):
245
283
 
246
284
  return f"Successfully exported table '{table}' to '{path}'."
247
285
  except (psycopg.Error, IOError) as e:
286
+ if self._connection and not self._connection.closed:
287
+ self._connection.rollback()
248
288
  return f"Error exporting table: {e}"
249
289
 
250
290
  def run_query(self, query: str) -> str:
agno/tools/python.py CHANGED
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
  from typing import Any, List, Optional
5
5
 
6
6
  from agno.tools import Toolkit
7
- from agno.utils.log import log_debug, log_info, logger
7
+ from agno.utils.log import log_debug, log_error, log_info, logger
8
8
 
9
9
 
10
10
  @functools.lru_cache(maxsize=None)
@@ -18,9 +18,11 @@ class PythonTools(Toolkit):
18
18
  base_dir: Optional[Path] = None,
19
19
  safe_globals: Optional[dict] = None,
20
20
  safe_locals: Optional[dict] = None,
21
+ restrict_to_base_dir: bool = True,
21
22
  **kwargs,
22
23
  ):
23
- self.base_dir: Path = base_dir or Path.cwd()
24
+ self.base_dir: Path = (base_dir or Path.cwd()).resolve()
25
+ self.restrict_to_base_dir = restrict_to_base_dir
24
26
 
25
27
  # Restricted global and local scope
26
28
  self.safe_globals: dict = safe_globals or globals()
@@ -55,7 +57,9 @@ class PythonTools(Toolkit):
55
57
  """
56
58
  try:
57
59
  warn()
58
- file_path = self.base_dir.joinpath(file_name)
60
+ safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
61
+ if not safe:
62
+ return f"Error: Path '{file_name}' is outside the allowed base directory"
59
63
  log_debug(f"Saving code to {file_path}")
60
64
  if not file_path.parent.exists():
61
65
  file_path.parent.mkdir(parents=True, exist_ok=True)
@@ -89,8 +93,9 @@ class PythonTools(Toolkit):
89
93
  """
90
94
  try:
91
95
  warn()
92
- file_path = self.base_dir.joinpath(file_name)
93
-
96
+ safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
97
+ if not safe:
98
+ return f"Error: Path '{file_name}' is outside the allowed base directory"
94
99
  log_info(f"Running {file_path}")
95
100
  globals_after_run = runpy.run_path(str(file_path), init_globals=self.safe_globals, run_name="__main__")
96
101
  if variable_to_return:
@@ -113,7 +118,10 @@ class PythonTools(Toolkit):
113
118
  """
114
119
  try:
115
120
  log_info(f"Reading file: {file_name}")
116
- file_path = self.base_dir.joinpath(file_name)
121
+ safe, file_path = self._check_path(file_name, self.base_dir, self.restrict_to_base_dir)
122
+ if not safe:
123
+ log_error(f"Attempted to read file outside base directory: {file_name}")
124
+ return "Error reading file: path outside allowed directory"
117
125
  contents = file_path.read_text(encoding="utf-8")
118
126
  return str(contents)
119
127
  except Exception as e:
agno/tools/reasoning.py CHANGED
@@ -1,7 +1,8 @@
1
1
  from textwrap import dedent
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Any, List, Optional
3
3
 
4
4
  from agno.reasoning.step import NextAction, ReasoningStep
5
+ from agno.run import RunContext
5
6
  from agno.tools import Toolkit
6
7
  from agno.utils.log import log_debug, log_error
7
8
 
@@ -49,7 +50,7 @@ class ReasoningTools(Toolkit):
49
50
 
50
51
  def think(
51
52
  self,
52
- session_state: Dict[str, Any],
53
+ run_context: RunContext,
53
54
  title: str,
54
55
  thought: str,
55
56
  action: Optional[str] = None,
@@ -80,21 +81,24 @@ class ReasoningTools(Toolkit):
80
81
  confidence=confidence,
81
82
  )
82
83
 
83
- current_run_id = session_state.get("current_run_id", None)
84
+ current_run_id = run_context.run_id
84
85
 
85
86
  # Add this step to the Agent's session state
86
- if session_state is None:
87
- session_state = {}
88
- if "reasoning_steps" not in session_state:
89
- session_state["reasoning_steps"] = {}
90
- if current_run_id not in session_state["reasoning_steps"]:
91
- session_state["reasoning_steps"][current_run_id] = []
92
- session_state["reasoning_steps"][current_run_id].append(reasoning_step.model_dump_json())
87
+ if run_context.session_state is None:
88
+ run_context.session_state = {}
89
+ if "reasoning_steps" not in run_context.session_state:
90
+ run_context.session_state["reasoning_steps"] = {}
91
+ if current_run_id not in run_context.session_state["reasoning_steps"]:
92
+ run_context.session_state["reasoning_steps"][current_run_id] = []
93
+ run_context.session_state["reasoning_steps"][current_run_id].append(reasoning_step.model_dump_json())
93
94
 
94
95
  # Return all previous reasoning_steps and the new reasoning_step
95
- if "reasoning_steps" in session_state and current_run_id in session_state["reasoning_steps"]:
96
+ if (
97
+ "reasoning_steps" in run_context.session_state
98
+ and current_run_id in run_context.session_state["reasoning_steps"]
99
+ ):
96
100
  formatted_reasoning_steps = ""
97
- for i, step in enumerate(session_state["reasoning_steps"][current_run_id], 1):
101
+ for i, step in enumerate(run_context.session_state["reasoning_steps"][current_run_id], 1):
98
102
  step_parsed = ReasoningStep.model_validate_json(step)
99
103
  step_str = dedent(f"""\
100
104
  Step {i}:
@@ -112,7 +116,7 @@ Confidence: {step_parsed.confidence}
112
116
 
113
117
  def analyze(
114
118
  self,
115
- session_state: Dict[str, Any],
119
+ run_context: RunContext,
116
120
  title: str,
117
121
  result: str,
118
122
  analysis: str,
@@ -150,20 +154,23 @@ Confidence: {step_parsed.confidence}
150
154
  confidence=confidence,
151
155
  )
152
156
 
153
- current_run_id = session_state.get("current_run_id", None)
157
+ current_run_id = run_context.run_id
154
158
  # Add this step to the Agent's session state
155
- if session_state is None:
156
- session_state = {}
157
- if "reasoning_steps" not in session_state:
158
- session_state["reasoning_steps"] = {}
159
- if current_run_id not in session_state["reasoning_steps"]:
160
- session_state["reasoning_steps"][current_run_id] = []
161
- session_state["reasoning_steps"][current_run_id].append(reasoning_step.model_dump_json())
159
+ if run_context.session_state is None:
160
+ run_context.session_state = {}
161
+ if "reasoning_steps" not in run_context.session_state:
162
+ run_context.session_state["reasoning_steps"] = {}
163
+ if current_run_id not in run_context.session_state["reasoning_steps"]:
164
+ run_context.session_state["reasoning_steps"][current_run_id] = []
165
+ run_context.session_state["reasoning_steps"][current_run_id].append(reasoning_step.model_dump_json())
162
166
 
163
167
  # Return all previous reasoning_steps and the new reasoning_step
164
- if "reasoning_steps" in session_state and current_run_id in session_state["reasoning_steps"]:
168
+ if (
169
+ "reasoning_steps" in run_context.session_state
170
+ and current_run_id in run_context.session_state["reasoning_steps"]
171
+ ):
165
172
  formatted_reasoning_steps = ""
166
- for i, step in enumerate(session_state["reasoning_steps"][current_run_id], 1):
173
+ for i, step in enumerate(run_context.session_state["reasoning_steps"][current_run_id], 1):
167
174
  step_parsed = ReasoningStep.model_validate_json(step)
168
175
  step_str = dedent(f"""\
169
176
  Step {i}: