agno 0.1.2__py3-none-any.whl → 2.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (723) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +44 -5
  3. agno/agent/agent.py +10531 -2975
  4. agno/api/agent.py +14 -53
  5. agno/api/api.py +7 -46
  6. agno/api/evals.py +22 -0
  7. agno/api/os.py +17 -0
  8. agno/api/routes.py +6 -25
  9. agno/api/schemas/__init__.py +9 -0
  10. agno/api/schemas/agent.py +6 -9
  11. agno/api/schemas/evals.py +16 -0
  12. agno/api/schemas/os.py +14 -0
  13. agno/api/schemas/team.py +10 -10
  14. agno/api/schemas/utils.py +21 -0
  15. agno/api/schemas/workflows.py +16 -0
  16. agno/api/settings.py +53 -0
  17. agno/api/team.py +22 -26
  18. agno/api/workflow.py +28 -0
  19. agno/cloud/aws/base.py +214 -0
  20. agno/cloud/aws/s3/__init__.py +2 -0
  21. agno/cloud/aws/s3/api_client.py +43 -0
  22. agno/cloud/aws/s3/bucket.py +195 -0
  23. agno/cloud/aws/s3/object.py +57 -0
  24. agno/compression/__init__.py +3 -0
  25. agno/compression/manager.py +247 -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 +946 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2781 -0
  33. agno/db/dynamo/schemas.py +442 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +2379 -0
  37. agno/db/firestore/schemas.py +181 -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 +1791 -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 +1312 -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 +1777 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/manager.py +199 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/migrations/versions/v2_3_0.py +938 -0
  51. agno/db/mongo/__init__.py +17 -0
  52. agno/db/mongo/async_mongo.py +2760 -0
  53. agno/db/mongo/mongo.py +2597 -0
  54. agno/db/mongo/schemas.py +119 -0
  55. agno/db/mongo/utils.py +276 -0
  56. agno/db/mysql/__init__.py +4 -0
  57. agno/db/mysql/async_mysql.py +2912 -0
  58. agno/db/mysql/mysql.py +2923 -0
  59. agno/db/mysql/schemas.py +186 -0
  60. agno/db/mysql/utils.py +488 -0
  61. agno/db/postgres/__init__.py +4 -0
  62. agno/db/postgres/async_postgres.py +2579 -0
  63. agno/db/postgres/postgres.py +2870 -0
  64. agno/db/postgres/schemas.py +187 -0
  65. agno/db/postgres/utils.py +442 -0
  66. agno/db/redis/__init__.py +3 -0
  67. agno/db/redis/redis.py +2141 -0
  68. agno/db/redis/schemas.py +159 -0
  69. agno/db/redis/utils.py +346 -0
  70. agno/db/schemas/__init__.py +4 -0
  71. agno/db/schemas/culture.py +120 -0
  72. agno/db/schemas/evals.py +34 -0
  73. agno/db/schemas/knowledge.py +40 -0
  74. agno/db/schemas/memory.py +61 -0
  75. agno/db/singlestore/__init__.py +3 -0
  76. agno/db/singlestore/schemas.py +179 -0
  77. agno/db/singlestore/singlestore.py +2877 -0
  78. agno/db/singlestore/utils.py +384 -0
  79. agno/db/sqlite/__init__.py +4 -0
  80. agno/db/sqlite/async_sqlite.py +2911 -0
  81. agno/db/sqlite/schemas.py +181 -0
  82. agno/db/sqlite/sqlite.py +2908 -0
  83. agno/db/sqlite/utils.py +429 -0
  84. agno/db/surrealdb/__init__.py +3 -0
  85. agno/db/surrealdb/metrics.py +292 -0
  86. agno/db/surrealdb/models.py +334 -0
  87. agno/db/surrealdb/queries.py +71 -0
  88. agno/db/surrealdb/surrealdb.py +1908 -0
  89. agno/db/surrealdb/utils.py +147 -0
  90. agno/db/utils.py +118 -0
  91. agno/eval/__init__.py +24 -0
  92. agno/eval/accuracy.py +666 -276
  93. agno/eval/agent_as_judge.py +861 -0
  94. agno/eval/base.py +29 -0
  95. agno/eval/performance.py +779 -0
  96. agno/eval/reliability.py +241 -62
  97. agno/eval/utils.py +120 -0
  98. agno/exceptions.py +143 -1
  99. agno/filters.py +354 -0
  100. agno/guardrails/__init__.py +6 -0
  101. agno/guardrails/base.py +19 -0
  102. agno/guardrails/openai.py +144 -0
  103. agno/guardrails/pii.py +94 -0
  104. agno/guardrails/prompt_injection.py +52 -0
  105. agno/hooks/__init__.py +3 -0
  106. agno/hooks/decorator.py +164 -0
  107. agno/integrations/discord/__init__.py +3 -0
  108. agno/integrations/discord/client.py +203 -0
  109. agno/knowledge/__init__.py +5 -1
  110. agno/{document → knowledge}/chunking/agentic.py +22 -14
  111. agno/{document → knowledge}/chunking/document.py +2 -2
  112. agno/{document → knowledge}/chunking/fixed.py +7 -6
  113. agno/knowledge/chunking/markdown.py +151 -0
  114. agno/{document → knowledge}/chunking/recursive.py +15 -3
  115. agno/knowledge/chunking/row.py +39 -0
  116. agno/knowledge/chunking/semantic.py +91 -0
  117. agno/knowledge/chunking/strategy.py +165 -0
  118. agno/knowledge/content.py +74 -0
  119. agno/knowledge/document/__init__.py +5 -0
  120. agno/{document → knowledge/document}/base.py +12 -2
  121. agno/knowledge/embedder/__init__.py +5 -0
  122. agno/knowledge/embedder/aws_bedrock.py +343 -0
  123. agno/knowledge/embedder/azure_openai.py +210 -0
  124. agno/{embedder → knowledge/embedder}/base.py +8 -0
  125. agno/knowledge/embedder/cohere.py +323 -0
  126. agno/knowledge/embedder/fastembed.py +62 -0
  127. agno/{embedder → knowledge/embedder}/fireworks.py +1 -1
  128. agno/knowledge/embedder/google.py +258 -0
  129. agno/knowledge/embedder/huggingface.py +94 -0
  130. agno/knowledge/embedder/jina.py +182 -0
  131. agno/knowledge/embedder/langdb.py +22 -0
  132. agno/knowledge/embedder/mistral.py +206 -0
  133. agno/knowledge/embedder/nebius.py +13 -0
  134. agno/knowledge/embedder/ollama.py +154 -0
  135. agno/knowledge/embedder/openai.py +195 -0
  136. agno/knowledge/embedder/sentence_transformer.py +63 -0
  137. agno/{embedder → knowledge/embedder}/together.py +1 -1
  138. agno/knowledge/embedder/vllm.py +262 -0
  139. agno/knowledge/embedder/voyageai.py +165 -0
  140. agno/knowledge/knowledge.py +3006 -0
  141. agno/knowledge/reader/__init__.py +7 -0
  142. agno/knowledge/reader/arxiv_reader.py +81 -0
  143. agno/knowledge/reader/base.py +95 -0
  144. agno/knowledge/reader/csv_reader.py +164 -0
  145. agno/knowledge/reader/docx_reader.py +82 -0
  146. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  147. agno/knowledge/reader/firecrawl_reader.py +201 -0
  148. agno/knowledge/reader/json_reader.py +88 -0
  149. agno/knowledge/reader/markdown_reader.py +137 -0
  150. agno/knowledge/reader/pdf_reader.py +431 -0
  151. agno/knowledge/reader/pptx_reader.py +101 -0
  152. agno/knowledge/reader/reader_factory.py +313 -0
  153. agno/knowledge/reader/s3_reader.py +89 -0
  154. agno/knowledge/reader/tavily_reader.py +193 -0
  155. agno/knowledge/reader/text_reader.py +127 -0
  156. agno/knowledge/reader/web_search_reader.py +325 -0
  157. agno/knowledge/reader/website_reader.py +455 -0
  158. agno/knowledge/reader/wikipedia_reader.py +91 -0
  159. agno/knowledge/reader/youtube_reader.py +78 -0
  160. agno/knowledge/remote_content/remote_content.py +88 -0
  161. agno/knowledge/reranker/__init__.py +3 -0
  162. agno/{reranker → knowledge/reranker}/base.py +1 -1
  163. agno/{reranker → knowledge/reranker}/cohere.py +2 -2
  164. agno/knowledge/reranker/infinity.py +195 -0
  165. agno/knowledge/reranker/sentence_transformer.py +54 -0
  166. agno/knowledge/types.py +39 -0
  167. agno/knowledge/utils.py +234 -0
  168. agno/media.py +439 -95
  169. agno/memory/__init__.py +16 -3
  170. agno/memory/manager.py +1474 -123
  171. agno/memory/strategies/__init__.py +15 -0
  172. agno/memory/strategies/base.py +66 -0
  173. agno/memory/strategies/summarize.py +196 -0
  174. agno/memory/strategies/types.py +37 -0
  175. agno/models/aimlapi/__init__.py +5 -0
  176. agno/models/aimlapi/aimlapi.py +62 -0
  177. agno/models/anthropic/__init__.py +4 -0
  178. agno/models/anthropic/claude.py +960 -496
  179. agno/models/aws/__init__.py +15 -0
  180. agno/models/aws/bedrock.py +686 -451
  181. agno/models/aws/claude.py +190 -183
  182. agno/models/azure/__init__.py +18 -1
  183. agno/models/azure/ai_foundry.py +489 -0
  184. agno/models/azure/openai_chat.py +89 -40
  185. agno/models/base.py +2477 -550
  186. agno/models/cerebras/__init__.py +12 -0
  187. agno/models/cerebras/cerebras.py +565 -0
  188. agno/models/cerebras/cerebras_openai.py +131 -0
  189. agno/models/cohere/__init__.py +4 -0
  190. agno/models/cohere/chat.py +306 -492
  191. agno/models/cometapi/__init__.py +5 -0
  192. agno/models/cometapi/cometapi.py +74 -0
  193. agno/models/dashscope/__init__.py +5 -0
  194. agno/models/dashscope/dashscope.py +90 -0
  195. agno/models/deepinfra/__init__.py +5 -0
  196. agno/models/deepinfra/deepinfra.py +45 -0
  197. agno/models/deepseek/__init__.py +4 -0
  198. agno/models/deepseek/deepseek.py +110 -9
  199. agno/models/fireworks/__init__.py +4 -0
  200. agno/models/fireworks/fireworks.py +19 -22
  201. agno/models/google/__init__.py +3 -7
  202. agno/models/google/gemini.py +1717 -662
  203. agno/models/google/utils.py +22 -0
  204. agno/models/groq/__init__.py +4 -0
  205. agno/models/groq/groq.py +391 -666
  206. agno/models/huggingface/__init__.py +4 -0
  207. agno/models/huggingface/huggingface.py +266 -538
  208. agno/models/ibm/__init__.py +5 -0
  209. agno/models/ibm/watsonx.py +432 -0
  210. agno/models/internlm/__init__.py +3 -0
  211. agno/models/internlm/internlm.py +20 -3
  212. agno/models/langdb/__init__.py +1 -0
  213. agno/models/langdb/langdb.py +60 -0
  214. agno/models/litellm/__init__.py +14 -0
  215. agno/models/litellm/chat.py +503 -0
  216. agno/models/litellm/litellm_openai.py +42 -0
  217. agno/models/llama_cpp/__init__.py +5 -0
  218. agno/models/llama_cpp/llama_cpp.py +22 -0
  219. agno/models/lmstudio/__init__.py +5 -0
  220. agno/models/lmstudio/lmstudio.py +25 -0
  221. agno/models/message.py +361 -39
  222. agno/models/meta/__init__.py +12 -0
  223. agno/models/meta/llama.py +502 -0
  224. agno/models/meta/llama_openai.py +79 -0
  225. agno/models/metrics.py +120 -0
  226. agno/models/mistral/__init__.py +4 -0
  227. agno/models/mistral/mistral.py +293 -393
  228. agno/models/nebius/__init__.py +3 -0
  229. agno/models/nebius/nebius.py +53 -0
  230. agno/models/nexus/__init__.py +3 -0
  231. agno/models/nexus/nexus.py +22 -0
  232. agno/models/nvidia/__init__.py +4 -0
  233. agno/models/nvidia/nvidia.py +22 -3
  234. agno/models/ollama/__init__.py +4 -2
  235. agno/models/ollama/chat.py +257 -492
  236. agno/models/openai/__init__.py +7 -0
  237. agno/models/openai/chat.py +725 -770
  238. agno/models/openai/like.py +16 -2
  239. agno/models/openai/responses.py +1121 -0
  240. agno/models/openrouter/__init__.py +4 -0
  241. agno/models/openrouter/openrouter.py +62 -5
  242. agno/models/perplexity/__init__.py +5 -0
  243. agno/models/perplexity/perplexity.py +203 -0
  244. agno/models/portkey/__init__.py +3 -0
  245. agno/models/portkey/portkey.py +82 -0
  246. agno/models/requesty/__init__.py +5 -0
  247. agno/models/requesty/requesty.py +69 -0
  248. agno/models/response.py +177 -7
  249. agno/models/sambanova/__init__.py +4 -0
  250. agno/models/sambanova/sambanova.py +23 -4
  251. agno/models/siliconflow/__init__.py +5 -0
  252. agno/models/siliconflow/siliconflow.py +42 -0
  253. agno/models/together/__init__.py +4 -0
  254. agno/models/together/together.py +21 -164
  255. agno/models/utils.py +266 -0
  256. agno/models/vercel/__init__.py +3 -0
  257. agno/models/vercel/v0.py +43 -0
  258. agno/models/vertexai/__init__.py +0 -1
  259. agno/models/vertexai/claude.py +190 -0
  260. agno/models/vllm/__init__.py +3 -0
  261. agno/models/vllm/vllm.py +83 -0
  262. agno/models/xai/__init__.py +2 -0
  263. agno/models/xai/xai.py +111 -7
  264. agno/os/__init__.py +3 -0
  265. agno/os/app.py +1027 -0
  266. agno/os/auth.py +244 -0
  267. agno/os/config.py +126 -0
  268. agno/os/interfaces/__init__.py +1 -0
  269. agno/os/interfaces/a2a/__init__.py +3 -0
  270. agno/os/interfaces/a2a/a2a.py +42 -0
  271. agno/os/interfaces/a2a/router.py +249 -0
  272. agno/os/interfaces/a2a/utils.py +924 -0
  273. agno/os/interfaces/agui/__init__.py +3 -0
  274. agno/os/interfaces/agui/agui.py +47 -0
  275. agno/os/interfaces/agui/router.py +147 -0
  276. agno/os/interfaces/agui/utils.py +574 -0
  277. agno/os/interfaces/base.py +25 -0
  278. agno/os/interfaces/slack/__init__.py +3 -0
  279. agno/os/interfaces/slack/router.py +148 -0
  280. agno/os/interfaces/slack/security.py +30 -0
  281. agno/os/interfaces/slack/slack.py +47 -0
  282. agno/os/interfaces/whatsapp/__init__.py +3 -0
  283. agno/os/interfaces/whatsapp/router.py +210 -0
  284. agno/os/interfaces/whatsapp/security.py +55 -0
  285. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  286. agno/os/mcp.py +293 -0
  287. agno/os/middleware/__init__.py +9 -0
  288. agno/os/middleware/jwt.py +797 -0
  289. agno/os/router.py +258 -0
  290. agno/os/routers/__init__.py +3 -0
  291. agno/os/routers/agents/__init__.py +3 -0
  292. agno/os/routers/agents/router.py +599 -0
  293. agno/os/routers/agents/schema.py +261 -0
  294. agno/os/routers/evals/__init__.py +3 -0
  295. agno/os/routers/evals/evals.py +450 -0
  296. agno/os/routers/evals/schemas.py +174 -0
  297. agno/os/routers/evals/utils.py +231 -0
  298. agno/os/routers/health.py +31 -0
  299. agno/os/routers/home.py +52 -0
  300. agno/os/routers/knowledge/__init__.py +3 -0
  301. agno/os/routers/knowledge/knowledge.py +1008 -0
  302. agno/os/routers/knowledge/schemas.py +178 -0
  303. agno/os/routers/memory/__init__.py +3 -0
  304. agno/os/routers/memory/memory.py +661 -0
  305. agno/os/routers/memory/schemas.py +88 -0
  306. agno/os/routers/metrics/__init__.py +3 -0
  307. agno/os/routers/metrics/metrics.py +190 -0
  308. agno/os/routers/metrics/schemas.py +47 -0
  309. agno/os/routers/session/__init__.py +3 -0
  310. agno/os/routers/session/session.py +997 -0
  311. agno/os/routers/teams/__init__.py +3 -0
  312. agno/os/routers/teams/router.py +512 -0
  313. agno/os/routers/teams/schema.py +257 -0
  314. agno/os/routers/traces/__init__.py +3 -0
  315. agno/os/routers/traces/schemas.py +414 -0
  316. agno/os/routers/traces/traces.py +499 -0
  317. agno/os/routers/workflows/__init__.py +3 -0
  318. agno/os/routers/workflows/router.py +624 -0
  319. agno/os/routers/workflows/schema.py +75 -0
  320. agno/os/schema.py +534 -0
  321. agno/os/scopes.py +469 -0
  322. agno/{playground → os}/settings.py +7 -15
  323. agno/os/utils.py +973 -0
  324. agno/reasoning/anthropic.py +80 -0
  325. agno/reasoning/azure_ai_foundry.py +67 -0
  326. agno/reasoning/deepseek.py +63 -0
  327. agno/reasoning/default.py +97 -0
  328. agno/reasoning/gemini.py +73 -0
  329. agno/reasoning/groq.py +71 -0
  330. agno/reasoning/helpers.py +24 -1
  331. agno/reasoning/ollama.py +67 -0
  332. agno/reasoning/openai.py +86 -0
  333. agno/reasoning/step.py +2 -1
  334. agno/reasoning/vertexai.py +76 -0
  335. agno/run/__init__.py +6 -0
  336. agno/run/agent.py +822 -0
  337. agno/run/base.py +247 -0
  338. agno/run/cancel.py +81 -0
  339. agno/run/requirement.py +181 -0
  340. agno/run/team.py +767 -0
  341. agno/run/workflow.py +708 -0
  342. agno/session/__init__.py +10 -0
  343. agno/session/agent.py +260 -0
  344. agno/session/summary.py +265 -0
  345. agno/session/team.py +342 -0
  346. agno/session/workflow.py +501 -0
  347. agno/table.py +10 -0
  348. agno/team/__init__.py +37 -0
  349. agno/team/team.py +9536 -0
  350. agno/tools/__init__.py +7 -0
  351. agno/tools/agentql.py +120 -0
  352. agno/tools/airflow.py +22 -12
  353. agno/tools/api.py +122 -0
  354. agno/tools/apify.py +276 -83
  355. agno/tools/{arxiv_toolkit.py → arxiv.py} +20 -12
  356. agno/tools/aws_lambda.py +28 -7
  357. agno/tools/aws_ses.py +66 -0
  358. agno/tools/baidusearch.py +11 -4
  359. agno/tools/bitbucket.py +292 -0
  360. agno/tools/brandfetch.py +213 -0
  361. agno/tools/bravesearch.py +106 -0
  362. agno/tools/brightdata.py +367 -0
  363. agno/tools/browserbase.py +209 -0
  364. agno/tools/calcom.py +32 -23
  365. agno/tools/calculator.py +24 -37
  366. agno/tools/cartesia.py +187 -0
  367. agno/tools/{clickup_tool.py → clickup.py} +17 -28
  368. agno/tools/confluence.py +91 -26
  369. agno/tools/crawl4ai.py +139 -43
  370. agno/tools/csv_toolkit.py +28 -22
  371. agno/tools/dalle.py +36 -22
  372. agno/tools/daytona.py +475 -0
  373. agno/tools/decorator.py +169 -14
  374. agno/tools/desi_vocal.py +23 -11
  375. agno/tools/discord.py +32 -29
  376. agno/tools/docker.py +716 -0
  377. agno/tools/duckdb.py +76 -81
  378. agno/tools/duckduckgo.py +43 -40
  379. agno/tools/e2b.py +703 -0
  380. agno/tools/eleven_labs.py +65 -54
  381. agno/tools/email.py +13 -5
  382. agno/tools/evm.py +129 -0
  383. agno/tools/exa.py +324 -42
  384. agno/tools/fal.py +39 -35
  385. agno/tools/file.py +196 -30
  386. agno/tools/file_generation.py +356 -0
  387. agno/tools/financial_datasets.py +288 -0
  388. agno/tools/firecrawl.py +108 -33
  389. agno/tools/function.py +960 -122
  390. agno/tools/giphy.py +34 -12
  391. agno/tools/github.py +1294 -97
  392. agno/tools/gmail.py +922 -0
  393. agno/tools/google_bigquery.py +117 -0
  394. agno/tools/google_drive.py +271 -0
  395. agno/tools/google_maps.py +253 -0
  396. agno/tools/googlecalendar.py +607 -107
  397. agno/tools/googlesheets.py +377 -0
  398. agno/tools/hackernews.py +20 -12
  399. agno/tools/jina.py +24 -14
  400. agno/tools/jira.py +48 -19
  401. agno/tools/knowledge.py +218 -0
  402. agno/tools/linear.py +82 -43
  403. agno/tools/linkup.py +58 -0
  404. agno/tools/local_file_system.py +15 -7
  405. agno/tools/lumalab.py +41 -26
  406. agno/tools/mcp/__init__.py +10 -0
  407. agno/tools/mcp/mcp.py +331 -0
  408. agno/tools/mcp/multi_mcp.py +347 -0
  409. agno/tools/mcp/params.py +24 -0
  410. agno/tools/mcp_toolbox.py +284 -0
  411. agno/tools/mem0.py +193 -0
  412. agno/tools/memory.py +419 -0
  413. agno/tools/mlx_transcribe.py +11 -9
  414. agno/tools/models/azure_openai.py +190 -0
  415. agno/tools/models/gemini.py +203 -0
  416. agno/tools/models/groq.py +158 -0
  417. agno/tools/models/morph.py +186 -0
  418. agno/tools/models/nebius.py +124 -0
  419. agno/tools/models_labs.py +163 -82
  420. agno/tools/moviepy_video.py +18 -13
  421. agno/tools/nano_banana.py +151 -0
  422. agno/tools/neo4j.py +134 -0
  423. agno/tools/newspaper.py +15 -4
  424. agno/tools/newspaper4k.py +19 -6
  425. agno/tools/notion.py +204 -0
  426. agno/tools/openai.py +181 -17
  427. agno/tools/openbb.py +27 -20
  428. agno/tools/opencv.py +321 -0
  429. agno/tools/openweather.py +233 -0
  430. agno/tools/oxylabs.py +385 -0
  431. agno/tools/pandas.py +25 -15
  432. agno/tools/parallel.py +314 -0
  433. agno/tools/postgres.py +238 -185
  434. agno/tools/pubmed.py +125 -13
  435. agno/tools/python.py +48 -35
  436. agno/tools/reasoning.py +283 -0
  437. agno/tools/reddit.py +207 -29
  438. agno/tools/redshift.py +406 -0
  439. agno/tools/replicate.py +69 -26
  440. agno/tools/resend.py +11 -6
  441. agno/tools/scrapegraph.py +179 -19
  442. agno/tools/searxng.py +23 -31
  443. agno/tools/serpapi.py +15 -10
  444. agno/tools/serper.py +255 -0
  445. agno/tools/shell.py +23 -12
  446. agno/tools/shopify.py +1519 -0
  447. agno/tools/slack.py +56 -14
  448. agno/tools/sleep.py +8 -6
  449. agno/tools/spider.py +35 -11
  450. agno/tools/spotify.py +919 -0
  451. agno/tools/sql.py +34 -19
  452. agno/tools/tavily.py +158 -8
  453. agno/tools/telegram.py +18 -8
  454. agno/tools/todoist.py +218 -0
  455. agno/tools/toolkit.py +134 -9
  456. agno/tools/trafilatura.py +388 -0
  457. agno/tools/trello.py +25 -28
  458. agno/tools/twilio.py +18 -9
  459. agno/tools/user_control_flow.py +78 -0
  460. agno/tools/valyu.py +228 -0
  461. agno/tools/visualization.py +467 -0
  462. agno/tools/webbrowser.py +28 -0
  463. agno/tools/webex.py +76 -0
  464. agno/tools/website.py +23 -19
  465. agno/tools/webtools.py +45 -0
  466. agno/tools/whatsapp.py +286 -0
  467. agno/tools/wikipedia.py +28 -19
  468. agno/tools/workflow.py +285 -0
  469. agno/tools/{twitter.py → x.py} +142 -46
  470. agno/tools/yfinance.py +41 -39
  471. agno/tools/youtube.py +34 -17
  472. agno/tools/zendesk.py +15 -5
  473. agno/tools/zep.py +454 -0
  474. agno/tools/zoom.py +86 -37
  475. agno/tracing/__init__.py +12 -0
  476. agno/tracing/exporter.py +157 -0
  477. agno/tracing/schemas.py +276 -0
  478. agno/tracing/setup.py +111 -0
  479. agno/utils/agent.py +938 -0
  480. agno/utils/audio.py +37 -1
  481. agno/utils/certs.py +27 -0
  482. agno/utils/code_execution.py +11 -0
  483. agno/utils/common.py +103 -20
  484. agno/utils/cryptography.py +22 -0
  485. agno/utils/dttm.py +33 -0
  486. agno/utils/events.py +700 -0
  487. agno/utils/functions.py +107 -37
  488. agno/utils/gemini.py +426 -0
  489. agno/utils/hooks.py +171 -0
  490. agno/utils/http.py +185 -0
  491. agno/utils/json_schema.py +159 -37
  492. agno/utils/knowledge.py +36 -0
  493. agno/utils/location.py +19 -0
  494. agno/utils/log.py +221 -8
  495. agno/utils/mcp.py +214 -0
  496. agno/utils/media.py +335 -14
  497. agno/utils/merge_dict.py +22 -1
  498. agno/utils/message.py +77 -2
  499. agno/utils/models/ai_foundry.py +50 -0
  500. agno/utils/models/claude.py +373 -0
  501. agno/utils/models/cohere.py +94 -0
  502. agno/utils/models/llama.py +85 -0
  503. agno/utils/models/mistral.py +100 -0
  504. agno/utils/models/openai_responses.py +140 -0
  505. agno/utils/models/schema_utils.py +153 -0
  506. agno/utils/models/watsonx.py +41 -0
  507. agno/utils/openai.py +257 -0
  508. agno/utils/pickle.py +1 -1
  509. agno/utils/pprint.py +124 -8
  510. agno/utils/print_response/agent.py +930 -0
  511. agno/utils/print_response/team.py +1914 -0
  512. agno/utils/print_response/workflow.py +1668 -0
  513. agno/utils/prompts.py +111 -0
  514. agno/utils/reasoning.py +108 -0
  515. agno/utils/response.py +163 -0
  516. agno/utils/serialize.py +32 -0
  517. agno/utils/shell.py +4 -4
  518. agno/utils/streamlit.py +487 -0
  519. agno/utils/string.py +204 -51
  520. agno/utils/team.py +139 -0
  521. agno/utils/timer.py +9 -2
  522. agno/utils/tokens.py +657 -0
  523. agno/utils/tools.py +19 -1
  524. agno/utils/whatsapp.py +305 -0
  525. agno/utils/yaml_io.py +3 -3
  526. agno/vectordb/__init__.py +2 -0
  527. agno/vectordb/base.py +87 -9
  528. agno/vectordb/cassandra/__init__.py +5 -1
  529. agno/vectordb/cassandra/cassandra.py +383 -27
  530. agno/vectordb/chroma/__init__.py +4 -0
  531. agno/vectordb/chroma/chromadb.py +748 -83
  532. agno/vectordb/clickhouse/__init__.py +7 -1
  533. agno/vectordb/clickhouse/clickhousedb.py +554 -53
  534. agno/vectordb/couchbase/__init__.py +3 -0
  535. agno/vectordb/couchbase/couchbase.py +1446 -0
  536. agno/vectordb/lancedb/__init__.py +5 -0
  537. agno/vectordb/lancedb/lance_db.py +730 -98
  538. agno/vectordb/langchaindb/__init__.py +5 -0
  539. agno/vectordb/langchaindb/langchaindb.py +163 -0
  540. agno/vectordb/lightrag/__init__.py +5 -0
  541. agno/vectordb/lightrag/lightrag.py +388 -0
  542. agno/vectordb/llamaindex/__init__.py +3 -0
  543. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  544. agno/vectordb/milvus/__init__.py +3 -0
  545. agno/vectordb/milvus/milvus.py +966 -78
  546. agno/vectordb/mongodb/__init__.py +9 -1
  547. agno/vectordb/mongodb/mongodb.py +1175 -172
  548. agno/vectordb/pgvector/__init__.py +8 -0
  549. agno/vectordb/pgvector/pgvector.py +599 -115
  550. agno/vectordb/pineconedb/__init__.py +5 -1
  551. agno/vectordb/pineconedb/pineconedb.py +406 -43
  552. agno/vectordb/qdrant/__init__.py +4 -0
  553. agno/vectordb/qdrant/qdrant.py +914 -61
  554. agno/vectordb/redis/__init__.py +9 -0
  555. agno/vectordb/redis/redisdb.py +682 -0
  556. agno/vectordb/singlestore/__init__.py +8 -1
  557. agno/vectordb/singlestore/singlestore.py +771 -0
  558. agno/vectordb/surrealdb/__init__.py +3 -0
  559. agno/vectordb/surrealdb/surrealdb.py +663 -0
  560. agno/vectordb/upstashdb/__init__.py +5 -0
  561. agno/vectordb/upstashdb/upstashdb.py +718 -0
  562. agno/vectordb/weaviate/__init__.py +8 -0
  563. agno/vectordb/weaviate/index.py +15 -0
  564. agno/vectordb/weaviate/weaviate.py +1009 -0
  565. agno/workflow/__init__.py +23 -1
  566. agno/workflow/agent.py +299 -0
  567. agno/workflow/condition.py +759 -0
  568. agno/workflow/loop.py +756 -0
  569. agno/workflow/parallel.py +853 -0
  570. agno/workflow/router.py +723 -0
  571. agno/workflow/step.py +1564 -0
  572. agno/workflow/steps.py +613 -0
  573. agno/workflow/types.py +556 -0
  574. agno/workflow/workflow.py +4327 -514
  575. agno-2.3.13.dist-info/METADATA +639 -0
  576. agno-2.3.13.dist-info/RECORD +613 -0
  577. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +1 -1
  578. agno-2.3.13.dist-info/licenses/LICENSE +201 -0
  579. agno/api/playground.py +0 -91
  580. agno/api/schemas/playground.py +0 -22
  581. agno/api/schemas/user.py +0 -22
  582. agno/api/schemas/workspace.py +0 -46
  583. agno/api/user.py +0 -160
  584. agno/api/workspace.py +0 -151
  585. agno/cli/auth_server.py +0 -118
  586. agno/cli/config.py +0 -275
  587. agno/cli/console.py +0 -88
  588. agno/cli/credentials.py +0 -23
  589. agno/cli/entrypoint.py +0 -571
  590. agno/cli/operator.py +0 -355
  591. agno/cli/settings.py +0 -85
  592. agno/cli/ws/ws_cli.py +0 -817
  593. agno/constants.py +0 -13
  594. agno/document/__init__.py +0 -1
  595. agno/document/chunking/semantic.py +0 -47
  596. agno/document/chunking/strategy.py +0 -31
  597. agno/document/reader/__init__.py +0 -1
  598. agno/document/reader/arxiv_reader.py +0 -41
  599. agno/document/reader/base.py +0 -22
  600. agno/document/reader/csv_reader.py +0 -84
  601. agno/document/reader/docx_reader.py +0 -46
  602. agno/document/reader/firecrawl_reader.py +0 -99
  603. agno/document/reader/json_reader.py +0 -43
  604. agno/document/reader/pdf_reader.py +0 -219
  605. agno/document/reader/s3/pdf_reader.py +0 -46
  606. agno/document/reader/s3/text_reader.py +0 -51
  607. agno/document/reader/text_reader.py +0 -41
  608. agno/document/reader/website_reader.py +0 -175
  609. agno/document/reader/youtube_reader.py +0 -50
  610. agno/embedder/__init__.py +0 -1
  611. agno/embedder/azure_openai.py +0 -86
  612. agno/embedder/cohere.py +0 -72
  613. agno/embedder/fastembed.py +0 -37
  614. agno/embedder/google.py +0 -73
  615. agno/embedder/huggingface.py +0 -54
  616. agno/embedder/mistral.py +0 -80
  617. agno/embedder/ollama.py +0 -57
  618. agno/embedder/openai.py +0 -74
  619. agno/embedder/sentence_transformer.py +0 -38
  620. agno/embedder/voyageai.py +0 -64
  621. agno/eval/perf.py +0 -201
  622. agno/file/__init__.py +0 -1
  623. agno/file/file.py +0 -16
  624. agno/file/local/csv.py +0 -32
  625. agno/file/local/txt.py +0 -19
  626. agno/infra/app.py +0 -240
  627. agno/infra/base.py +0 -144
  628. agno/infra/context.py +0 -20
  629. agno/infra/db_app.py +0 -52
  630. agno/infra/resource.py +0 -205
  631. agno/infra/resources.py +0 -55
  632. agno/knowledge/agent.py +0 -230
  633. agno/knowledge/arxiv.py +0 -22
  634. agno/knowledge/combined.py +0 -22
  635. agno/knowledge/csv.py +0 -28
  636. agno/knowledge/csv_url.py +0 -19
  637. agno/knowledge/document.py +0 -20
  638. agno/knowledge/docx.py +0 -30
  639. agno/knowledge/json.py +0 -28
  640. agno/knowledge/langchain.py +0 -71
  641. agno/knowledge/llamaindex.py +0 -66
  642. agno/knowledge/pdf.py +0 -28
  643. agno/knowledge/pdf_url.py +0 -26
  644. agno/knowledge/s3/base.py +0 -60
  645. agno/knowledge/s3/pdf.py +0 -21
  646. agno/knowledge/s3/text.py +0 -23
  647. agno/knowledge/text.py +0 -30
  648. agno/knowledge/website.py +0 -88
  649. agno/knowledge/wikipedia.py +0 -31
  650. agno/knowledge/youtube.py +0 -22
  651. agno/memory/agent.py +0 -392
  652. agno/memory/classifier.py +0 -104
  653. agno/memory/db/__init__.py +0 -1
  654. agno/memory/db/base.py +0 -42
  655. agno/memory/db/mongodb.py +0 -189
  656. agno/memory/db/postgres.py +0 -203
  657. agno/memory/db/sqlite.py +0 -193
  658. agno/memory/memory.py +0 -15
  659. agno/memory/row.py +0 -36
  660. agno/memory/summarizer.py +0 -192
  661. agno/memory/summary.py +0 -19
  662. agno/memory/workflow.py +0 -38
  663. agno/models/google/gemini_openai.py +0 -26
  664. agno/models/ollama/hermes.py +0 -221
  665. agno/models/ollama/tools.py +0 -362
  666. agno/models/vertexai/gemini.py +0 -595
  667. agno/playground/__init__.py +0 -3
  668. agno/playground/async_router.py +0 -421
  669. agno/playground/deploy.py +0 -249
  670. agno/playground/operator.py +0 -92
  671. agno/playground/playground.py +0 -91
  672. agno/playground/schemas.py +0 -76
  673. agno/playground/serve.py +0 -55
  674. agno/playground/sync_router.py +0 -405
  675. agno/reasoning/agent.py +0 -68
  676. agno/run/response.py +0 -112
  677. agno/storage/agent/__init__.py +0 -0
  678. agno/storage/agent/base.py +0 -38
  679. agno/storage/agent/dynamodb.py +0 -350
  680. agno/storage/agent/json.py +0 -92
  681. agno/storage/agent/mongodb.py +0 -228
  682. agno/storage/agent/postgres.py +0 -367
  683. agno/storage/agent/session.py +0 -79
  684. agno/storage/agent/singlestore.py +0 -303
  685. agno/storage/agent/sqlite.py +0 -357
  686. agno/storage/agent/yaml.py +0 -93
  687. agno/storage/workflow/__init__.py +0 -0
  688. agno/storage/workflow/base.py +0 -40
  689. agno/storage/workflow/mongodb.py +0 -233
  690. agno/storage/workflow/postgres.py +0 -366
  691. agno/storage/workflow/session.py +0 -60
  692. agno/storage/workflow/sqlite.py +0 -359
  693. agno/tools/googlesearch.py +0 -88
  694. agno/utils/defaults.py +0 -57
  695. agno/utils/filesystem.py +0 -39
  696. agno/utils/git.py +0 -52
  697. agno/utils/json_io.py +0 -30
  698. agno/utils/load_env.py +0 -19
  699. agno/utils/py_io.py +0 -19
  700. agno/utils/pyproject.py +0 -18
  701. agno/utils/resource_filter.py +0 -31
  702. agno/vectordb/singlestore/s2vectordb.py +0 -390
  703. agno/vectordb/singlestore/s2vectordb2.py +0 -355
  704. agno/workspace/__init__.py +0 -0
  705. agno/workspace/config.py +0 -325
  706. agno/workspace/enums.py +0 -6
  707. agno/workspace/helpers.py +0 -48
  708. agno/workspace/operator.py +0 -758
  709. agno/workspace/settings.py +0 -63
  710. agno-0.1.2.dist-info/LICENSE +0 -375
  711. agno-0.1.2.dist-info/METADATA +0 -502
  712. agno-0.1.2.dist-info/RECORD +0 -352
  713. agno-0.1.2.dist-info/entry_points.txt +0 -3
  714. /agno/{cli → db/migrations}/__init__.py +0 -0
  715. /agno/{cli/ws → db/migrations/versions}/__init__.py +0 -0
  716. /agno/{document/chunking/__init__.py → db/schemas/metrics.py} +0 -0
  717. /agno/{document/reader/s3 → integrations}/__init__.py +0 -0
  718. /agno/{file/local → knowledge/chunking}/__init__.py +0 -0
  719. /agno/{infra → knowledge/remote_content}/__init__.py +0 -0
  720. /agno/{knowledge/s3 → tools/models}/__init__.py +0 -0
  721. /agno/{reranker → utils/models}/__init__.py +0 -0
  722. /agno/{storage → utils/print_response}/__init__.py +0 -0
  723. {agno-0.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/tools/function.py CHANGED
@@ -1,10 +1,16 @@
1
- from typing import Any, Callable, Dict, Optional, Type, TypeVar, get_type_hints
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
2
5
 
3
6
  from docstring_parser import parse
7
+ from packaging.version import Version
4
8
  from pydantic import BaseModel, Field, validate_call
5
9
 
6
10
  from agno.exceptions import AgentRunException
7
- from agno.utils.log import logger
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
8
14
 
9
15
  T = TypeVar("T")
10
16
 
@@ -12,22 +18,50 @@ T = TypeVar("T")
12
18
  def get_entrypoint_docstring(entrypoint: Callable) -> str:
13
19
  from inspect import getdoc
14
20
 
15
- doc = getdoc(entrypoint)
16
- if not doc:
21
+ if isinstance(entrypoint, partial):
22
+ return str(entrypoint)
23
+
24
+ docstring = getdoc(entrypoint)
25
+ if not docstring:
17
26
  return ""
18
27
 
19
- parsed = parse(doc)
28
+ parsed_doc = parse(docstring)
20
29
 
21
30
  # Combine short and long descriptions
22
31
  lines = []
23
- if parsed.short_description:
24
- lines.append(parsed.short_description)
25
- if parsed.long_description:
26
- lines.extend(parsed.long_description.split("\n"))
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"))
27
36
 
28
37
  return "\n".join(lines)
29
38
 
30
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
+
31
65
  class Function(BaseModel):
32
66
  """Model for storing functions that can be called by an agent."""
33
67
 
@@ -44,10 +78,14 @@ class Function(BaseModel):
44
78
  )
