agno 2.2.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 (575) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +51 -0
  3. agno/agent/agent.py +10405 -0
  4. agno/api/__init__.py +0 -0
  5. agno/api/agent.py +28 -0
  6. agno/api/api.py +40 -0
  7. agno/api/evals.py +22 -0
  8. agno/api/os.py +17 -0
  9. agno/api/routes.py +13 -0
  10. agno/api/schemas/__init__.py +9 -0
  11. agno/api/schemas/agent.py +16 -0
  12. agno/api/schemas/evals.py +16 -0
  13. agno/api/schemas/os.py +14 -0
  14. agno/api/schemas/response.py +6 -0
  15. agno/api/schemas/team.py +16 -0
  16. agno/api/schemas/utils.py +21 -0
  17. agno/api/schemas/workflows.py +16 -0
  18. agno/api/settings.py +53 -0
  19. agno/api/team.py +30 -0
  20. agno/api/workflow.py +28 -0
  21. agno/cloud/aws/base.py +214 -0
  22. agno/cloud/aws/s3/__init__.py +2 -0
  23. agno/cloud/aws/s3/api_client.py +43 -0
  24. agno/cloud/aws/s3/bucket.py +195 -0
  25. agno/cloud/aws/s3/object.py +57 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +598 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2042 -0
  33. agno/db/dynamo/schemas.py +314 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +1795 -0
  37. agno/db/firestore/schemas.py +140 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1335 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1160 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1328 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/__init__.py +0 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/mongo/__init__.py +17 -0
  51. agno/db/mongo/async_mongo.py +2026 -0
  52. agno/db/mongo/mongo.py +1982 -0
  53. agno/db/mongo/schemas.py +87 -0
  54. agno/db/mongo/utils.py +259 -0
  55. agno/db/mysql/__init__.py +3 -0
  56. agno/db/mysql/mysql.py +2308 -0
  57. agno/db/mysql/schemas.py +138 -0
  58. agno/db/mysql/utils.py +355 -0
  59. agno/db/postgres/__init__.py +4 -0
  60. agno/db/postgres/async_postgres.py +1927 -0
  61. agno/db/postgres/postgres.py +2260 -0
  62. agno/db/postgres/schemas.py +139 -0
  63. agno/db/postgres/utils.py +442 -0
  64. agno/db/redis/__init__.py +3 -0
  65. agno/db/redis/redis.py +1660 -0
  66. agno/db/redis/schemas.py +123 -0
  67. agno/db/redis/utils.py +346 -0
  68. agno/db/schemas/__init__.py +4 -0
  69. agno/db/schemas/culture.py +120 -0
  70. agno/db/schemas/evals.py +33 -0
  71. agno/db/schemas/knowledge.py +40 -0
  72. agno/db/schemas/memory.py +46 -0
  73. agno/db/schemas/metrics.py +0 -0
  74. agno/db/singlestore/__init__.py +3 -0
  75. agno/db/singlestore/schemas.py +130 -0
  76. agno/db/singlestore/singlestore.py +2272 -0
  77. agno/db/singlestore/utils.py +384 -0
  78. agno/db/sqlite/__init__.py +4 -0
  79. agno/db/sqlite/async_sqlite.py +2293 -0
  80. agno/db/sqlite/schemas.py +133 -0
  81. agno/db/sqlite/sqlite.py +2288 -0
  82. agno/db/sqlite/utils.py +431 -0
  83. agno/db/surrealdb/__init__.py +3 -0
  84. agno/db/surrealdb/metrics.py +292 -0
  85. agno/db/surrealdb/models.py +309 -0
  86. agno/db/surrealdb/queries.py +71 -0
  87. agno/db/surrealdb/surrealdb.py +1353 -0
  88. agno/db/surrealdb/utils.py +147 -0
  89. agno/db/utils.py +116 -0
  90. agno/debug.py +18 -0
  91. agno/eval/__init__.py +14 -0
  92. agno/eval/accuracy.py +834 -0
  93. agno/eval/performance.py +773 -0
  94. agno/eval/reliability.py +306 -0
  95. agno/eval/utils.py +119 -0
  96. agno/exceptions.py +161 -0
  97. agno/filters.py +354 -0
  98. agno/guardrails/__init__.py +6 -0
  99. agno/guardrails/base.py +19 -0
  100. agno/guardrails/openai.py +144 -0
  101. agno/guardrails/pii.py +94 -0
  102. agno/guardrails/prompt_injection.py +52 -0
  103. agno/integrations/__init__.py +0 -0
  104. agno/integrations/discord/__init__.py +3 -0
  105. agno/integrations/discord/client.py +203 -0
  106. agno/knowledge/__init__.py +5 -0
  107. agno/knowledge/chunking/__init__.py +0 -0
  108. agno/knowledge/chunking/agentic.py +79 -0
  109. agno/knowledge/chunking/document.py +91 -0
  110. agno/knowledge/chunking/fixed.py +57 -0
  111. agno/knowledge/chunking/markdown.py +151 -0
  112. agno/knowledge/chunking/recursive.py +63 -0
  113. agno/knowledge/chunking/row.py +39 -0
  114. agno/knowledge/chunking/semantic.py +86 -0
  115. agno/knowledge/chunking/strategy.py +165 -0
  116. agno/knowledge/content.py +74 -0
  117. agno/knowledge/document/__init__.py +5 -0
  118. agno/knowledge/document/base.py +58 -0
  119. agno/knowledge/embedder/__init__.py +5 -0
  120. agno/knowledge/embedder/aws_bedrock.py +343 -0
  121. agno/knowledge/embedder/azure_openai.py +210 -0
  122. agno/knowledge/embedder/base.py +23 -0
  123. agno/knowledge/embedder/cohere.py +323 -0
  124. agno/knowledge/embedder/fastembed.py +62 -0
  125. agno/knowledge/embedder/fireworks.py +13 -0
  126. agno/knowledge/embedder/google.py +258 -0
  127. agno/knowledge/embedder/huggingface.py +94 -0
  128. agno/knowledge/embedder/jina.py +182 -0
  129. agno/knowledge/embedder/langdb.py +22 -0
  130. agno/knowledge/embedder/mistral.py +206 -0
  131. agno/knowledge/embedder/nebius.py +13 -0
  132. agno/knowledge/embedder/ollama.py +154 -0
  133. agno/knowledge/embedder/openai.py +195 -0
  134. agno/knowledge/embedder/sentence_transformer.py +63 -0
  135. agno/knowledge/embedder/together.py +13 -0
  136. agno/knowledge/embedder/vllm.py +262 -0
  137. agno/knowledge/embedder/voyageai.py +165 -0
  138. agno/knowledge/knowledge.py +1988 -0
  139. agno/knowledge/reader/__init__.py +7 -0
  140. agno/knowledge/reader/arxiv_reader.py +81 -0
  141. agno/knowledge/reader/base.py +95 -0
  142. agno/knowledge/reader/csv_reader.py +166 -0
  143. agno/knowledge/reader/docx_reader.py +82 -0
  144. agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
  145. agno/knowledge/reader/firecrawl_reader.py +201 -0
  146. agno/knowledge/reader/json_reader.py +87 -0
  147. agno/knowledge/reader/markdown_reader.py +137 -0
  148. agno/knowledge/reader/pdf_reader.py +431 -0
  149. agno/knowledge/reader/pptx_reader.py +101 -0
  150. agno/knowledge/reader/reader_factory.py +313 -0
  151. agno/knowledge/reader/s3_reader.py +89 -0
  152. agno/knowledge/reader/tavily_reader.py +194 -0
  153. agno/knowledge/reader/text_reader.py +115 -0
  154. agno/knowledge/reader/web_search_reader.py +372 -0
  155. agno/knowledge/reader/website_reader.py +455 -0
  156. agno/knowledge/reader/wikipedia_reader.py +59 -0
  157. agno/knowledge/reader/youtube_reader.py +78 -0
  158. agno/knowledge/remote_content/__init__.py +0 -0
  159. agno/knowledge/remote_content/remote_content.py +88 -0
  160. agno/knowledge/reranker/__init__.py +3 -0
  161. agno/knowledge/reranker/base.py +14 -0
  162. agno/knowledge/reranker/cohere.py +64 -0
  163. agno/knowledge/reranker/infinity.py +195 -0
  164. agno/knowledge/reranker/sentence_transformer.py +54 -0
  165. agno/knowledge/types.py +39 -0
  166. agno/knowledge/utils.py +189 -0
  167. agno/media.py +462 -0
  168. agno/memory/__init__.py +3 -0
  169. agno/memory/manager.py +1327 -0
  170. agno/models/__init__.py +0 -0
  171. agno/models/aimlapi/__init__.py +5 -0
  172. agno/models/aimlapi/aimlapi.py +45 -0
  173. agno/models/anthropic/__init__.py +5 -0
  174. agno/models/anthropic/claude.py +757 -0
  175. agno/models/aws/__init__.py +15 -0
  176. agno/models/aws/bedrock.py +701 -0
  177. agno/models/aws/claude.py +378 -0
  178. agno/models/azure/__init__.py +18 -0
  179. agno/models/azure/ai_foundry.py +485 -0
  180. agno/models/azure/openai_chat.py +131 -0
  181. agno/models/base.py +2175 -0
  182. agno/models/cerebras/__init__.py +12 -0
  183. agno/models/cerebras/cerebras.py +501 -0
  184. agno/models/cerebras/cerebras_openai.py +112 -0
  185. agno/models/cohere/__init__.py +5 -0
  186. agno/models/cohere/chat.py +389 -0
  187. agno/models/cometapi/__init__.py +5 -0
  188. agno/models/cometapi/cometapi.py +57 -0
  189. agno/models/dashscope/__init__.py +5 -0
  190. agno/models/dashscope/dashscope.py +91 -0
  191. agno/models/deepinfra/__init__.py +5 -0
  192. agno/models/deepinfra/deepinfra.py +28 -0
  193. agno/models/deepseek/__init__.py +5 -0
  194. agno/models/deepseek/deepseek.py +61 -0
  195. agno/models/defaults.py +1 -0
  196. agno/models/fireworks/__init__.py +5 -0
  197. agno/models/fireworks/fireworks.py +26 -0
  198. agno/models/google/__init__.py +5 -0
  199. agno/models/google/gemini.py +1085 -0
  200. agno/models/groq/__init__.py +5 -0
  201. agno/models/groq/groq.py +556 -0
  202. agno/models/huggingface/__init__.py +5 -0
  203. agno/models/huggingface/huggingface.py +491 -0
  204. agno/models/ibm/__init__.py +5 -0
  205. agno/models/ibm/watsonx.py +422 -0
  206. agno/models/internlm/__init__.py +3 -0
  207. agno/models/internlm/internlm.py +26 -0
  208. agno/models/langdb/__init__.py +1 -0
  209. agno/models/langdb/langdb.py +48 -0
  210. agno/models/litellm/__init__.py +14 -0
  211. agno/models/litellm/chat.py +468 -0
  212. agno/models/litellm/litellm_openai.py +25 -0
  213. agno/models/llama_cpp/__init__.py +5 -0
  214. agno/models/llama_cpp/llama_cpp.py +22 -0
  215. agno/models/lmstudio/__init__.py +5 -0
  216. agno/models/lmstudio/lmstudio.py +25 -0
  217. agno/models/message.py +434 -0
  218. agno/models/meta/__init__.py +12 -0
  219. agno/models/meta/llama.py +475 -0
  220. agno/models/meta/llama_openai.py +78 -0
  221. agno/models/metrics.py +120 -0
  222. agno/models/mistral/__init__.py +5 -0
  223. agno/models/mistral/mistral.py +432 -0
  224. agno/models/nebius/__init__.py +3 -0
  225. agno/models/nebius/nebius.py +54 -0
  226. agno/models/nexus/__init__.py +3 -0
  227. agno/models/nexus/nexus.py +22 -0
  228. agno/models/nvidia/__init__.py +5 -0
  229. agno/models/nvidia/nvidia.py +28 -0
  230. agno/models/ollama/__init__.py +5 -0
  231. agno/models/ollama/chat.py +441 -0
  232. agno/models/openai/__init__.py +9 -0
  233. agno/models/openai/chat.py +883 -0
  234. agno/models/openai/like.py +27 -0
  235. agno/models/openai/responses.py +1050 -0
  236. agno/models/openrouter/__init__.py +5 -0
  237. agno/models/openrouter/openrouter.py +66 -0
  238. agno/models/perplexity/__init__.py +5 -0
  239. agno/models/perplexity/perplexity.py +187 -0
  240. agno/models/portkey/__init__.py +3 -0
  241. agno/models/portkey/portkey.py +81 -0
  242. agno/models/requesty/__init__.py +5 -0
  243. agno/models/requesty/requesty.py +52 -0
  244. agno/models/response.py +199 -0
  245. agno/models/sambanova/__init__.py +5 -0
  246. agno/models/sambanova/sambanova.py +28 -0
  247. agno/models/siliconflow/__init__.py +5 -0
  248. agno/models/siliconflow/siliconflow.py +25 -0
  249. agno/models/together/__init__.py +5 -0
  250. agno/models/together/together.py +25 -0
  251. agno/models/utils.py +266 -0
  252. agno/models/vercel/__init__.py +3 -0
  253. agno/models/vercel/v0.py +26 -0
  254. agno/models/vertexai/__init__.py +0 -0
  255. agno/models/vertexai/claude.py +70 -0
  256. agno/models/vllm/__init__.py +3 -0
  257. agno/models/vllm/vllm.py +78 -0
  258. agno/models/xai/__init__.py +3 -0
  259. agno/models/xai/xai.py +113 -0
  260. agno/os/__init__.py +3 -0
  261. agno/os/app.py +876 -0
  262. agno/os/auth.py +57 -0
  263. agno/os/config.py +104 -0
  264. agno/os/interfaces/__init__.py +1 -0
  265. agno/os/interfaces/a2a/__init__.py +3 -0
  266. agno/os/interfaces/a2a/a2a.py +42 -0
  267. agno/os/interfaces/a2a/router.py +250 -0
  268. agno/os/interfaces/a2a/utils.py +924 -0
  269. agno/os/interfaces/agui/__init__.py +3 -0
  270. agno/os/interfaces/agui/agui.py +47 -0
  271. agno/os/interfaces/agui/router.py +144 -0
  272. agno/os/interfaces/agui/utils.py +534 -0
  273. agno/os/interfaces/base.py +25 -0
  274. agno/os/interfaces/slack/__init__.py +3 -0
  275. agno/os/interfaces/slack/router.py +148 -0
  276. agno/os/interfaces/slack/security.py +30 -0
  277. agno/os/interfaces/slack/slack.py +47 -0
  278. agno/os/interfaces/whatsapp/__init__.py +3 -0
  279. agno/os/interfaces/whatsapp/router.py +211 -0
  280. agno/os/interfaces/whatsapp/security.py +53 -0
  281. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  282. agno/os/mcp.py +292 -0
  283. agno/os/middleware/__init__.py +7 -0
  284. agno/os/middleware/jwt.py +233 -0
  285. agno/os/router.py +1763 -0
  286. agno/os/routers/__init__.py +3 -0
  287. agno/os/routers/evals/__init__.py +3 -0
  288. agno/os/routers/evals/evals.py +430 -0
  289. agno/os/routers/evals/schemas.py +142 -0
  290. agno/os/routers/evals/utils.py +162 -0
  291. agno/os/routers/health.py +31 -0
  292. agno/os/routers/home.py +52 -0
  293. agno/os/routers/knowledge/__init__.py +3 -0
  294. agno/os/routers/knowledge/knowledge.py +997 -0
  295. agno/os/routers/knowledge/schemas.py +178 -0
  296. agno/os/routers/memory/__init__.py +3 -0
  297. agno/os/routers/memory/memory.py +515 -0
  298. agno/os/routers/memory/schemas.py +62 -0
  299. agno/os/routers/metrics/__init__.py +3 -0
  300. agno/os/routers/metrics/metrics.py +190 -0
  301. agno/os/routers/metrics/schemas.py +47 -0
  302. agno/os/routers/session/__init__.py +3 -0
  303. agno/os/routers/session/session.py +997 -0
  304. agno/os/schema.py +1055 -0
  305. agno/os/settings.py +43 -0
  306. agno/os/utils.py +630 -0
  307. agno/py.typed +0 -0
  308. agno/reasoning/__init__.py +0 -0
  309. agno/reasoning/anthropic.py +80 -0
  310. agno/reasoning/azure_ai_foundry.py +67 -0
  311. agno/reasoning/deepseek.py +63 -0
  312. agno/reasoning/default.py +97 -0
  313. agno/reasoning/gemini.py +73 -0
  314. agno/reasoning/groq.py +71 -0
  315. agno/reasoning/helpers.py +63 -0
  316. agno/reasoning/ollama.py +67 -0
  317. agno/reasoning/openai.py +86 -0
  318. agno/reasoning/step.py +31 -0
  319. agno/reasoning/vertexai.py +76 -0
  320. agno/run/__init__.py +6 -0
  321. agno/run/agent.py +787 -0
  322. agno/run/base.py +229 -0
  323. agno/run/cancel.py +81 -0
  324. agno/run/messages.py +32 -0
  325. agno/run/team.py +753 -0
  326. agno/run/workflow.py +708 -0
  327. agno/session/__init__.py +10 -0
  328. agno/session/agent.py +295 -0
  329. agno/session/summary.py +265 -0
  330. agno/session/team.py +392 -0
  331. agno/session/workflow.py +205 -0
  332. agno/team/__init__.py +37 -0
  333. agno/team/team.py +8793 -0
  334. agno/tools/__init__.py +10 -0
  335. agno/tools/agentql.py +120 -0
  336. agno/tools/airflow.py +69 -0
  337. agno/tools/api.py +122 -0
  338. agno/tools/apify.py +314 -0
  339. agno/tools/arxiv.py +127 -0
  340. agno/tools/aws_lambda.py +53 -0
  341. agno/tools/aws_ses.py +66 -0
  342. agno/tools/baidusearch.py +89 -0
  343. agno/tools/bitbucket.py +292 -0
  344. agno/tools/brandfetch.py +213 -0
  345. agno/tools/bravesearch.py +106 -0
  346. agno/tools/brightdata.py +367 -0
  347. agno/tools/browserbase.py +209 -0
  348. agno/tools/calcom.py +255 -0
  349. agno/tools/calculator.py +151 -0
  350. agno/tools/cartesia.py +187 -0
  351. agno/tools/clickup.py +244 -0
  352. agno/tools/confluence.py +240 -0
  353. agno/tools/crawl4ai.py +158 -0
  354. agno/tools/csv_toolkit.py +185 -0
  355. agno/tools/dalle.py +110 -0
  356. agno/tools/daytona.py +475 -0
  357. agno/tools/decorator.py +262 -0
  358. agno/tools/desi_vocal.py +108 -0
  359. agno/tools/discord.py +161 -0
  360. agno/tools/docker.py +716 -0
  361. agno/tools/duckdb.py +379 -0
  362. agno/tools/duckduckgo.py +91 -0
  363. agno/tools/e2b.py +703 -0
  364. agno/tools/eleven_labs.py +196 -0
  365. agno/tools/email.py +67 -0
  366. agno/tools/evm.py +129 -0
  367. agno/tools/exa.py +396 -0
  368. agno/tools/fal.py +127 -0
  369. agno/tools/file.py +240 -0
  370. agno/tools/file_generation.py +350 -0
  371. agno/tools/financial_datasets.py +288 -0
  372. agno/tools/firecrawl.py +143 -0
  373. agno/tools/function.py +1187 -0
  374. agno/tools/giphy.py +93 -0
  375. agno/tools/github.py +1760 -0
  376. agno/tools/gmail.py +922 -0
  377. agno/tools/google_bigquery.py +117 -0
  378. agno/tools/google_drive.py +270 -0
  379. agno/tools/google_maps.py +253 -0
  380. agno/tools/googlecalendar.py +674 -0
  381. agno/tools/googlesearch.py +98 -0
  382. agno/tools/googlesheets.py +377 -0
  383. agno/tools/hackernews.py +77 -0
  384. agno/tools/jina.py +101 -0
  385. agno/tools/jira.py +170 -0
  386. agno/tools/knowledge.py +218 -0
  387. agno/tools/linear.py +426 -0
  388. agno/tools/linkup.py +58 -0
  389. agno/tools/local_file_system.py +90 -0
  390. agno/tools/lumalab.py +183 -0
  391. agno/tools/mcp/__init__.py +10 -0
  392. agno/tools/mcp/mcp.py +331 -0
  393. agno/tools/mcp/multi_mcp.py +347 -0
  394. agno/tools/mcp/params.py +24 -0
  395. agno/tools/mcp_toolbox.py +284 -0
  396. agno/tools/mem0.py +193 -0
  397. agno/tools/memori.py +339 -0
  398. agno/tools/memory.py +419 -0
  399. agno/tools/mlx_transcribe.py +139 -0
  400. agno/tools/models/__init__.py +0 -0
  401. agno/tools/models/azure_openai.py +190 -0
  402. agno/tools/models/gemini.py +203 -0
  403. agno/tools/models/groq.py +158 -0
  404. agno/tools/models/morph.py +186 -0
  405. agno/tools/models/nebius.py +124 -0
  406. agno/tools/models_labs.py +195 -0
  407. agno/tools/moviepy_video.py +349 -0
  408. agno/tools/neo4j.py +134 -0
  409. agno/tools/newspaper.py +46 -0
  410. agno/tools/newspaper4k.py +93 -0
  411. agno/tools/notion.py +204 -0
  412. agno/tools/openai.py +202 -0
  413. agno/tools/openbb.py +160 -0
  414. agno/tools/opencv.py +321 -0
  415. agno/tools/openweather.py +233 -0
  416. agno/tools/oxylabs.py +385 -0
  417. agno/tools/pandas.py +102 -0
  418. agno/tools/parallel.py +314 -0
  419. agno/tools/postgres.py +257 -0
  420. agno/tools/pubmed.py +188 -0
  421. agno/tools/python.py +205 -0
  422. agno/tools/reasoning.py +283 -0
  423. agno/tools/reddit.py +467 -0
  424. agno/tools/replicate.py +117 -0
  425. agno/tools/resend.py +62 -0
  426. agno/tools/scrapegraph.py +222 -0
  427. agno/tools/searxng.py +152 -0
  428. agno/tools/serpapi.py +116 -0
  429. agno/tools/serper.py +255 -0
  430. agno/tools/shell.py +53 -0
  431. agno/tools/slack.py +136 -0
  432. agno/tools/sleep.py +20 -0
  433. agno/tools/spider.py +116 -0
  434. agno/tools/sql.py +154 -0
  435. agno/tools/streamlit/__init__.py +0 -0
  436. agno/tools/streamlit/components.py +113 -0
  437. agno/tools/tavily.py +254 -0
  438. agno/tools/telegram.py +48 -0
  439. agno/tools/todoist.py +218 -0
  440. agno/tools/tool_registry.py +1 -0
  441. agno/tools/toolkit.py +146 -0
  442. agno/tools/trafilatura.py +388 -0
  443. agno/tools/trello.py +274 -0
  444. agno/tools/twilio.py +186 -0
  445. agno/tools/user_control_flow.py +78 -0
  446. agno/tools/valyu.py +228 -0
  447. agno/tools/visualization.py +467 -0
  448. agno/tools/webbrowser.py +28 -0
  449. agno/tools/webex.py +76 -0
  450. agno/tools/website.py +54 -0
  451. agno/tools/webtools.py +45 -0
  452. agno/tools/whatsapp.py +286 -0
  453. agno/tools/wikipedia.py +63 -0
  454. agno/tools/workflow.py +278 -0
  455. agno/tools/x.py +335 -0
  456. agno/tools/yfinance.py +257 -0
  457. agno/tools/youtube.py +184 -0
  458. agno/tools/zendesk.py +82 -0
  459. agno/tools/zep.py +454 -0
  460. agno/tools/zoom.py +382 -0
  461. agno/utils/__init__.py +0 -0
  462. agno/utils/agent.py +820 -0
  463. agno/utils/audio.py +49 -0
  464. agno/utils/certs.py +27 -0
  465. agno/utils/code_execution.py +11 -0
  466. agno/utils/common.py +132 -0
  467. agno/utils/dttm.py +13 -0
  468. agno/utils/enum.py +22 -0
  469. agno/utils/env.py +11 -0
  470. agno/utils/events.py +696 -0
  471. agno/utils/format_str.py +16 -0
  472. agno/utils/functions.py +166 -0
  473. agno/utils/gemini.py +426 -0
  474. agno/utils/hooks.py +57 -0
  475. agno/utils/http.py +74 -0
  476. agno/utils/json_schema.py +234 -0
  477. agno/utils/knowledge.py +36 -0
  478. agno/utils/location.py +19 -0
  479. agno/utils/log.py +255 -0
  480. agno/utils/mcp.py +214 -0
  481. agno/utils/media.py +352 -0
  482. agno/utils/merge_dict.py +41 -0
  483. agno/utils/message.py +118 -0
  484. agno/utils/models/__init__.py +0 -0
  485. agno/utils/models/ai_foundry.py +43 -0
  486. agno/utils/models/claude.py +358 -0
  487. agno/utils/models/cohere.py +87 -0
  488. agno/utils/models/llama.py +78 -0
  489. agno/utils/models/mistral.py +98 -0
  490. agno/utils/models/openai_responses.py +140 -0
  491. agno/utils/models/schema_utils.py +153 -0
  492. agno/utils/models/watsonx.py +41 -0
  493. agno/utils/openai.py +257 -0
  494. agno/utils/pickle.py +32 -0
  495. agno/utils/pprint.py +178 -0
  496. agno/utils/print_response/__init__.py +0 -0
  497. agno/utils/print_response/agent.py +842 -0
  498. agno/utils/print_response/team.py +1724 -0
  499. agno/utils/print_response/workflow.py +1668 -0
  500. agno/utils/prompts.py +111 -0
  501. agno/utils/reasoning.py +108 -0
  502. agno/utils/response.py +163 -0
  503. agno/utils/response_iterator.py +17 -0
  504. agno/utils/safe_formatter.py +24 -0
  505. agno/utils/serialize.py +32 -0
  506. agno/utils/shell.py +22 -0
  507. agno/utils/streamlit.py +487 -0
  508. agno/utils/string.py +231 -0
  509. agno/utils/team.py +139 -0
  510. agno/utils/timer.py +41 -0
  511. agno/utils/tools.py +102 -0
  512. agno/utils/web.py +23 -0
  513. agno/utils/whatsapp.py +305 -0
  514. agno/utils/yaml_io.py +25 -0
  515. agno/vectordb/__init__.py +3 -0
  516. agno/vectordb/base.py +127 -0
  517. agno/vectordb/cassandra/__init__.py +5 -0
  518. agno/vectordb/cassandra/cassandra.py +501 -0
  519. agno/vectordb/cassandra/extra_param_mixin.py +11 -0
  520. agno/vectordb/cassandra/index.py +13 -0
  521. agno/vectordb/chroma/__init__.py +5 -0
  522. agno/vectordb/chroma/chromadb.py +929 -0
  523. agno/vectordb/clickhouse/__init__.py +9 -0
  524. agno/vectordb/clickhouse/clickhousedb.py +835 -0
  525. agno/vectordb/clickhouse/index.py +9 -0
  526. agno/vectordb/couchbase/__init__.py +3 -0
  527. agno/vectordb/couchbase/couchbase.py +1442 -0
  528. agno/vectordb/distance.py +7 -0
  529. agno/vectordb/lancedb/__init__.py +6 -0
  530. agno/vectordb/lancedb/lance_db.py +995 -0
  531. agno/vectordb/langchaindb/__init__.py +5 -0
  532. agno/vectordb/langchaindb/langchaindb.py +163 -0
  533. agno/vectordb/lightrag/__init__.py +5 -0
  534. agno/vectordb/lightrag/lightrag.py +388 -0
  535. agno/vectordb/llamaindex/__init__.py +3 -0
  536. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  537. agno/vectordb/milvus/__init__.py +4 -0
  538. agno/vectordb/milvus/milvus.py +1182 -0
  539. agno/vectordb/mongodb/__init__.py +9 -0
  540. agno/vectordb/mongodb/mongodb.py +1417 -0
  541. agno/vectordb/pgvector/__init__.py +12 -0
  542. agno/vectordb/pgvector/index.py +23 -0
  543. agno/vectordb/pgvector/pgvector.py +1462 -0
  544. agno/vectordb/pineconedb/__init__.py +5 -0
  545. agno/vectordb/pineconedb/pineconedb.py +747 -0
  546. agno/vectordb/qdrant/__init__.py +5 -0
  547. agno/vectordb/qdrant/qdrant.py +1134 -0
  548. agno/vectordb/redis/__init__.py +9 -0
  549. agno/vectordb/redis/redisdb.py +694 -0
  550. agno/vectordb/search.py +7 -0
  551. agno/vectordb/singlestore/__init__.py +10 -0
  552. agno/vectordb/singlestore/index.py +41 -0
  553. agno/vectordb/singlestore/singlestore.py +763 -0
  554. agno/vectordb/surrealdb/__init__.py +3 -0
  555. agno/vectordb/surrealdb/surrealdb.py +699 -0
  556. agno/vectordb/upstashdb/__init__.py +5 -0
  557. agno/vectordb/upstashdb/upstashdb.py +718 -0
  558. agno/vectordb/weaviate/__init__.py +8 -0
  559. agno/vectordb/weaviate/index.py +15 -0
  560. agno/vectordb/weaviate/weaviate.py +1005 -0
  561. agno/workflow/__init__.py +23 -0
  562. agno/workflow/agent.py +299 -0
  563. agno/workflow/condition.py +738 -0
  564. agno/workflow/loop.py +735 -0
  565. agno/workflow/parallel.py +824 -0
  566. agno/workflow/router.py +702 -0
  567. agno/workflow/step.py +1432 -0
  568. agno/workflow/steps.py +592 -0
  569. agno/workflow/types.py +520 -0
  570. agno/workflow/workflow.py +4321 -0
  571. agno-2.2.13.dist-info/METADATA +614 -0
  572. agno-2.2.13.dist-info/RECORD +575 -0
  573. agno-2.2.13.dist-info/WHEEL +5 -0
  574. agno-2.2.13.dist-info/licenses/LICENSE +201 -0
  575. agno-2.2.13.dist-info/top_level.txt +1 -0
