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/utils/tokens.py ADDED
@@ -0,0 +1,657 @@
1
+ import json
2
+ import math
3
+ from functools import lru_cache
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from agno.media import Audio, File, Image, Video
10
+ from agno.models.message import Message
11
+ from agno.tools.function import Function
12
+ from agno.utils.log import log_warning
13
+
14
+ # Default image dimensions used as fallback when actual dimensions cannot be determined.
15
+ # These values provide a more conservative estimate for high-detail image token counting.
16
+ DEFAULT_IMAGE_WIDTH = 1024
17
+ DEFAULT_IMAGE_HEIGHT = 1024
18
+
19
+
20
+ # Different models use different encodings
21
+ @lru_cache(maxsize=16)
22
+ def _get_tiktoken_encoding(model_id: str):
23
+ model_id = model_id.lower()
24
+ try:
25
+ import tiktoken
26
+
27
+ try:
28
+ # Use model-specific encoding
29
+ return tiktoken.encoding_for_model(model_id)
30
+ except KeyError:
31
+ return tiktoken.get_encoding("o200k_base")
32
+ except ImportError:
33
+ log_warning("tiktoken not installed. Please install it using `pip install tiktoken`.")
34
+ return None
35
+
36
+
37
+ @lru_cache(maxsize=16)
38
+ def _get_hf_tokenizer(model_id: str):
39
+ try:
40
+ from tokenizers import Tokenizer
41
+
42
+ model_id = model_id.lower()
43
+
44
+ # Llama-3 models use a different tokenizer than Llama-2
45
+ if "llama-3" in model_id or "llama3" in model_id:
46
+ return Tokenizer.from_pretrained("Xenova/llama-3-tokenizer")
47
+
48
+ # Llama-2 models and Replicate models (LiteLLM uses llama tokenizer for replicate)
49
+ if "llama-2" in model_id or "llama2" in model_id or "replicate" in model_id:
50
+ return Tokenizer.from_pretrained("hf-internal-testing/llama-tokenizer")
51
+
52
+ # Cohere command-r models have their own tokenizer
53
+ if "command-r" in model_id:
54
+ return Tokenizer.from_pretrained("Xenova/c4ai-command-r-v01-tokenizer")
55
+
56
+ return None
57
+ except ImportError:
58
+ log_warning("tokenizers not installed. Please install it using `pip install tokenizers`.")
59
+ return None
60
+ except Exception:
61
+ return None
62
+
63
+
64
+ def _select_tokenizer(model_id: str) -> Tuple[str, Any]:
65
+ # Priority 1: HuggingFace tokenizers for models with specific tokenizers
66
+ hf_tokenizer = _get_hf_tokenizer(model_id)
67
+ if hf_tokenizer is not None:
68
+ return ("huggingface", hf_tokenizer)
69
+
70
+ # Priority 2: tiktoken for OpenAI models
71
+ tiktoken_enc = _get_tiktoken_encoding(model_id)
72
+ if tiktoken_enc is not None:
73
+ return ("tiktoken", tiktoken_enc)
74
+
75
+ # Fallback: No tokenizer available, will use character-based estimation
76
+ return ("none", None)
77
+
78
+
79
+ # =============================================================================
80
+ # Tool Token Counting
81
+ # =============================================================================
82
+ # OpenAI counts tool/function tokens by converting them to a TypeScript-like
83
+ # namespace format. This approach was reverse-engineered and documented from:
84
+ # https://github.com/forestwanglin/openai-java/blob/main/jtokkit/src/main/java/xyz/felh/openai/jtokkit/utils/TikTokenUtils.java
85
+ #
86
+ # The formatted output looks like:
87
+ # namespace functions {
88
+ # // {description}
89
+ # type {name} = (_: {
90
+ # // {param_description}
91
+ # {param_name}{?}: {type},
92
+ # }) => any;
93
+ # } // namespace functions
94
+ # =============================================================================
95
+
96
+
97
+ # OpenAI internally represents function/tool definitions in a TypeScript-like format for tokenization
98
+ def _format_function_definitions(tools: List[Dict[str, Any]]) -> str:
99
+ """
100
+ Formats tool definitions as a TypeScript namespace.
101
+
102
+ Returns:
103
+ A TypeScript namespace string representation of all tools.
104
+
105
+ Example:
106
+ Input tool: {"function": {"name": "get_weather", "parameters": {...}}}
107
+ Output: "namespace functions {\ntype get_weather = (_: {...}) => any;\n}"
108
+ """
109
+ lines = []
110
+ lines.append("namespace functions {")
111
+ lines.append("")
112
+
113
+ for tool in tools:
114
+ # Handle both {"function": {...}} and direct function dict formats
115
+ function = tool.get("function", tool)
116
+ if function_description := function.get("description"):
117
+ lines.append(f"// {function_description}")
118
+
119
+ function_name = function.get("name", "")
120
+ parameters = function.get("parameters", {})
121
+ properties = parameters.get("properties", {})
122
+
123
+ if properties:
124
+ lines.append(f"type {function_name} = (_: {{")
125
+ lines.append(_format_object_parameters(parameters, 0))
126
+ lines.append("}) => any;")
127
+ else:
128
+ # Functions with no parameters
129
+ lines.append(f"type {function_name} = () => any;")
130
+ lines.append("")
131
+
132
+ lines.append("} // namespace functions")
133
+ return "\n".join(lines)
134
+
135
+
136
+ def _format_object_parameters(parameters: Dict[str, Any], indent: int) -> str:
137
+ """
138
+ Format JSON Schema object properties as TypeScript object properties.
139
+
140
+ Args:
141
+ parameters: A JSON Schema object with 'properties' and optional 'required' keys.
142
+ indent: Number of spaces for indentation.
143
+
144
+ Returns:
145
+ TypeScript property definitions, one per line.
146
+
147
+ Example:
148
+ Input: {"properties": {"name": {"type": "string"}}, "required": ["name"]}
149
+ Output: "name: string,"
150
+ """
151
+ properties = parameters.get("properties", {})
152
+ if not properties:
153
+ return ""
154
+
155
+ required_params = parameters.get("required", [])
156
+ lines = []
157
+
158
+ for key, props in properties.items():
159
+ # Add property description as a comment
160
+ description = props.get("description")
161
+ if description:
162
+ lines.append(f"// {description}")
163
+
164
+ # Required params have no "?", optional params have "?"
165
+ question = "" if required_params and key in required_params else "?"
166
+ lines.append(f"{key}{question}: {_format_type(props, indent)},")
167
+
168
+ return "\n".join([" " * max(0, indent) + line for line in lines])
169
+
170
+
171
+ def _format_type(props: Dict[str, Any], indent: int) -> str:
172
+ """
173
+ Convert a JSON Schema type to its TypeScript equivalent.
174
+
175
+ Recursively handles nested types including arrays and objects.
176
+
177
+ Args:
178
+ props: A JSON Schema property definition containing 'type' and optionally
179
+ 'enum', 'items' (for arrays), or 'properties' (for objects).
180
+ indent: The current indentation level for nested object formatting.
181
+
182
+ Returns:
183
+ A TypeScript type string.
184
+
185
+ Example:
186
+ - {"type": "string"} -> "string"
187
+ - {"type": "string", "enum": ["low", "high"]} -> '"low" | "high"'
188
+ - {"type": "array", "items": {"type": "number"}} -> "number[]"
189
+ """
190
+ type_name = props.get("type", "any")
191
+
192
+ if type_name == "string":
193
+ if "enum" in props:
194
+ # Convert enum to TypeScript union of string literals
195
+ return " | ".join([f'"{item}"' for item in props["enum"]])
196
+ return "string"
197
+ elif type_name == "array":
198
+ # Recursively format the array item type
199
+ items = props.get("items", {})
200
+ return f"{_format_type(items, indent)}[]"
201
+ elif type_name == "object":
202
+ # Recursively format nested object properties
203
+ return f"{{\n{_format_object_parameters(props, indent + 2)}\n}}"
204
+ elif type_name in ["integer", "number"]:
205
+ if "enum" in props:
206
+ return " | ".join([f'"{item}"' for item in props["enum"]])
207
+ return "number"
208
+ elif type_name == "boolean":
209
+ return "boolean"
210
+ elif type_name == "null":
211
+ return "null"
212
+ else:
213
+ # Default to "any" for unknown types
214
+ return "any"
215
+
216
+
217
+ # =============================================================================
218
+ # Multi-modal Token Counting
219
+ # =============================================================================
220
+ # Image dimension parsing uses magic byte detection to identify file formats
221
+ # without relying on external libraries. This allows efficient header-only reads.
222
+ # =============================================================================
223
+
224
+
225
+ def _get_image_type(data: bytes) -> Optional[str]:
226
+ """Returns the image format from magic bytes in the file header."""
227
+ if len(data) < 12:
228
+ return None
229
+ # PNG: 8-byte signature
230
+ if data[0:8] == b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a":
231
+ return "png"
232
+ # GIF: "GIF8" followed by "9a" or "7a" (we check for 'a')
233
+ if data[0:4] == b"GIF8" and data[5:6] == b"a":
234
+ return "gif"
235
+ # JPEG: SOI marker (Start of Image)
236
+ if data[0:3] == b"\xff\xd8\xff":
237
+ return "jpeg"
238
+ # HEIC/HEIF: ftyp box at offset 4
239
+ if data[4:8] == b"ftyp":
240
+ return "heic"
241
+ # WebP: RIFF container with WEBP identifier
242
+ if data[0:4] == b"RIFF" and data[8:12] == b"WEBP":
243
+ return "webp"
244
+ return None
245
+
246
+
247
+ def _parse_image_dimensions_from_bytes(data: bytes, img_type: Optional[str] = None) -> Tuple[int, int]:
248
+ """Returns the image dimensions (width, height) from raw image bytes."""
249
+ import io
250
+ import struct
251
+
252
+ if img_type is None:
253
+ img_type = _get_image_type(data)
254
+
255
+ if img_type == "png":
256
+ # PNG IHDR chunk: width at offset 16, height at offset 20 (big-endian)
257
+ return struct.unpack(">LL", data[16:24])
258
+ elif img_type == "gif":
259
+ # GIF logical screen descriptor: width/height at offset 6 (little-endian)
260
+ return struct.unpack("<HH", data[6:10])
261
+ elif img_type == "jpeg":
262
+ # JPEG requires scanning for SOF (Start of Frame) markers
263
+ # SOF markers are 0xC0-0xCF, excluding 0xC4 (DHT), 0xC8 (JPG), 0xCC (DAC)
264
+ with io.BytesIO(data) as f:
265
+ f.seek(0)
266
+ size = 2
267
+ ftype = 0
268
+ while not 0xC0 <= ftype <= 0xCF or ftype in (0xC4, 0xC8, 0xCC):
269
+ f.seek(size, 1)
270
+ byte = f.read(1)
271
+ # Skip any padding 0xFF bytes
272
+ while ord(byte) == 0xFF:
273
+ byte = f.read(1)
274
+ ftype = ord(byte)
275
+ size = struct.unpack(">H", f.read(2))[0] - 2
276
+ f.seek(1, 1) # Skip precision byte
277
+ h, w = struct.unpack(">HH", f.read(4))
278
+ return w, h
279
+ elif img_type == "webp":
280
+ # WebP has three encoding formats with different dimension locations
281
+ if data[12:16] == b"VP8X":
282
+ # Extended format: 24-bit dimensions stored in 3 bytes each
283
+ w = struct.unpack("<I", data[24:27] + b"\x00")[0] + 1
284
+ h = struct.unpack("<I", data[27:30] + b"\x00")[0] + 1
285
+ return w, h
286
+ elif data[12:16] == b"VP8 ":
287
+ # Lossy format: dimensions in first frame header, 14-bit masked
288
+ w = struct.unpack("<H", data[26:28])[0] & 0x3FFF
289
+ h = struct.unpack("<H", data[28:30])[0] & 0x3FFF
290
+ return w, h
291
+ elif data[12:16] == b"VP8L":
292
+ # Lossless format: dimensions bit-packed in 4 bytes
293
+ bits = struct.unpack("<I", data[21:25])[0]
294
+ w = (bits & 0x3FFF) + 1
295
+ h = ((bits >> 14) & 0x3FFF) + 1
296
+ return w, h
297
+
298
+ return DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT
299
+
300
+
301
+ def _get_image_dimensions(image: Image) -> Tuple[int, int]:
302
+ """Returns the image dimensions (width, height) from an Image object."""
303
+ try:
304
+ # Try to get format hint from metadata to skip magic byte detection
305
+ img_format = image.format
306
+ if not img_format and image.mime_type:
307
+ img_format = image.mime_type.split("/")[-1] if "/" in image.mime_type else None
308
+
309
+ # Get raw bytes from the appropriate source
310
+ if image.content:
311
+ data = image.content
312
+ elif image.filepath:
313
+ with open(image.filepath, "rb") as f:
314
+ data = f.read(100) # Only need header bytes for dimension parsing
315
+ elif image.url:
316
+ import httpx
317
+
318
+ response = httpx.get(image.url, timeout=5)
319
+ data = response.content
320
+ else:
321
+ return DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT
322
+
323
+ return _parse_image_dimensions_from_bytes(data, img_format)
324
+ except Exception:
325
+ return DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT
326
+
327
+
328
+ def count_file_tokens(file: File) -> int:
329
+ """Estimate the number of tokens in a file based on its size and type."""
330
+ # Determine file size from available source
331
+ size = 0
332
+ if file.content and isinstance(file.content, (str, bytes)):
333
+ size = len(file.content)
334
+ elif file.filepath:
335
+ try:
336
+ path = Path(file.filepath) if isinstance(file.filepath, str) else file.filepath
337
+ if path.exists():
338
+ size = path.stat().st_size
339
+ except Exception:
340
+ pass
341
+ elif file.url:
342
+ # Use HEAD request to get Content-Length without downloading
343
+ try:
344
+ import urllib.request
345
+
346
+ req = urllib.request.Request(file.url, method="HEAD")
347
+ with urllib.request.urlopen(req, timeout=5) as response:
348
+ content_length = response.headers.get("Content-Length")
349
+ if content_length:
350
+ size = int(content_length)
351
+ except Exception:
352
+ pass
353
+
354
+ if size == 0:
355
+ return 0
356
+
357
+ # Determine file extension for type-based estimation
358
+ ext = None
359
+ if file.format:
360
+ ext = file.format.lower().lstrip(".")
361
+ elif file.filepath:
362
+ path = Path(file.filepath) if isinstance(file.filepath, str) else file.filepath
363
+ ext = path.suffix.lower().lstrip(".") if path.suffix else None
364
+ elif file.url:
365
+ url_path = file.url.split("?")[0]
366
+ if "." in url_path:
367
+ ext = url_path.rsplit(".", 1)[-1].lower()
368
+
369
+ # Text files: ~4 characters per token (based on typical tiktoken ratios)
370
+ if ext in {"txt", "csv", "md", "json", "xml", "html"}:
371
+ return size // 4
372
+ # Binary/other files: ~40 bytes per token (rough estimate)
373
+ return size // 40
374
+
375
+
376
+ def count_tool_tokens(
377
+ tools: Sequence[Union[Function, Dict[str, Any]]],
378
+ model_id: str = "gpt-4o",
379
+ ) -> int:
380
+ """Count tokens consumed by tool/function definitions"""
381
+ if not tools:
382
+ return 0
383
+
384
+ # Convert Function objects to dict format for formatting
385
+ tool_dicts = []
386
+ for tool in tools:
387
+ if isinstance(tool, Function):
388
+ tool_dicts.append(tool.to_dict())
389
+ else:
390
+ tool_dicts.append(tool)
391
+
392
+ # Format tools in TypeScript namespace format and count tokens
393
+ formatted = _format_function_definitions(tool_dicts)
394
+ tokens = count_text_tokens(formatted, model_id)
395
+ return tokens
396
+
397
+
398
+ def count_schema_tokens(
399
+ output_schema: Optional[Union[Dict, Type["BaseModel"]]],
400
+ model_id: str = "gpt-4o",
401
+ ) -> int:
402
+ """Estimate tokens for output_schema/output_schema."""
403
+ if output_schema is None:
404
+ return 0
405
+
406
+ try:
407
+ from pydantic import BaseModel
408
+
409
+ if isinstance(output_schema, type) and issubclass(output_schema, BaseModel):
410
+ # Convert Pydantic model to JSON schema
411
+ schema = output_schema.model_json_schema()
412
+ elif isinstance(output_schema, dict):
413
+ schema = output_schema
414
+ else:
415
+ return 0
416
+
417
+ schema_json = json.dumps(schema)
418
+ return count_text_tokens(schema_json, model_id)
419
+ except Exception:
420
+ return 0
421
+
422
+
423
+ def count_text_tokens(text: str, model_id: str = "gpt-4o") -> int:
424
+ if not text:
425
+ return 0
426
+ tokenizer_type, tokenizer = _select_tokenizer(model_id)
427
+ if tokenizer_type == "huggingface":
428
+ return len(tokenizer.encode(text).ids)
429
+ elif tokenizer_type == "tiktoken":
430
+ # disallowed_special=() allows all special tokens to be encoded
431
+ return len(tokenizer.encode(text, disallowed_special=()))
432
+ else:
433
+ # Fallback: ~4 characters per token (typical for English text)
434
+ return len(text) // 4
435
+
436
+
437
+ # =============================================================================
438
+ # Image Token Counting
439
+ # =============================================================================
440
+ # OpenAI's vision models process images by dividing them into 512x512 tiles.
441
+ # The token count depends on the image dimensions and detail level.
442
+ # OpenAI's image token formula:
443
+ # 1. If max(width, height) > 2000: scale to fit in 2000px on longest side
444
+ # 2. If min(width, height) > 768: scale so shortest side is 768px
445
+ # 3. tiles = ceil(width/512) * ceil(height/512)
446
+ # 4. tokens = 85 + (170 * tiles)
447
+
448
+ # Token constants:
449
+ # - 85: Base tokens for any image (covers metadata, low-detail representation)
450
+ # - 170: Additional tokens per 512x512 tile (high-detail tile encoding)
451
+
452
+ # Detail modes:
453
+ # - "low": Fixed 85 tokens (thumbnail/overview only)
454
+ # - "high"/"auto": Full tile-based calculation
455
+
456
+
457
+ # Example:
458
+ # 1024x1024 image with high detail:
459
+ # - No scaling needed (within limits)
460
+ # - tiles = ceil(1024/512) * ceil(1024/512) = 2 * 2 = 4
461
+ # - tokens = 85 + (170 * 4) = 765
462
+ # =============================================================================
463
+ def count_image_tokens(image: Image) -> int:
464
+ width, height = _get_image_dimensions(image)
465
+ detail = image.detail or "auto"
466
+
467
+ if width <= 0 or height <= 0:
468
+ return 0
469
+
470
+ # Low detail: fixed 85 tokens regardless of dimensions
471
+ if detail == "low":
472
+ return 85
473
+
474
+ # For auto/high detail, calculate based on dimensions
475
+ # Step 1: Scale down if longest side exceeds 2000px
476
+ if max(width, height) > 2000:
477
+ scale = 2000 / max(width, height)
478
+ width, height = int(width * scale), int(height * scale)
479
+
480
+ # Step 2: Scale down if shortest side exceeds 768px
481
+ if min(width, height) > 768:
482
+ scale = 768 / min(width, height)
483
+ width, height = int(width * scale), int(height * scale)
484
+
485
+ # Step 3: Calculate tiles (512x512 each)
486
+ tiles = math.ceil(width / 512) * math.ceil(height / 512)
487
+
488
+ # Step 4: 85 base tokens + 170 tokens per tile
489
+ return 85 + (170 * tiles)
490
+
491
+
492
+ # =============================================================================
493
+ # Audio Token Counting
494
+ # =============================================================================
495
+ # This is an Agno-specific implementation using a conservative estimate of 25 tokens per second of audio.
496
+ # OpenAI's Whisper model actually uses ~50 tokens/second (20ms per token), but this estimate is more conservative for context window planning.
497
+ # Example:
498
+ # 10 seconds of audio: 10 * 25 = 250 tokens
499
+
500
+
501
+ def count_audio_tokens(audio: Audio) -> int:
502
+ """Estimate the number of tokens for an audio clip based on duration."""
503
+ duration = audio.duration or 0
504
+ if duration <= 0:
505
+ return 0
506
+ return int(duration * 25)
507
+
508
+
509
+ # =============================================================================
510
+ # Video Token Counting
511
+ # =============================================================================
512
+ # This is an Agno-specific implementation that treats video as a sequence of
513
+ # images, applying the OpenAI image token formula to each frame.
514
+ # Example:
515
+ # 5 second video at 1 fps with 512x512 resolution:
516
+ # - tiles = 1 (512/512 = 1)
517
+ # - tokens_per_frame = 85 + 170 = 255
518
+ # - num_frames = 5
519
+ # - total = 255 * 5 = 1275 tokens
520
+ # =============================================================================
521
+
522
+
523
+ def count_video_tokens(video: Video) -> int:
524
+ duration = video.duration or 0
525
+ if duration <= 0:
526
+ return 0
527
+
528
+ # Use defaults if dimensions/fps not specified
529
+ width = video.width or 512
530
+ height = video.height or 512
531
+ fps = video.fps or 1.0
532
+
533
+ # Calculate tokens per frame using the same formula as images (high detail)
534
+ w, h = width, height
535
+ # Scale down if longest side exceeds 2000px
536
+ if max(w, h) > 2000:
537
+ scale = 2000 / max(w, h)
538
+ w, h = int(w * scale), int(h * scale)
539
+ # Scale down if shortest side exceeds 768px
540
+ if min(w, h) > 768:
541
+ scale = 768 / min(w, h)
542
+ w, h = int(w * scale), int(h * scale)
543
+ tiles = math.ceil(w / 512) * math.ceil(h / 512)
544
+ tokens_per_frame = 85 + (170 * tiles)
545
+
546
+ # Calculate total tokens for all frames
547
+ num_frames = max(int(duration * fps), 1)
548
+ return num_frames * tokens_per_frame
549
+
550
+
551
+ def _count_media_tokens(message: Message) -> int:
552
+ tokens = 0
553
+
554
+ if message.images:
555
+ for image in message.images:
556
+ tokens += count_image_tokens(image)
557
+
558
+ if message.audio:
559
+ for audio in message.audio:
560
+ tokens += count_audio_tokens(audio)
561
+
562
+ if message.videos:
563
+ for video in message.videos:
564
+ tokens += count_video_tokens(video)
565
+
566
+ if message.files:
567
+ for file in message.files:
568
+ tokens += count_file_tokens(file)
569
+
570
+ return tokens
571
+
572
+
573
+ def _count_message_tokens(message: Message, model_id: str = "gpt-4o") -> int:
574
+ tokens = 0
575
+ text_parts: List[str] = []
576
+
577
+ # Collect content text
578
+ content = message.get_content(use_compressed_content=True)
579
+ if content:
580
+ if isinstance(content, str):
581
+ text_parts.append(content)
582
+ elif isinstance(content, list):
583
+ # Handle multimodal content blocks
584
+ for item in content:
585
+ if isinstance(item, str):
586
+ text_parts.append(item)
587
+ elif isinstance(item, dict):
588
+ item_type = item.get("type", "")
589
+ if item_type == "text":
590
+ text_parts.append(item.get("text", ""))
591
+ elif item_type == "image_url":
592
+ # Handle OpenAI-style content lists without populating message.images
593
+ image_url_data = item.get("image_url", {})
594
+ url = image_url_data.get("url") if isinstance(image_url_data, dict) else None
595
+ detail = image_url_data.get("detail", "auto") if isinstance(image_url_data, dict) else "auto"
596
+
597
+ temp_image = Image(url=url, detail=detail)
598
+ tokens += count_image_tokens(temp_image)
599
+ else:
600
+ text_parts.append(json.dumps(item))
601
+ else:
602
+ text_parts.append(str(content))
603
+
604
+ # Collect tool call arguments
605
+ if message.tool_calls:
606
+ for tool_call in message.tool_calls:
607
+ if isinstance(tool_call, dict) and "function" in tool_call:
608
+ args = tool_call["function"].get("arguments", "")
609
+ text_parts.append(str(args))
610
+
611
+ # Collect tool response id
612
+ if message.tool_call_id:
613
+ text_parts.append(message.tool_call_id)
614
+
615
+ # Collect reasoning content
616
+ if message.reasoning_content:
617
+ text_parts.append(message.reasoning_content)
618
+ if message.redacted_reasoning_content:
619
+ text_parts.append(message.redacted_reasoning_content)
620
+
621
+ # Collect name field
622
+ if message.name:
623
+ text_parts.append(message.name)
624
+
625
+ # Count all text tokens in a single call
626
+ if text_parts:
627
+ tokens += count_text_tokens(" ".join(text_parts), model_id)
628
+
629
+ # Count all media attachments
630
+ tokens += _count_media_tokens(message)
631
+
632
+ return tokens
633
+
634
+
635
+ def count_tokens(
636
+ messages: List[Message],
637
+ tools: Optional[List[Union[Function, Dict[str, Any]]]] = None,
638
+ model_id: str = "gpt-4o",
639
+ output_schema: Optional[Union[Dict, Type["BaseModel"]]] = None,
640
+ ) -> int:
641
+ total = 0
642
+ model_id = model_id.lower()
643
+
644
+ # Count message tokens
645
+ if messages:
646
+ for msg in messages:
647
+ total += _count_message_tokens(msg, model_id)
648
+
649
+ # Add tool tokens
650
+ if tools:
651
+ total += count_tool_tokens(tools, model_id)
652
+
653
+ # Add output_schema/output_schema tokens
654
+ if output_schema is not None:
655
+ total += count_schema_tokens(output_schema, model_id)
656
+
657
+ return total
agno/utils/tools.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import Any, Dict, Optional
2
2
 