45
79
  strict: Optional[bool] = None
46
80
 
81
+ instructions: Optional[str] = None
82
+ # If True, add instructions to the Agent's system message
83
+ add_instructions: bool = True
84
+
47
85
  # The function to be called.
48
86
  entrypoint: Optional[Callable] = None
49
- # If True, the arguments are sanitized before being passed to the function.
50
- sanitize_arguments: bool = True
87
+ # If True, the entrypoint processing is skipped and the Function is used as is.
88
+ skip_entrypoint_processing: bool = False
51
89
  # If True, the function call will show the result along with sending it to the model.
52
90
  show_result: bool = False
53
91
  # If True, the agent will stop after the function call.
@@ -59,37 +97,148 @@ class Function(BaseModel):
59
97
  # If defined, can accept the FunctionCall instance as a parameter.
60
98
  post_hook: Optional[Callable] = None
61
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
+
62
121
  # --*-- FOR INTERNAL USE ONLY --*--
63
122
  # The agent that the function is associated with
64
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
65
138
 
66
139
  def to_dict(self) -> Dict[str, Any]:
67
- return self.model_dump(exclude_none=True, include={"name", "description", "parameters", "strict"})
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)
68
184
 
69
185
  @classmethod
70
- def from_callable(cls, c: Callable, strict: bool = False) -> "Function":
186
+ def from_callable(cls, c: Callable, name: Optional[str] = None, strict: bool = False) -> "Function":
71
187
  from inspect import getdoc, signature