agno/tools/function.py ADDED
@@ -0,0 +1,1187 @@
1
+ from dataclasses import dataclass
2
+ from functools import partial
3
+ from importlib.metadata import version
4
+ from typing import Any, Callable, Dict, List, Literal, Optional, Sequence, Type, TypeVar, get_type_hints
5
+
6
+ from docstring_parser import parse
7
+ from packaging.version import Version
8
+ from pydantic import BaseModel, Field, validate_call
9
+
10
+ from agno.exceptions import AgentRunException
11
+ from agno.media import Audio, File, Image, Video
12
+ from agno.run import RunContext
13
+ from agno.utils.log import log_debug, log_error, log_exception, log_warning
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ def get_entrypoint_docstring(entrypoint: Callable) -> str:
19
+ from inspect import getdoc
20
+
21
+ if isinstance(entrypoint, partial):
22
+ return str(entrypoint)
23
+
24
+ docstring = getdoc(entrypoint)
25
+ if not docstring:
26
+ return ""
27
+
28
+ parsed_doc = parse(docstring)
29
+
30
+ # Combine short and long descriptions
31
+ lines = []
32
+ if parsed_doc.short_description:
33
+ lines.append(parsed_doc.short_description)
34
+ if parsed_doc.long_description:
35
+ lines.extend(parsed_doc.long_description.split("\n"))
36
+
37
+ return "\n".join(lines)
38
+
39
+
40
+ @dataclass
41
+ class UserInputField:
42
+ name: str
43
+ field_type: Type
44
+ description: Optional[str] = None
45
+ value: Optional[Any] = None
46
+
47
+ def to_dict(self) -> Dict[str, Any]:
48
+ return {
49
+ "name": self.name,
50
+ "field_type": str(self.field_type.__name__),
51
+ "description": self.description,
52
+ "value": self.value,
53
+ }
54
+
55
+ @classmethod
56
+ def from_dict(cls, data: Dict[str, Any]) -> "UserInputField":
57
+ return cls(
58
+ name=data["name"],
59
+ field_type=eval(data["field_type"]), # Convert string type name to actual type
60
+ description=data["description"],
61
+ value=data["value"],
62
+ )
63
+
64
+
65
+ class Function(BaseModel):
66
+ """Model for storing functions that can be called by an agent."""
67
+
68
+ # The name of the function to be called.
69
+ # Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
70
+ name: str
71
+ # A description of what the function does, used by the model to choose when and how to call the function.
72
+ description: Optional[str] = None
73
+ # The parameters the functions accepts, described as a JSON Schema object.
74
+ # To describe a function that accepts no parameters, provide the value {"type": "object", "properties": {}}.
75
+ parameters: Dict[str, Any] = Field(
76
+ default_factory=lambda: {"type": "object", "properties": {}, "required": []},
77
+ description="JSON Schema object describing function parameters",
78
+ )
79
+ strict: Optional[bool] = None
80
+
81
+ instructions: Optional[str] = None
82
+ # If True, add instructions to the Agent's system message
83
+ add_instructions: bool = True
84
+
85
+ # The function to be called.
86
+ entrypoint: Optional[Callable] = None
87
+ # If True, the entrypoint processing is skipped and the Function is used as is.
88
+ skip_entrypoint_processing: bool = False
89
+ # If True, the function call will show the result along with sending it to the model.
90
+ show_result: bool = False
91
+ # If True, the agent will stop after the function call.
92
+ stop_after_tool_call: bool = False
93
+ # Hook that runs before the function is executed.
94
+ # If defined, can accept the FunctionCall instance as a parameter.
95
+ pre_hook: Optional[Callable] = None
96
+ # Hook that runs after the function is executed, regardless of success/failure.
97
+ # If defined, can accept the FunctionCall instance as a parameter.
98
+ post_hook: Optional[Callable] = None
99
+
100
+ # A list of hooks to run around tool calls.
101
+ tool_hooks: Optional[List[Callable]] = None
102
+
103
+ # If True, the function will require confirmation before execution
104
+ requires_confirmation: Optional[bool] = None
105
+
106
+ # If True, the function will require user input before execution
107
+ requires_user_input: Optional[bool] = None
108
+ # List of fields that the user will provide as input and that should be ignored by the agent (empty list means all fields are provided by the user)
109
+ user_input_fields: Optional[List[str]] = None
110
+ # This is set during parsing, not by the user
111
+ user_input_schema: Optional[List[UserInputField]] = None
112
+
113
+ # If True, the function will be executed outside the agent's control.
114
+ external_execution: Optional[bool] = None
115
+
116
+ # Caching configuration
117
+ cache_results: bool = False
118
+ cache_dir: Optional[str] = None
119
+ cache_ttl: int = 3600
120
+
121
+ # --*-- FOR INTERNAL USE ONLY --*--
122
+ # The agent that the function is associated with
123
+ _agent: Optional[Any] = None
124
+ # The team that the function is associated with
125
+ _team: Optional[Any] = None
126
+ # The run context that the function is associated with
127
+ _run_context: Optional[RunContext] = None
128
+ # The session state that the function is associated with
129
+ _session_state: Optional[Dict[str, Any]] = None
130
+ # The dependencies that the function is associated with
131
+ _dependencies: Optional[Dict[str, Any]] = None
132
+
133
+ # Media context that the function is associated with
134
+ _images: Optional[Sequence[Image]] = None
135
+ _videos: Optional[Sequence[Video]] = None
136
+ _audios: Optional[Sequence[Audio]] = None
137
+ _files: Optional[Sequence[File]] = None
138
+
139
+ def to_dict(self) -> Dict[str, Any]:
140
+ return self.model_dump(
141
+ exclude_none=True,
142
+ include={"name", "description", "parameters", "strict", "requires_confirmation", "external_execution"},
143
+ )
144
+
145
+ def model_copy(self, *, deep: bool = False) -> "Function":
146
+ """
147
+ Override model_copy to handle callable fields that can't be deep copied (pickled).
148
+ Callables should always be shallow copied (referenced), not deep copied.
149
+ """
150
+ # For deep copy, we need to handle callable fields specially
151
+ if deep:
152
+ # Fields that should NOT be deep copied (callables and complex objects)
153
+ shallow_fields = {
154
+ "entrypoint",
155
+ "pre_hook",
156
+ "post_hook",
157
+ "tool_hooks",
158
+ "_agent",
159
+ "_team",
160
+ }
161
+
162
+ # Create a copy with shallow references to callable fields
163
+ copied_data = {}
164
+ for field_name, field_value in self.__dict__.items():
165
+ if field_name in shallow_fields:
166
+ # Shallow copy - just reference the same object
167
+ copied_data[field_name] = field_value
168
+ elif field_name == "parameters":
169
+ # Deep copy the parameters dict
170
+ from copy import deepcopy
171
+
172
+ copied_data[field_name] = deepcopy(field_value)
173
+ else:
174
+ # For simple types, just copy the value
175
+ copied_data[field_name] = field_value
176
+
177
+ # Create new instance with copied data
178
+ new_instance = self.__class__.model_construct(**copied_data)
179
+
180
+ return new_instance
181
+ else:
182
+ # For shallow copy, use the default Pydantic behavior
183
+ return super().model_copy(deep=False)
184
+
185
+ @classmethod
186
+ def from_callable(cls, c: Callable, name: Optional[str] = None, strict: bool = False) -> "Function":
187
+ from inspect import getdoc, signature
188
+
189
+ from agno.utils.json_schema import get_json_schema
190
+
191
+ function_name = name or c.__name__
192
+ parameters = {"type": "object", "properties": {}, "required": []}
193
+ try:
194
+ sig = signature(c)
195
+ type_hints = get_type_hints(c)
196
+
197
+ # If function has an the agent argument, remove the agent parameter from the type hints
198
+ if "agent" in sig.parameters and "agent" in type_hints:
199
+ del type_hints["agent"]
200
+ if "team" in sig.parameters and "team" in type_hints:
201
+ del type_hints["team"]
202
+ if "run_context" in sig.parameters and "run_context" in type_hints:
203
+ del type_hints["run_context"]
204
+ if "session_state" in sig.parameters and "session_state" in type_hints:
205
+ del type_hints["session_state"]
206
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
207
+ del type_hints["dependencies"]
208
+
209
+ # Remove media parameters from type hints as they are injected automatically
210
+ if "images" in sig.parameters and "images" in type_hints:
211
+ del type_hints["images"]
212
+ if "videos" in sig.parameters and "videos" in type_hints:
213
+ del type_hints["videos"]
214
+ if "audios" in sig.parameters and "audios" in type_hints:
215
+ del type_hints["audios"]
216
+ if "files" in sig.parameters and "files" in type_hints:
217
+ del type_hints["files"]
218
+ # log_info(f"Type hints for {function_name}: {type_hints}")
219
+
220
+ # Filter out return type and only process parameters
221
+ param_type_hints = {
222
+ name: type_hints.get(name)
223
+ for name in sig.parameters
224
+ if name != "return"
225
+ and name
226
+ not in [
227
+ "agent",
228
+ "team",
229
+ "run_context",
230
+ "session_state",
231
+ "dependencies",
232
+ "self",
233
+ "images",
234
+ "videos",
235
+ "audios",
236
+ "files",
237
+ ]
238
+ }
239
+
240
+ # Parse docstring for parameters
241
+ param_descriptions: Dict[str, Any] = {}
242
+ if docstring := getdoc(c):
243
+ parsed_doc = parse(docstring)
244
+ param_docs = parsed_doc.params
245
+
246
+ if param_docs is not None:
247
+ for param in param_docs:
248
+ param_name = param.arg_name
249
+ param_type = param.type_name
250
+ if param_type is None:
251
+ param_descriptions[param_name] = param.description
252
+ else:
253
+ param_descriptions[param_name] = f"({param_type}) {param.description}"
254
+
255
+ # Get JSON schema for parameters only
256
+ parameters = get_json_schema(
257
+ type_hints=param_type_hints, param_descriptions=param_descriptions, strict=strict
258
+ )
259
+
260
+ # If strict=True mark all fields as required
261
+ # See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
262
+ if strict:
263
+ parameters["required"] = [
264
+ name
265
+ for name in parameters["properties"]
266
+ if name
267
+ not in [
268
+ "agent",
269
+ "team",
270
+ "run_context",
271
+ "session_state",
272
+ "dependencies",
273
+ "self",
274
+ "images",
275
+ "videos",
276
+ "audios",
277
+ "files",
278
+ ]
279
+ ]
280
+ else:
281
+ # Mark a field as required if it has no default value (this would include optional fields)
282
+ parameters["required"] = [
283
+ name
284
+ for name, param in sig.parameters.items()
285
+ if param.default == param.empty
286
+ and name
287
+ not in [
288
+ "agent",
289
+ "team",
290
+ "run_context",
291
+ "session_state",
292
+ "dependencies",
293
+ "self",
294
+ "images",
295
+ "videos",
296
+ "audios",
297
+ "files",
298
+ ]
299
+ ]
300
+
301
+ # log_debug(f"JSON schema for {function_name}: {parameters}")
302
+ except Exception as e:
303
+ log_warning(f"Could not parse args for {function_name}: {e}", exc_info=True)
304
+
305
+ entrypoint = cls._wrap_callable(c)
306
+
307
+ return cls(
308
+ name=function_name,
309
+ description=get_entrypoint_docstring(entrypoint=c),
310
+ parameters=parameters,
311
+ entrypoint=entrypoint,
312
+ )
313
+
314
+ def process_entrypoint(self, strict: bool = False):
315
+ """Process the entrypoint and make it ready for use by an agent."""
316
+ from inspect import getdoc, signature
317
+
318
+ from agno.utils.json_schema import get_json_schema
319
+
320
+ if self.skip_entrypoint_processing:
321
+ if strict:
322
+ self.process_schema_for_strict()
323
+ return
324
+
325
+ if self.entrypoint is None:
326
+ return
327
+
328
+ parameters = {"type": "object", "properties": {}, "required": []}
329
+
330
+ params_set_by_user = False
331
+ # If the user set the parameters (i.e. they are different from the default), we should keep them
332
+ if self.parameters != parameters:
333
+ params_set_by_user = True
334
+
335
+ if self.requires_user_input:
336
+ self.user_input_schema = self.user_input_schema or []
337
+
338
+ try:
339
+ sig = signature(self.entrypoint)
340
+ type_hints = get_type_hints(self.entrypoint)
341
+
342
+ # If function has an the agent argument, remove the agent parameter from the type hints
343
+ if "agent" in sig.parameters and "agent" in type_hints:
344
+ del type_hints["agent"]
345
+ if "team" in sig.parameters and "team" in type_hints:
346
+ del type_hints["team"]
347
+ if "run_context" in sig.parameters and "run_context" in type_hints:
348
+ del type_hints["run_context"]
349
+ if "session_state" in sig.parameters and "session_state" in type_hints:
350
+ del type_hints["session_state"]
351
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
352
+ del type_hints["dependencies"]
353
+ if "images" in sig.parameters and "images" in type_hints:
354
+ del type_hints["images"]
355
+ if "videos" in sig.parameters and "videos" in type_hints:
356
+ del type_hints["videos"]
357
+ if "audios" in sig.parameters and "audios" in type_hints:
358
+ del type_hints["audios"]
359
+ if "files" in sig.parameters and "files" in type_hints:
360
+ del type_hints["files"]
361
+ # log_info(f"Type hints for {self.name}: {type_hints}")
362
+
363
+ # Filter out return type and only process parameters
364
+ excluded_params = [
365
+ "return",
366
+ "agent",
367
+ "team",
368
+ "run_context",
369
+ "session_state",
370
+ "dependencies",
371
+ "self",
372
+ "images",
373
+ "videos",
374
+ "audios",
375
+ "files",
376
+ ]
377
+ if self.requires_user_input and self.user_input_fields:
378
+ if len(self.user_input_fields) == 0:
379
+ excluded_params.extend(list(type_hints.keys()))
380
+ else:
381
+ excluded_params.extend(self.user_input_fields)
382
+
383
+ # Get filtered list of parameter types
384
+ param_type_hints = {name: type_hints.get(name) for name in sig.parameters if name not in excluded_params}
385
+
386
+ # Parse docstring for parameters
387
+ param_descriptions = {}
388
+ param_descriptions_clean = {}
389
+ if docstring := getdoc(self.entrypoint):
390
+ parsed_doc = parse(docstring)
391
+ param_docs = parsed_doc.params
392
+
393
+ if param_docs is not None:
394
+ for param in param_docs:
395
+ param_name = param.arg_name
396
+ param_type = param.type_name
397
+
398
+ # TODO: We should use type hints first, then map param types in docs to json schema types.
399
+ # This is temporary to not lose information
400
+ param_descriptions[param_name] = f"({param_type}) {param.description}"
401
+ param_descriptions_clean[param_name] = param.description
402
+
403
+ # If the function requires user input, we should set the user_input_schema to all parameters. The arguments provided by the model are filled in later.
404
+ if self.requires_user_input:
405
+ self.user_input_schema = [
406
+ UserInputField(
407
+ name=name,
408
+ description=param_descriptions_clean.get(name),
409
+ field_type=type_hints.get(name, str),
410
+ )
411
+ for name in sig.parameters
412
+ ]
413
+
414
+ # Get JSON schema for parameters only
415
+ parameters = get_json_schema(
416
+ type_hints=param_type_hints, param_descriptions=param_descriptions, strict=strict
417
+ )
418
+
419
+ # If strict=True mark all fields as required
420
+ # See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
421
+ if strict:
422
+ parameters["required"] = [name for name in parameters["properties"] if name not in excluded_params]
423
+ else:
424
+ # Mark a field as required if it has no default value
425
+ parameters["required"] = [
426
+ name
427
+ for name, param in sig.parameters.items()
428
+ if param.default == param.empty and name != "self" and name not in excluded_params
429
+ ]
430
+
431
+ if params_set_by_user:
432
+ self.parameters["additionalProperties"] = False
433
+ if strict:
434
+ self.parameters["required"] = [
435
+ name for name in self.parameters["properties"] if name not in excluded_params
436
+ ]
437
+ else:
438
+ # Mark a field as required if it has no default value
439
+ self.parameters["required"] = [
440
+ name
441
+ for name, param in sig.parameters.items()
442
+ if param.default == param.empty and name != "self" and name not in excluded_params
443
+ ]
444
+
445
+ self.description = self.description or get_entrypoint_docstring(self.entrypoint)
446
+
447
+ # log_debug(f"JSON schema for {self.name}: {parameters}")
448
+ except Exception as e:
449
+ log_warning(f"Could not parse args for {self.name}: {e}", exc_info=True)
450
+
451
+ if not params_set_by_user:
452
+ self.parameters = parameters
453
+
454
+ if strict:
455
+ self.process_schema_for_strict()
456
+
457
+ try:
458
+ self.entrypoint = self._wrap_callable(self.entrypoint)
459
+ except Exception as e:
460
+ log_warning(f"Failed to add validate decorator to entrypoint: {e}")
461
+
462
+ @staticmethod
463
+ def _wrap_callable(func: Callable) -> Callable:
464
+ """Wrap a callable with Pydantic's validate_call decorator, if relevant"""
465
+ from inspect import isasyncgenfunction, iscoroutinefunction, signature
466
+
467
+ pydantic_version = Version(version("pydantic"))
468
+
469
+ # Don't wrap async generators validate_call
470
+ if isasyncgenfunction(func):
471
+ return func
472
+
473
+ # Don't wrap coroutines with validate_call if pydantic version is less than 2.10.0
474
+ if iscoroutinefunction(func) and pydantic_version < Version("2.10.0"):
475
+ log_debug(
476
+ f"Skipping validate_call for {func.__name__} because pydantic version is less than 2.10.0, please consider upgrading to pydantic 2.10.0 or higher"
477
+ )
478
+ return func
479
+
480
+ # Don't wrap callables that are already wrapped with validate_call
481
+ elif getattr(func, "_wrapped_for_validation", False):
482
+ return func
483
+ # Don't wrap functions with session_state parameter
484
+ # session_state needs to be passed by reference, not copied by pydantic's validation
485
+ elif "session_state" in signature(func).parameters:
486
+ return func
487
+ # Wrap the callable with validate_call
488
+ else:
489
+ wrapped = validate_call(func, config=dict(arbitrary_types_allowed=True)) # type: ignore
490
+ wrapped._wrapped_for_validation = True # Mark as wrapped to avoid infinite recursion
491
+ return wrapped
492
+
493
+ def process_schema_for_strict(self):
494
+ """Process the schema to make it strict mode compliant."""
495
+
496
+ def make_nested_strict(schema):
497
+ """Recursively ensure all object schemas have additionalProperties: false"""
498
+ if not isinstance(schema, dict):
499
+ return schema
500
+
501
+ # Make a copy to avoid modifying the original
502
+ result = schema.copy()
503
+
504
+ # If this is an object schema, ensure additionalProperties: false
505
+ if result.get("type") == "object" or "properties" in result:
506
+ result["additionalProperties"] = False
507
+
508
+ # If schema has no type but has other schema properties, give it a type
509
+ if "type" not in result:
510
+ if "properties" in result:
511
+ result["type"] = "object"
512
+ result["additionalProperties"] = False
513
+ elif result.get("title") and not any(
514
+ key in result for key in ["properties", "items", "anyOf", "oneOf", "allOf", "enum"]
515
+ ):
516
+ result["type"] = "string"
517
+
518
+ # Recursively process nested schemas
519
+ for key, value in result.items():
520
+ if key == "properties" and isinstance(value, dict):
521
+ result[key] = {k: make_nested_strict(v) for k, v in value.items()}
522
+ elif key == "items" and isinstance(value, dict):
523
+ # This handles array items like List[KnowledgeFilter]
524
+ result[key] = make_nested_strict(value)
525
+ elif isinstance(value, dict):
526
+ result[key] = make_nested_strict(value)
527
+
528
+ return result
529
+
530
+ # Apply strict mode to the entire schema
531
+ self.parameters = make_nested_strict(self.parameters)
532
+
533
+ self.parameters["required"] = [
534
+ name
535
+ for name in self.parameters["properties"]
536
+ if name
537
+ not in [
538
+ "agent",
539
+ "team",
540
+ "run_context",
541
+ "session_state",
542
+ "dependencies",
543
+ "images",
544
+ "videos",
545
+ "audios",
546
+ "files",
547
+ "self",
548
+ ]
549
+ ]
550
+
551
+ def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
552
+ """Generate a cache key based on function name and arguments."""
553
+ import json
554
+ from hashlib import md5
555
+
556
+ copy_entrypoint_args = entrypoint_args.copy()
557
+ # Remove agent from entrypoint_args
558
+ if "agent" in copy_entrypoint_args:
559
+ del copy_entrypoint_args["agent"]
560
+ if "team" in copy_entrypoint_args:
561
+ del copy_entrypoint_args["team"]
562
+ if "run_context" in copy_entrypoint_args:
563
+ del copy_entrypoint_args["run_context"]
564
+ if "session_state" in copy_entrypoint_args:
565
+ del copy_entrypoint_args["session_state"]
566
+ if "dependencies" in copy_entrypoint_args:
567
+ del copy_entrypoint_args["dependencies"]
568
+ if "images" in copy_entrypoint_args:
569
+ del copy_entrypoint_args["images"]
570
+ if "videos" in copy_entrypoint_args:
571
+ del copy_entrypoint_args["videos"]
572
+ if "audios" in copy_entrypoint_args:
573
+ del copy_entrypoint_args["audios"]
574
+ if "files" in copy_entrypoint_args:
575
+ del copy_entrypoint_args["files"]
576
+ # Use json.dumps with sort_keys=True to ensure consistent ordering regardless of dict key order
577
+ args_str = json.dumps(copy_entrypoint_args, sort_keys=True, default=str)
578
+
579
+ kwargs_str = str(sorted((call_args or {}).items()))
580
+ key_str = f"{self.name}:{args_str}:{kwargs_str}"
581
+ return md5(key_str.encode()).hexdigest()
582
+
583
+ def _get_cache_file_path(self, cache_key: str) -> str:
584
+ """Get the full path for the cache file."""
585
+ from pathlib import Path
586
+ from tempfile import gettempdir
587
+
588
+ base_cache_dir = self.cache_dir or Path(gettempdir()) / "agno_cache"
589
+ func_cache_dir = Path(base_cache_dir) / "functions" / self.name
590
+ func_cache_dir.mkdir(parents=True, exist_ok=True)
591
+ return str(func_cache_dir / f"{cache_key}.json")
592
+
593
+ def _get_cached_result(self, cache_file: str) -> Optional[Any]:
594
+ """Retrieve cached result if valid."""
595
+ import json
596
+ from pathlib import Path
597
+ from time import time
598
+
599
+ cache_path = Path(cache_file)
600
+ if not cache_path.exists():
601
+ return None
602
+
603
+ try:
604
+ with cache_path.open("r") as f:
605
+ cache_data = json.load(f)
606
+
607
+ timestamp = cache_data.get("timestamp", 0)
608
+ result = cache_data.get("result")
609
+
610
+ if time() - timestamp <= self.cache_ttl:
611
+ return result
612
+
613
+ # Remove expired entry
614
+ cache_path.unlink()
615
+ except Exception as e:
616
+ log_error(f"Error reading cache: {e}")
617
+
618
+ return None
619
+
620
+ def _save_to_cache(self, cache_file: str, result: Any):
621
+ """Save result to cache."""
622
+ import json
623
+ from time import time
624
+
625
+ try:
626
+ with open(cache_file, "w") as f:
627
+ json.dump({"timestamp": time(), "result": result}, f)
628
+ except Exception as e:
629
+ log_error(f"Error writing cache: {e}")
630
+
631
+
632
+ class FunctionExecutionResult(BaseModel):
633
+ status: Literal["success", "failure"]
634
+ result: Optional[Any] = None
635
+ error: Optional[str] = None
636
+
637
+ updated_session_state: Optional[Dict[str, Any]] = None
638
+
639
+ # New fields for media artifacts
640
+ images: Optional[List[Image]] = None
641
+ videos: Optional[List[Video]] = None
642
+ audios: Optional[List[Audio]] = None
643
+ files: Optional[List[File]] = None
644
+
645
+
646
+ class FunctionCall(BaseModel):
647
+ """Model for Function Calls"""
648
+
649
+ # The function to be called.
650
+ function: Function
651
+ # The arguments to call the function with.
652
+ arguments: Optional[Dict[str, Any]] = None
653
+ # The result of the function call.
654
+ result: Optional[Any] = None
655
+ # The ID of the function call.
656
+ call_id: Optional[str] = None
657
+
658
+ # Error while parsing arguments or running the function.
659
+ error: Optional[str] = None
660
+
661
+ def get_call_str(self) -> str:
662
+ """Returns a string representation of the function call."""
663
+ import shutil
664
+
665
+ # Get terminal width, default to 80 if it can't be determined
666
+ term_width = shutil.get_terminal_size().columns or 80
667
+ max_arg_len = max(20, (term_width - len(self.function.name) - 4) // 2)
668
+
669
+ if self.arguments is None:
670
+ return f"{self.function.name}()"
671
+
672
+ trimmed_arguments = {}
673
+ for k, v in self.arguments.items():
674
+ if isinstance(v, str) and len(str(v)) > max_arg_len:
675
+ trimmed_arguments[k] = "..."
676
+ else:
677
+ trimmed_arguments[k] = v
678
+
679
+ call_str = f"{self.function.name}({', '.join([f'{k}={v}' for k, v in trimmed_arguments.items()])})"
680
+
681
+ # If call string is too long, truncate arguments
682
+ if len(call_str) > term_width:
683
+ return f"{self.function.name}(...)"
684
+
685
+ return call_str
686
+
687
+ def _handle_pre_hook(self):
688
+ """Handles the pre-hook for the function call."""
689
+ if self.function.pre_hook is not None:
690
+ try:
691
+ from inspect import signature
692
+
693
+ pre_hook_args = {}
694
+ # Check if the pre-hook has and agent argument
695
+ if "agent" in signature(self.function.pre_hook).parameters:
696
+ pre_hook_args["agent"] = self.function._agent
697
+ # Check if the pre-hook has an team argument
698
+ if "team" in signature(self.function.pre_hook).parameters:
699
+ pre_hook_args["team"] = self.function._team
700
+ # Check if the pre-hook has an session_state argument
701
+ if "run_context" in signature(self.function.pre_hook).parameters:
702
+ pre_hook_args["run_context"] = self.function._run_context
703
+ # Check if the pre-hook has an session_state argument
704
+ if "session_state" in signature(self.function.pre_hook).parameters:
705
+ pre_hook_args["session_state"] = self.function._session_state
706
+ # Check if the pre-hook has an dependencies argument
707
+ if "dependencies" in signature(self.function.pre_hook).parameters:
708
+ pre_hook_args["dependencies"] = self.function._dependencies
709
+ # Check if the pre-hook has an fc argument
710
+ if "fc" in signature(self.function.pre_hook).parameters:
711
+ pre_hook_args["fc"] = self
712
+ self.function.pre_hook(**pre_hook_args)
713
+ except AgentRunException as e:
714
+ log_debug(f"{e.__class__.__name__}: {e}")
715
+ self.error = str(e)
716
+ raise
717
+ except Exception as e:
718
+ log_warning(f"Error in pre-hook callback: {e}")
719
+ log_exception(e)
720
+
721
+ def _handle_post_hook(self):
722
+ """Handles the post-hook for the function call."""
723
+ if self.function.post_hook is not None:
724
+ try:
725
+ from inspect import signature
726
+
727
+ post_hook_args = {}
728
+ # Check if the post-hook has and agent argument
729
+ if "agent" in signature(self.function.post_hook).parameters:
730
+ post_hook_args["agent"] = self.function._agent
731
+ # Check if the post-hook has an team argument
732
+ if "team" in signature(self.function.post_hook).parameters:
733
+ post_hook_args["team"] = self.function._team
734
+ # Check if the post-hook has an session_state argument
735
+ if "run_context" in signature(self.function.post_hook).parameters:
736
+ post_hook_args["run_context"] = self.function._run_context
737
+ # Check if the post-hook has an session_state argument
738
+ if "session_state" in signature(self.function.post_hook).parameters:
739
+ post_hook_args["session_state"] = self.function._session_state
740
+ # Check if the post-hook has an dependencies argument
741
+ if "dependencies" in signature(self.function.post_hook).parameters:
742
+ post_hook_args["dependencies"] = self.function._dependencies
743
+ # Check if the post-hook has an fc argument
744
+ if "fc" in signature(self.function.post_hook).parameters:
745
+ post_hook_args["fc"] = self
746
+ self.function.post_hook(**post_hook_args)
747
+ except AgentRunException as e:
748
+ log_debug(f"{e.__class__.__name__}: {e}")
749
+ self.error = str(e)
750
+ raise
751
+ except Exception as e:
752
+ log_warning(f"Error in post-hook callback: {e}")
753
+ log_exception(e)
754
+
755
+ def _build_entrypoint_args(self) -> Dict[str, Any]:
756
+ """Builds the arguments for the entrypoint."""
757
+ from inspect import signature
758
+
759
+ entrypoint_args = {}
760
+ # Check if the entrypoint has an agent argument
761
+ if "agent" in signature(self.function.entrypoint).parameters: # type: ignore
762
+ entrypoint_args["agent"] = self.function._agent
763
+ # Check if the entrypoint has an team argument
764
+ if "team" in signature(self.function.entrypoint).parameters: # type: ignore
765
+ entrypoint_args["team"] = self.function._team
766
+ # Check if the entrypoint has an run_context argument
767
+ if "run_context" in signature(self.function.entrypoint).parameters: # type: ignore
768
+ entrypoint_args["run_context"] = self.function._run_context
769
+ # Check if the entrypoint has an session_state argument
770
+ if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
771
+ entrypoint_args["session_state"] = self.function._session_state
772
+ # Check if the entrypoint has an dependencies argument
773
+ if "dependencies" in signature(self.function.entrypoint).parameters: # type: ignore
774
+ entrypoint_args["dependencies"] = self.function._dependencies
775
+ # Check if the entrypoint has an fc argument
776
+ if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
777
+ entrypoint_args["fc"] = self
778
+
779
+ # Check if the entrypoint has media arguments
780
+ if "images" in signature(self.function.entrypoint).parameters: # type: ignore
781
+ entrypoint_args["images"] = self.function._images
782
+ if "videos" in signature(self.function.entrypoint).parameters: # type: ignore
783
+ entrypoint_args["videos"] = self.function._videos
784
+ if "audios" in signature(self.function.entrypoint).parameters: # type: ignore
785
+ entrypoint_args["audios"] = self.function._audios
786
+ if "files" in signature(self.function.entrypoint).parameters: # type: ignore
787
+ entrypoint_args["files"] = self.function._files
788
+ return entrypoint_args
789
+
790
+ def _build_hook_args(self, hook: Callable, name: str, func: Callable, args: Dict[str, Any]) -> Dict[str, Any]:
791
+ """Build the arguments for the hook."""
792
+ from inspect import signature
793
+
794
+ hook_args = {}
795
+ # Check if the hook has an agent argument
796
+ if "agent" in signature(hook).parameters:
797
+ hook_args["agent"] = self.function._agent
798
+ # Check if the hook has an team argument
799
+ if "team" in signature(hook).parameters:
800
+ hook_args["team"] = self.function._team
801
+ # Check if the hook has an run_context argument
802
+ if "run_context" in signature(hook).parameters:
803
+ hook_args["run_context"] = self.function._run_context
804
+ # Check if the hook has an session_state argument
805
+ if "session_state" in signature(hook).parameters:
806
+ hook_args["session_state"] = self.function._session_state
807
+ # Check if the hook has an dependencies argument
808
+ if "dependencies" in signature(hook).parameters:
809
+ hook_args["dependencies"] = self.function._dependencies
810
+ if "name" in signature(hook).parameters:
811
+ hook_args["name"] = name
812
+ if "function_name" in signature(hook).parameters:
813
+ hook_args["function_name"] = name
814
+ if "function" in signature(hook).parameters:
815
+ hook_args["function"] = func
816
+ if "func" in signature(hook).parameters:
817
+ hook_args["func"] = func
818
+ if "function_call" in signature(hook).parameters:
819
+ hook_args["function_call"] = func
820
+ if "args" in signature(hook).parameters:
821
+ hook_args["args"] = args
822
+ if "arguments" in signature(hook).parameters:
823
+ hook_args["arguments"] = args
824
+ return hook_args
825
+
826
+ def _build_nested_execution_chain(self, entrypoint_args: Dict[str, Any]):
827
+ """Build a nested chain of hook executions with the entrypoint at the center.
828
+
829
+ This creates a chain where each hook wraps the next one, with the function call
830
+ at the innermost level. Returns bubble back up through each hook.
831
+ """
832
+ from functools import reduce
833
+ from inspect import iscoroutinefunction
834
+
835
+ def execute_entrypoint(name, func, args):
836
+ """Execute the entrypoint function."""
837
+ arguments = entrypoint_args.copy()
838
+ if self.arguments is not None:
839
+ arguments.update(self.arguments)
840
+ return self.function.entrypoint(**arguments) # type: ignore
841
+
842
+ # If no hooks, just return the entrypoint execution function
843
+ if not self.function.tool_hooks:
844
+ return execute_entrypoint
845
+
846
+ def create_hook_wrapper(inner_func, hook):
847
+ """Create a nested wrapper for the hook."""
848
+
849
+ def wrapper(name, func, args):
850
+ # Pass the inner function as next_func to the hook
851
+ # The hook will call next_func to continue the chain
852
+ def next_func(**kwargs):
853
+ return inner_func(name, func, kwargs)
854
+
855
+ hook_args = self._build_hook_args(hook, name, next_func, args)
856
+
857
+ return hook(**hook_args)
858
+
859
+ return wrapper
860
+
861
+ # Remove coroutine hooks
862
+ final_hooks = []
863
+ for hook in self.function.tool_hooks:
864
+ if iscoroutinefunction(hook):
865
+ log_warning(f"Cannot use async hooks with sync function calls. Skipping hook: {hook.__name__}")
866
+ else:
867
+ final_hooks.append(hook)
868
+
869
+ # Build the chain from inside out - reverse the hooks to start from the innermost
870
+ hooks = list(reversed(final_hooks))
871
+ chain = reduce(create_hook_wrapper, hooks, execute_entrypoint)
872
+ return chain
873
+
874
+ def execute(self) -> FunctionExecutionResult:
875
+ """Runs the function call."""
876
+ from inspect import isgenerator, isgeneratorfunction
877
+
878
+ if self.function.entrypoint is None:
879
+ return FunctionExecutionResult(status="failure", error="Entrypoint is not set")
880
+
881
+ log_debug(f"Running: {self.get_call_str()}")
882
+
883
+ # Execute pre-hook if it exists
884
+ self._handle_pre_hook()
885
+
886
+ entrypoint_args = self._build_entrypoint_args()
887
+
888
+ # Check cache if enabled and not a generator function
889
+ if self.function.cache_results and not isgeneratorfunction(self.function.entrypoint):
890
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
891
+ cache_file = self.function._get_cache_file_path(cache_key)
892
+ cached_result = self.function._get_cached_result(cache_file)
893
+
894
+ if cached_result is not None:
895
+ log_debug(f"Cache hit for: {self.get_call_str()}")
896
+ self.result = cached_result
897
+ return FunctionExecutionResult(status="success", result=cached_result)
898
+
899
+ # Execute function
900
+ execution_result = None
901
+ exception_to_raise = None
902
+
903
+ try:
904
+ # Build and execute the nested chain of hooks
905
+ if self.function.tool_hooks is not None:
906
+ execution_chain = self._build_nested_execution_chain(entrypoint_args=entrypoint_args)
907
+ result = execution_chain(self.function.name, self.function.entrypoint, self.arguments or {})
908
+ else:
909
+ result = self.function.entrypoint(**entrypoint_args, **self.arguments) # type: ignore
910
+
911
+ updated_session_state = None
912
+ if entrypoint_args.get("run_context") is not None:
913
+ run_context = entrypoint_args.get("run_context")
914
+ updated_session_state = (
915
+ run_context.session_state
916
+ if run_context is not None and run_context.session_state is not None
917
+ else None
918
+ )
919
+ else:
920
+ if self.function._session_state is not None:
921
+ updated_session_state = self.function._session_state
922
+
923
+ # Handle generator case
924
+ if isgenerator(result):
925
+ self.result = result # Store generator directly, can't cache
926
+ else:
927
+ self.result = result
928
+ # Only cache non-generator results
929
+ if self.function.cache_results:
930
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
931
+ cache_file = self.function._get_cache_file_path(cache_key)
932
+ self.function._save_to_cache(cache_file, self.result)
933
+
934
+ execution_result = FunctionExecutionResult(
935
+ status="success", result=self.result, updated_session_state=updated_session_state
936
+ )
937
+
938
+ except AgentRunException as e:
939
+ log_debug(f"{e.__class__.__name__}: {e}")
940
+ self.error = str(e)
941
+ exception_to_raise = e
942
+ except Exception as e:
943
+ log_warning(f"Could not run function {self.get_call_str()}")
944
+ log_exception(e)
945
+ self.error = str(e)
946
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
947
+
948
+ finally:
949
+ self._handle_post_hook()
950
+
951
+ if exception_to_raise is not None:
952
+ raise exception_to_raise
953
+
954
+ return execution_result # type: ignore[return-value]
955
+
956
+ async def _handle_pre_hook_async(self):
957
+ """Handles the async pre-hook for the function call."""
958
+ if self.function.pre_hook is not None:
959
+ try:
960
+ from inspect import signature
961
+
962
+ pre_hook_args = {}
963
+ # Check if the pre-hook has an agent argument
964
+ if "agent" in signature(self.function.pre_hook).parameters:
965
+ pre_hook_args["agent"] = self.function._agent
966
+ # Check if the pre-hook has an team argument
967
+ if "team" in signature(self.function.pre_hook).parameters:
968
+ pre_hook_args["team"] = self.function._team
969
+ # Check if the pre-hook has an run_context argument
970
+ if "run_context" in signature(self.function.pre_hook).parameters:
971
+ pre_hook_args["run_context"] = self.function._run_context
972
+ # Check if the pre-hook has an session_state argument
973
+ if "session_state" in signature(self.function.pre_hook).parameters:
974
+ pre_hook_args["session_state"] = self.function._session_state
975
+ # Check if the pre-hook has an dependencies argument
976
+ if "dependencies" in signature(self.function.pre_hook).parameters:
977
+ pre_hook_args["dependencies"] = self.function._dependencies
978
+ # Check if the pre-hook has an fc argument
979
+ if "fc" in signature(self.function.pre_hook).parameters:
980
+ pre_hook_args["fc"] = self
981
+
982
+ await self.function.pre_hook(**pre_hook_args)
983
+ except AgentRunException as e:
984
+ log_debug(f"{e.__class__.__name__}: {e}")
985
+ self.error = str(e)
986
+ raise
987
+ except Exception as e:
988
+ log_warning(f"Error in pre-hook callback: {e}")
989
+ log_exception(e)
990
+
991
+ async def _handle_post_hook_async(self):
992
+ """Handles the async post-hook for the function call."""
993
+ if self.function.post_hook is not None:
994
+ try:
995
+ from inspect import signature
996
+
997
+ post_hook_args = {}
998
+ # Check if the post-hook has an agent argument
999
+ if "agent" in signature(self.function.post_hook).parameters:
1000
+ post_hook_args["agent"] = self.function._agent
1001
+ # Check if the post-hook has an team argument
1002
+ if "team" in signature(self.function.post_hook).parameters:
1003
+ post_hook_args["team"] = self.function._team
1004
+ # Check if the post-hook has an run_context argument
1005
+ if "run_context" in signature(self.function.post_hook).parameters:
1006
+ post_hook_args["run_context"] = self.function._run_context
1007
+ # Check if the post-hook has an session_state argument
1008
+ if "session_state" in signature(self.function.post_hook).parameters:
1009
+ post_hook_args["session_state"] = self.function._session_state
1010
+ # Check if the post-hook has an dependencies argument
1011
+ if "dependencies" in signature(self.function.post_hook).parameters:
1012
+ post_hook_args["dependencies"] = self.function._dependencies
1013
+
1014
+ # Check if the post-hook has an fc argument
1015
+ if "fc" in signature(self.function.post_hook).parameters:
1016
+ post_hook_args["fc"] = self
1017
+
1018
+ await self.function.post_hook(**post_hook_args)
1019
+ except AgentRunException as e:
1020
+ log_debug(f"{e.__class__.__name__}: {e}")
1021
+ self.error = str(e)
1022
+ raise
1023
+ except Exception as e:
1024
+ log_warning(f"Error in post-hook callback: {e}")
1025
+ log_exception(e)
1026
+
1027
+ async def _build_nested_execution_chain_async(self, entrypoint_args: Dict[str, Any]):
1028
+ """Build a nested chain of async hook executions with the entrypoint at the center.
1029
+
1030
+ Similar to _build_nested_execution_chain but for async execution.
1031
+ """
1032
+ from functools import reduce
1033
+ from inspect import isasyncgenfunction, iscoroutinefunction
1034
+
1035
+ async def execute_entrypoint_async(name, func, args):
1036
+ """Execute the entrypoint function asynchronously."""
1037
+ arguments = entrypoint_args.copy()
1038
+ if self.arguments is not None:
1039
+ arguments.update(self.arguments)
1040
+
1041
+ result = self.function.entrypoint(**arguments) # type: ignore
1042
+ if iscoroutinefunction(self.function.entrypoint) and not isasyncgenfunction(self.function.entrypoint):
1043
+ result = await result
1044
+ return result
1045
+
1046
+ def execute_entrypoint(name, func, args):
1047
+ """Execute the entrypoint function synchronously."""
1048
+ arguments = entrypoint_args.copy()
1049
+ if self.arguments is not None:
1050
+ arguments.update(self.arguments)
1051
+ return self.function.entrypoint(**arguments) # type: ignore
1052
+
1053
+ # If no hooks, just return the entrypoint execution function
1054
+ if not self.function.tool_hooks:
1055
+ return execute_entrypoint
1056
+
1057
+ def create_hook_wrapper(inner_func, hook):
1058
+ """Create a nested wrapper for the hook."""
1059
+
1060
+ async def wrapper(name, func, args):
1061
+ """Create a nested wrapper for the hook."""
1062
+
1063
+ # Pass the inner function as next_func to the hook
1064
+ # The hook will call next_func to continue the chain
1065
+ async def next_func(**kwargs):
1066
+ if iscoroutinefunction(inner_func):
1067
+ return await inner_func(name, func, kwargs)
1068
+ else:
1069
+ return inner_func(name, func, kwargs)
1070
+
1071
+ hook_args = self._build_hook_args(hook, name, next_func, args)
1072
+
1073
+ if iscoroutinefunction(hook):
1074
+ return await hook(**hook_args)
1075
+ else:
1076
+ return hook(**hook_args)
1077
+
1078
+ return wrapper
1079
+
1080
+ # Build the chain from inside out - reverse the hooks to start from the innermost
1081
+ hooks = list(reversed(self.function.tool_hooks))
1082
+
1083
+ # Handle async and sync entrypoints
1084
+ if iscoroutinefunction(self.function.entrypoint):
1085
+ chain = reduce(create_hook_wrapper, hooks, execute_entrypoint_async)
1086
+ else:
1087
+ chain = reduce(create_hook_wrapper, hooks, execute_entrypoint)
1088
+ return chain
1089
+
1090
+ async def aexecute(self) -> FunctionExecutionResult:
1091
+ """Runs the function call asynchronously."""
1092
+ from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction, isgenerator, isgeneratorfunction
1093
+
1094
+ if self.function.entrypoint is None:
1095
+ return FunctionExecutionResult(status="failure", error="Entrypoint is not set")
1096
+
1097
+ log_debug(f"Running: {self.get_call_str()}")
1098
+
1099
+ # Execute pre-hook if it exists
1100
+ if iscoroutinefunction(self.function.pre_hook):
1101
+ await self._handle_pre_hook_async()
1102
+ else:
1103
+ self._handle_pre_hook()
1104
+
1105
+ entrypoint_args = self._build_entrypoint_args()
1106
+
1107
+ # Check cache if enabled and not a generator function
1108
+ if self.function.cache_results and not (
1109
+ isasyncgenfunction(self.function.entrypoint) or isgeneratorfunction(self.function.entrypoint)
1110
+ ):
1111
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
1112
+ cache_file = self.function._get_cache_file_path(cache_key)
1113
+ cached_result = self.function._get_cached_result(cache_file)
1114
+ if cached_result is not None:
1115
+ log_debug(f"Cache hit for: {self.get_call_str()}")
1116
+ self.result = cached_result
1117
+ return FunctionExecutionResult(status="success", result=cached_result)
1118
+
1119
+ # Execute function
1120
+ execution_result = None
1121
+ exception_to_raise = None
1122
+
1123
+ try:
1124
+ # Build and execute the nested chain of hooks
1125
+ if self.function.tool_hooks is not None:
1126
+ execution_chain = await self._build_nested_execution_chain_async(entrypoint_args)
1127
+ self.result = await execution_chain(self.function.name, self.function.entrypoint, self.arguments or {})
1128
+ else:
1129
+ if self.arguments is None or self.arguments == {}:
1130
+ result = self.function.entrypoint(**entrypoint_args)
1131
+ else:
1132
+ result = self.function.entrypoint(**entrypoint_args, **self.arguments)
1133
+
1134
+ if isasyncgenfunction(self.function.entrypoint):
1135
+ self.result = result # Store async generator directly
1136
+ else:
1137
+ self.result = await result
1138
+
1139
+ # Only cache if not a generator
1140
+ if self.function.cache_results and not (isgenerator(self.result) or isasyncgen(self.result)):
1141
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
1142
+ cache_file = self.function._get_cache_file_path(cache_key)
1143
+ self.function._save_to_cache(cache_file, self.result)
1144
+
1145
+ updated_session_state = None
1146
+ if entrypoint_args.get("run_context") is not None:
1147
+ run_context = entrypoint_args.get("run_context")
1148
+ updated_session_state = (
1149
+ run_context.session_state
1150
+ if run_context is not None and run_context.session_state is not None
1151
+ else None
1152
+ )
1153
+
1154
+ execution_result = FunctionExecutionResult(
1155
+ status="success", result=self.result, updated_session_state=updated_session_state
1156
+ )
1157
+
1158
+ except AgentRunException as e:
1159
+ log_debug(f"{e.__class__.__name__}: {e}")
1160
+ self.error = str(e)
1161
+ exception_to_raise = e
1162
+ except Exception as e:
1163
+ log_warning(f"Could not run function {self.get_call_str()}")
1164
+ log_exception(e)
1165
+ self.error = str(e)
1166
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
1167
+
1168
+ finally:
1169
+ if iscoroutinefunction(self.function.post_hook):
1170
+ await self._handle_post_hook_async()
1171
+ else:
1172
+ self._handle_post_hook()
1173
+
1174
+ if exception_to_raise is not None:
1175
+ raise exception_to_raise
1176
+
1177
+ return execution_result # type: ignore[return-value]
1178
+
1179
+
1180
+ class ToolResult(BaseModel):
1181
+ """Result from a tool that can include media artifacts."""
1182
+
1183
+ content: str
1184
+ images: Optional[List[Image]] = None
1185
+ videos: Optional[List[Video]] = None
1186
+ audios: Optional[List[Audio]] = None
1187
+ files: Optional[List[File]] = None