3
+ from agno.models.response import ToolExecution
3
4
  from agno.tools.function import Function, FunctionCall
4
5
  from agno.utils.functions import get_function_call
5
6
 
@@ -12,7 +13,7 @@ def get_function_call_for_tool_call(
12
13
  _tool_call_function = tool_call.get("function")
13
14
  if _tool_call_function is not None:
14
15
  _tool_call_function_name = _tool_call_function.get("name")
15
- _tool_call_function_arguments_str = _tool_call_function.get("arguments")
16
+ _tool_call_function_arguments_str = _tool_call_function.get("arguments") or "{}"
16
17
  if _tool_call_function_name is not None:
17
18
  return get_function_call(
18
19
  name=_tool_call_function_name,
@@ -82,3 +83,20 @@ def remove_function_calls_from_string(
82
83
  end_index = text.find(end_tag) + len(end_tag)
83
84
  text = text[:start_index] + text[end_index:]
84
85
  return text
86
+
87
+
88
+ def get_function_call_for_tool_execution(
89
+ tool_execution: ToolExecution,
90
+ functions: Optional[Dict[str, Function]] = None,
91
+ ) -> Optional[FunctionCall]:
92
+ import json
93
+
94
+ _tool_call_id = tool_execution.tool_call_id
95
+ _tool_call_function_name = tool_execution.tool_name or ""
96
+ _tool_call_function_arguments_str = json.dumps(tool_execution.tool_args)
97
+ return get_function_call(
98
+ name=_tool_call_function_name,
99
+ arguments=_tool_call_function_arguments_str,
100
+ call_id=_tool_call_id,
101
+ functions=functions,
102
+ )