72
188
 
73
189
  from agno.utils.json_schema import get_json_schema
74
190
 
75
- function_name = c.__name__
191
+ function_name = name or c.__name__
76
192
  parameters = {"type": "object", "properties": {}, "required": []}
77
193
  try:
78
194
  sig = signature(c)
79
195
  type_hints = get_type_hints(c)
80
196
 
81
197
  # If function has an the agent argument, remove the agent parameter from the type hints
82
- if "agent" in sig.parameters:
198
+ if "agent" in sig.parameters and "agent" in type_hints:
83
199
  del type_hints["agent"]
84
- # logger.info(f"Type hints for {function_name}: {type_hints}")
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}")
85
219
 
86
220
  # Filter out return type and only process parameters
87
221
  param_type_hints = {
88
- name: type_hints.get(name) for name in sig.parameters if name != "return" and name != "agent"
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
+ ]
89
238
  }
90
239
 
91
240
  # Parse docstring for parameters
92
- param_descriptions = {}
241
+ param_descriptions: Dict[str, Any] = {}
93
242
  if docstring := getdoc(c):
94
243
  parsed_doc = parse(docstring)
95
244
  param_docs = parsed_doc.params
@@ -98,8 +247,10 @@ class Function(BaseModel):
98
247
  for param in param_docs:
99
248
  param_name = param.arg_name
100
249
  param_type = param.type_name
101
-
102
- param_descriptions[param_name] = f"({param_type}) {param.description}"
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}"
103
254
 
104
255
  # Get JSON schema for parameters only
105
256
  parameters = get_json_schema(
@@ -109,24 +260,55 @@ class Function(BaseModel):
109
260
  # If strict=True mark all fields as required
110
261
  # See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
111
262
  if strict:
112
- parameters["required"] = [name for name in parameters["properties"] if name != "agent"]
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
+ ]
113
280
  else:
114
- # Mark a field as required if it has no default value
281
+ # Mark a field as required if it has no default value (this would include optional fields)
115
282
  parameters["required"] = [
116
283
  name
117
284
  for name, param in sig.parameters.items()
118
- if param.default == param.empty and name != "self" and name != "agent"
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
+ ]
119
299
  ]
120
300
 
121
- # logger.debug(f"JSON schema for {function_name}: {parameters}")
301
+ # log_debug(f"JSON schema for {function_name}: {parameters}")
122
302
  except Exception as e:
123
- logger.warning(f"Could not parse args for {function_name}: {e}", exc_info=True)
303
+ log_warning(f"Could not parse args for {function_name}: {e}", exc_info=True)
304
+
305
+ entrypoint = cls._wrap_callable(c)
124
306
 
125
307
  return cls(
126
308
  name=function_name,
127
309
  description=get_entrypoint_docstring(entrypoint=c),
128
310
  parameters=parameters,
129
- entrypoint=validate_call(c, config=dict(arbitrary_types_allowed=True)), # type: ignore
311
+ entrypoint=entrypoint,
130
312
  )
131
313
 
132
314
  def process_entrypoint(self, strict: bool = False):
@@ -135,6 +317,11 @@ class Function(BaseModel):
135
317
 
136
318
  from agno.utils.json_schema import get_json_schema
137
319
 
320
+ if self.skip_entrypoint_processing:
321
+ if strict:
322
+ self.process_schema_for_strict()
323
+ return
324
+
138
325
  if self.entrypoint is None:
139
326
  return
140
327
 
@@ -145,22 +332,60 @@ class Function(BaseModel):
145
332
  if self.parameters != parameters:
146
333
  params_set_by_user = True
147
334
 
335
+ if self.requires_user_input:
336
+ self.user_input_schema = self.user_input_schema or []
337
+
148
338
  try:
149
339
  sig = signature(self.entrypoint)
150
340
  type_hints = get_type_hints(self.entrypoint)
151
341
 
152
342
  # If function has an the agent argument, remove the agent parameter from the type hints
153
- if "agent" in sig.parameters:
343
+ if "agent" in sig.parameters and "agent" in type_hints:
154
344
  del type_hints["agent"]
155
- # logger.info(f"Type hints for {self.name}: {type_hints}")
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}")
156
362
 
157
363
  # Filter out return type and only process parameters
158
- param_type_hints = {
159
- name: type_hints.get(name) for name in sig.parameters if name != "return" and name != "agent"
160
- }
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}
161
385
 
162
386
  # Parse docstring for parameters
163
387
  param_descriptions = {}
388
+ param_descriptions_clean = {}
164
389
  if docstring := getdoc(self.entrypoint):
165
390
  parsed_doc = parse(docstring)
166
391
  param_docs = parsed_doc.params
@@ -173,6 +398,18 @@ class Function(BaseModel):
173
398
  # TODO: We should use type hints first, then map param types in docs to json schema types.
174
399
  # This is temporary to not lose information
175
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
+ ]
176
413
 
177
414
  # Get JSON schema for parameters only
178
415
  parameters = get_json_schema(
@@ -182,60 +419,229 @@ class Function(BaseModel):
182
419
  # If strict=True mark all fields as required
183
420
  # See: https://platform.openai.com/docs/guides/structured-outputs/supported-schemas#all-fields-must-be-required
184
421
  if strict:
185
- parameters["required"] = [name for name in parameters["properties"] if name != "agent"]
422
+ parameters["required"] = [name for name in parameters["properties"] if name not in excluded_params]
186
423
  else:
187
424
  # Mark a field as required if it has no default value
188
425
  parameters["required"] = [
189
426
  name
190
427
  for name, param in sig.parameters.items()
191
- if param.default == param.empty and name != "self" and name != "agent"
428
+ if param.default == param.empty and name != "self" and name not in excluded_params
192
429
  ]
193
430
 
194
- # logger.debug(f"JSON schema for {self.name}: {parameters}")
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}")
195
448
  except Exception as e:
196
- logger.warning(f"Could not parse args for {self.name}: {e}", exc_info=True)
449
+ log_warning(f"Could not parse args for {self.name}: {e}", exc_info=True)
197
450
 
198
- self.description = self.description or get_entrypoint_docstring(self.entrypoint)
199
451
  if not params_set_by_user:
200
452
  self.parameters = parameters
201
- self.entrypoint = validate_call(self.entrypoint, config=dict(arbitrary_types_allowed=True)) # type: ignore
202
453
 
203
- def get_type_name(self, t: Type[T]):
204
- name = str(t)
205
- if "list" in name or "dict" in name:
206
- return name
207
- else:
208
- return t.__name__
454
+ if strict:
455
+ self.process_schema_for_strict()
209
456
 
210
- def get_definition_for_prompt_dict(self) -> Optional[Dict[str, Any]]:
211
- """Returns a function definition that can be used in a prompt."""
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}")
212
461
 
213
- if self.entrypoint is None:
214
- return None
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
215
466
 
216
- type_hints = get_type_hints(self.entrypoint)
217
- return_type = type_hints.get("return", None)
218
- returns = None
219
- if return_type is not None:
220
- returns = self.get_type_name(return_type)
467
+ pydantic_version = Version(version("pydantic"))
221
468
 
222
- function_info = {
223
- "name": self.name,
224
- "description": self.description,
225
- "arguments": self.parameters.get("properties", {}),
226
- "returns": returns,
227
- }
228
- return function_info
469
+ # Don't wrap async generators validate_call
470
+ if isasyncgenfunction(func):
471
+ return func
229
472
 
230
- def get_definition_for_prompt(self) -> Optional[str]:
231
- """Returns a function definition that can be used in a prompt."""
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."""
232
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}")
233
617
 
234
- function_info = self.get_definition_for_prompt_dict()
235
- if function_info is not None:
236
- return json.dumps(function_info, indent=2)
237
618
  return None
238
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
+
239
645
 
240
646
  class FunctionCall(BaseModel):
241
647
  """Model for Function Calls"""
@@ -254,112 +660,544 @@ class FunctionCall(BaseModel):
254
660
 
255
661
  def get_call_str(self) -> str:
256
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
+
257
669
  if self.arguments is None:
258
670
  return f"{self.function.name}()"
259
671
 
260
672
  trimmed_arguments = {}
261
673
  for k, v in self.arguments.items():
262
- if isinstance(v, str) and len(v) > 100:
674
+ if isinstance(v, str) and len(str(v)) > max_arg_len:
263
675
  trimmed_arguments[k] = "..."
264
676
  else:
265
677
  trimmed_arguments[k] = v
266
- call_str = f"{self.function.name}({', '.join([f'{k}={v}' for k, v in trimmed_arguments.items()])})"
267
- return call_str
268
-
269
- def execute(self) -> bool:
270
- """Runs the function call.
271
678
 
272
- Returns True if the function call was successful, False otherwise.
273
- The result of the function call is stored in self.result.
274
- """
275
- from inspect import signature
679
+ call_str = f"{self.function.name}({', '.join([f'{k}={v}' for k, v in trimmed_arguments.items()])})"
276
680
 
277
- if self.function.entrypoint is None:
278
- return False
681
+ # If call string is too long, truncate arguments
682
+ if len(call_str) > term_width:
683
+ return f"{self.function.name}(...)"
279
684
 
280
- logger.debug(f"Running: {self.get_call_str()}")
281
- function_call_success = False
685
+ return call_str
282
686
 
283
- # Execute pre-hook if it exists
687
+ def _handle_pre_hook(self):
688
+ """Handles the pre-hook for the function call."""
284
689
  if self.function.pre_hook is not None:
285
690
  try:
691
+ from inspect import signature
692
+
286
693
  pre_hook_args = {}
287
694
  # Check if the pre-hook has and agent argument
288
695
  if "agent" in signature(self.function.pre_hook).parameters:
289
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
290
709
  # Check if the pre-hook has an fc argument
291
710
  if "fc" in signature(self.function.pre_hook).parameters:
292
711
  pre_hook_args["fc"] = self
293
712
  self.function.pre_hook(**pre_hook_args)
294
713
  except AgentRunException as e:
295
- logger.debug(f"{e.__class__.__name__}: {e}")
714
+ log_debug(f"{e.__class__.__name__}: {e}")
296
715
  self.error = str(e)
297
716
  raise
298
717
  except Exception as e:
299
- logger.warning(f"Error in pre-hook callback: {e}")
300
- logger.exception(e)
718
+ log_warning(f"Error in pre-hook callback: {e}")
719
+ log_exception(e)
301
720
 
302
- # Call the function with no arguments if none are provided.
303
- if self.arguments is None:
721
+ def _handle_post_hook(self):
722
+ """Handles the post-hook for the function call."""
723
+ if self.function.post_hook is not None:
304
724
  try:
305
- entrypoint_args = {}
306
- # Check if the entrypoint has and agent argument
307
- if "agent" in signature(self.function.entrypoint).parameters:
308
- entrypoint_args["agent"] = self.function._agent
309
- # Check if the entrypoint has an fc argument
310
- if "fc" in signature(self.function.entrypoint).parameters:
311
- entrypoint_args["fc"] = self
312
-
313
- self.result = self.function.entrypoint(**entrypoint_args)
314
- function_call_success = True
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)
315
747
  except AgentRunException as e:
316
- logger.debug(f"{e.__class__.__name__}: {e}")
748
+ log_debug(f"{e.__class__.__name__}: {e}")
317
749
  self.error = str(e)
318
750
  raise
319
751
  except Exception as e:
320
- logger.warning(f"Could not run function {self.get_call_str()}")
321
- logger.exception(e)
322
- self.error = str(e)
323
- return function_call_success
324
- else:
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: FunctionExecutionResult
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
+ # Handle generator case
912
+ if isgenerator(result):
913
+ self.result = result # Store generator directly, can't cache
914
+ # For generators, don't capture updated_session_state yet -
915
+ # session_state is passed by reference, so mutations made during
916
+ # generator iteration are already reflected in the original dict.
917
+ # Returning None prevents stale state from being merged later.
918
+ execution_result = FunctionExecutionResult(
919
+ status="success", result=self.result, updated_session_state=None
920
+ )
921
+ else:
922
+ self.result = result
923
+ # Only cache non-generator results
924
+ if self.function.cache_results:
925
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
926
+ cache_file = self.function._get_cache_file_path(cache_key)
927
+ self.function._save_to_cache(cache_file, self.result)
928
+
929
+ updated_session_state = None
930
+ if entrypoint_args.get("run_context") is not None:
931
+ run_context = entrypoint_args.get("run_context")
932
+ updated_session_state = (
933
+ run_context.session_state
934
+ if run_context is not None and run_context.session_state is not None
935
+ else None
936
+ )
937
+ else:
938
+ if self.function._session_state is not None:
939
+ updated_session_state = self.function._session_state
940
+
941
+ execution_result = FunctionExecutionResult(
942
+ status="success", result=self.result, updated_session_state=updated_session_state
943
+ )
944
+
945
+ except AgentRunException as e:
946
+ log_debug(f"{e.__class__.__name__}: {e}")
947
+ self.error = str(e)
948
+ exception_to_raise = e
949
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
950
+ except Exception as e:
951
+ log_warning(f"Could not run function {self.get_call_str()}")
952
+ log_exception(e)
953
+ self.error = str(e)
954
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
955
+
956
+ finally:
957
+ self._handle_post_hook()
958
+
959
+ if exception_to_raise is not None:
960
+ raise exception_to_raise
961
+
962
+ return execution_result
963
+
964
+ async def _handle_pre_hook_async(self):
965
+ """Handles the async pre-hook for the function call."""
966
+ if self.function.pre_hook is not None:
325
967
  try:
326
- entrypoint_args = {}
327
- # Check if the entrypoint has and agent argument
328
- if "agent" in signature(self.function.entrypoint).parameters:
329
- entrypoint_args["agent"] = self.function._agent
330
- # Check if the entrypoint has an fc argument
331
- if "fc" in signature(self.function.entrypoint).parameters:
332
- entrypoint_args["fc"] = self
333
-
334
- self.result = self.function.entrypoint(**entrypoint_args, **self.arguments)
335
- function_call_success = True
968
+ from inspect import signature
969
+
970
+ pre_hook_args = {}
971
+ # Check if the pre-hook has an agent argument
972
+ if "agent" in signature(self.function.pre_hook).parameters:
973
+ pre_hook_args["agent"] = self.function._agent
974
+ # Check if the pre-hook has an team argument
975
+ if "team" in signature(self.function.pre_hook).parameters:
976
+ pre_hook_args["team"] = self.function._team
977
+ # Check if the pre-hook has an run_context argument
978
+ if "run_context" in signature(self.function.pre_hook).parameters:
979
+ pre_hook_args["run_context"] = self.function._run_context
980
+ # Check if the pre-hook has an session_state argument
981
+ if "session_state" in signature(self.function.pre_hook).parameters:
982
+ pre_hook_args["session_state"] = self.function._session_state
983
+ # Check if the pre-hook has an dependencies argument
984
+ if "dependencies" in signature(self.function.pre_hook).parameters:
985
+ pre_hook_args["dependencies"] = self.function._dependencies
986
+ # Check if the pre-hook has an fc argument
987
+ if "fc" in signature(self.function.pre_hook).parameters:
988
+ pre_hook_args["fc"] = self
989
+
990
+ await self.function.pre_hook(**pre_hook_args)
336
991
  except AgentRunException as e:
337
- logger.debug(f"{e.__class__.__name__}: {e}")
992
+ log_debug(f"{e.__class__.__name__}: {e}")
338
993
  self.error = str(e)
339
994
  raise
340
995
  except Exception as e:
341
- logger.warning(f"Could not run function {self.get_call_str()}")
342
- logger.exception(e)
343
- self.error = str(e)
344
- return function_call_success
996
+ log_warning(f"Error in pre-hook callback: {e}")
997
+ log_exception(e)
345
998
 
346
- # Execute post-hook if it exists
999
+ async def _handle_post_hook_async(self):
1000
+ """Handles the async post-hook for the function call."""
347
1001
  if self.function.post_hook is not None:
348
1002
  try:
1003
+ from inspect import signature
1004
+
349
1005
  post_hook_args = {}
350
- # Check if the post-hook has and agent argument
1006
+ # Check if the post-hook has an agent argument
351
1007
  if "agent" in signature(self.function.post_hook).parameters:
352
1008
  post_hook_args["agent"] = self.function._agent
1009
+ # Check if the post-hook has an team argument
1010
+ if "team" in signature(self.function.post_hook).parameters:
1011
+ post_hook_args["team"] = self.function._team
1012
+ # Check if the post-hook has an run_context argument
1013
+ if "run_context" in signature(self.function.post_hook).parameters:
1014
+ post_hook_args["run_context"] = self.function._run_context
1015
+ # Check if the post-hook has an session_state argument
1016
+ if "session_state" in signature(self.function.post_hook).parameters:
1017
+ post_hook_args["session_state"] = self.function._session_state
1018
+ # Check if the post-hook has an dependencies argument
1019
+ if "dependencies" in signature(self.function.post_hook).parameters:
1020
+ post_hook_args["dependencies"] = self.function._dependencies
1021
+
353
1022
  # Check if the post-hook has an fc argument
354
1023
  if "fc" in signature(self.function.post_hook).parameters:
355
1024
  post_hook_args["fc"] = self
356
- self.function.post_hook(**post_hook_args)
1025
+
1026
+ await self.function.post_hook(**post_hook_args)
357
1027
  except AgentRunException as e:
358
- logger.debug(f"{e.__class__.__name__}: {e}")
1028
+ log_debug(f"{e.__class__.__name__}: {e}")
359
1029
  self.error = str(e)
360
1030
  raise
361
1031
  except Exception as e:
362
- logger.warning(f"Error in post-hook callback: {e}")
363
- logger.exception(e)
1032
+ log_warning(f"Error in post-hook callback: {e}")
1033
+ log_exception(e)
1034
+
1035
+ async def _build_nested_execution_chain_async(self, entrypoint_args: Dict[str, Any]):
1036
+ """Build a nested chain of async hook executions with the entrypoint at the center.
1037
+
1038
+ Similar to _build_nested_execution_chain but for async execution.
1039
+ """
1040
+ from functools import reduce
1041
+ from inspect import isasyncgenfunction, iscoroutinefunction
1042
+
1043
+ async def execute_entrypoint_async(name, func, args):
1044
+ """Execute the entrypoint function asynchronously."""
1045
+ arguments = entrypoint_args.copy()
1046
+ if self.arguments is not None:
1047
+ arguments.update(self.arguments)
1048
+
1049
+ result = self.function.entrypoint(**arguments) # type: ignore
1050
+ if iscoroutinefunction(self.function.entrypoint) and not isasyncgenfunction(self.function.entrypoint):
1051
+ result = await result
1052
+ return result
1053
+
1054
+ def execute_entrypoint(name, func, args):
1055
+ """Execute the entrypoint function synchronously."""
1056
+ arguments = entrypoint_args.copy()
1057
+ if self.arguments is not None:
1058
+ arguments.update(self.arguments)
1059
+ return self.function.entrypoint(**arguments) # type: ignore
1060
+
1061
+ # If no hooks, just return the entrypoint execution function
1062
+ if not self.function.tool_hooks:
1063
+ return execute_entrypoint
1064
+
1065
+ def create_hook_wrapper(inner_func, hook):
1066
+ """Create a nested wrapper for the hook."""
1067
+
1068
+ async def wrapper(name, func, args):
1069
+ """Create a nested wrapper for the hook."""
1070
+
1071
+ # Pass the inner function as next_func to the hook
1072
+ # The hook will call next_func to continue the chain
1073
+ async def next_func(**kwargs):
1074
+ if iscoroutinefunction(inner_func):
1075
+ return await inner_func(name, func, kwargs)
1076
+ else:
1077
+ return inner_func(name, func, kwargs)
1078
+
1079
+ hook_args = self._build_hook_args(hook, name, next_func, args)
1080
+
1081
+ if iscoroutinefunction(hook):
1082
+ return await hook(**hook_args)
1083
+ else:
1084
+ return hook(**hook_args)
1085
+
1086
+ return wrapper
1087
+
1088
+ # Build the chain from inside out - reverse the hooks to start from the innermost
1089
+ hooks = list(reversed(self.function.tool_hooks))
1090
+
1091
+ # Handle async and sync entrypoints
1092
+ if iscoroutinefunction(self.function.entrypoint):
1093
+ chain = reduce(create_hook_wrapper, hooks, execute_entrypoint_async)
1094
+ else:
1095
+ chain = reduce(create_hook_wrapper, hooks, execute_entrypoint)
1096
+ return chain
1097
+
1098
+ async def aexecute(self) -> FunctionExecutionResult:
1099
+ """Runs the function call asynchronously."""
1100
+ from inspect import isasyncgen, isasyncgenfunction, iscoroutinefunction, isgenerator, isgeneratorfunction
1101
+
1102
+ if self.function.entrypoint is None:
1103
+ return FunctionExecutionResult(status="failure", error="Entrypoint is not set")
1104
+
1105
+ log_debug(f"Running: {self.get_call_str()}")
1106
+
1107
+ # Execute pre-hook if it exists
1108
+ if iscoroutinefunction(self.function.pre_hook):
1109
+ await self._handle_pre_hook_async()
1110
+ else:
1111
+ self._handle_pre_hook()
1112
+
1113
+ entrypoint_args = self._build_entrypoint_args()
1114
+
1115
+ # Check cache if enabled and not a generator function
1116
+ if self.function.cache_results and not (
1117
+ isasyncgenfunction(self.function.entrypoint) or isgeneratorfunction(self.function.entrypoint)
1118
+ ):
1119
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
1120
+ cache_file = self.function._get_cache_file_path(cache_key)
1121
+ cached_result = self.function._get_cached_result(cache_file)
1122
+ if cached_result is not None:
1123
+ log_debug(f"Cache hit for: {self.get_call_str()}")
1124
+ self.result = cached_result
1125
+ return FunctionExecutionResult(status="success", result=cached_result)
1126
+
1127
+ # Execute function
1128
+ execution_result: FunctionExecutionResult
1129
+ exception_to_raise = None
1130
+
1131
+ try:
1132
+ # Build and execute the nested chain of hooks
1133
+ if self.function.tool_hooks is not None:
1134
+ execution_chain = await self._build_nested_execution_chain_async(entrypoint_args)
1135
+ self.result = await execution_chain(self.function.name, self.function.entrypoint, self.arguments or {})
1136
+ else:
1137
+ if self.arguments is None or self.arguments == {}:
1138
+ result = self.function.entrypoint(**entrypoint_args)
1139
+ else:
1140
+ result = self.function.entrypoint(**entrypoint_args, **self.arguments)
1141
+
1142
+ if isasyncgenfunction(self.function.entrypoint):
1143
+ self.result = result # Store async generator directly
1144
+ else:
1145
+ self.result = await result
1146
+
1147
+ # Only cache if not a generator
1148
+ if self.function.cache_results and not (isgenerator(self.result) or isasyncgen(self.result)):
1149
+ cache_key = self.function._get_cache_key(entrypoint_args, self.arguments)
1150
+ cache_file = self.function._get_cache_file_path(cache_key)
1151
+ self.function._save_to_cache(cache_file, self.result)
1152
+
1153
+ # For generators, don't capture updated_session_state -
1154
+ # session_state is passed by reference, so mutations made during
1155
+ # generator iteration are already reflected in the original dict.
1156
+ # Returning None prevents stale state from being merged later.
1157
+ if isgenerator(self.result) or isasyncgen(self.result):
1158
+ updated_session_state = None
1159
+ else:
1160
+ updated_session_state = None
1161
+ if entrypoint_args.get("run_context") is not None:
1162
+ run_context = entrypoint_args.get("run_context")
1163
+ updated_session_state = (
1164
+ run_context.session_state
1165
+ if run_context is not None and run_context.session_state is not None
1166
+ else None
1167
+ )
1168
+
1169
+ execution_result = FunctionExecutionResult(
1170
+ status="success", result=self.result, updated_session_state=updated_session_state
1171
+ )
1172
+
1173
+ except AgentRunException as e:
1174
+ log_debug(f"{e.__class__.__name__}: {e}")
1175
+ self.error = str(e)
1176
+ exception_to_raise = e
1177
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
1178
+ except Exception as e:
1179
+ log_warning(f"Could not run function {self.get_call_str()}")
1180
+ log_exception(e)
1181
+ self.error = str(e)
1182
+ execution_result = FunctionExecutionResult(status="failure", error=str(e))
1183
+
1184
+ finally:
1185
+ if iscoroutinefunction(self.function.post_hook):
1186
+ await self._handle_post_hook_async()
1187
+ else:
1188
+ self._handle_post_hook()
1189
+
1190
+ if exception_to_raise is not None:
1191
+ raise exception_to_raise
1192
+
1193
+ return execution_result
1194
+
1195
+
1196
+ class ToolResult(BaseModel):
1197
+ """Result from a tool that can include media artifacts."""
364
1198
 
365
- return function_call_success
1199
+ content: str
1200
+ images: Optional[List[Image]] = None
1201
+ videos: Optional[List[Video]] = None
1202
+ audios: Optional[List[Audio]] = None
1203
+ files: Optional[List[File]] = None