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
@@ -0,0 +1,148 @@
1
+ from typing import Optional, Union
2
+
3
+ from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
4
+ from pydantic import BaseModel, Field
5
+
6
+ from agno.agent.agent import Agent
7
+ from agno.os.interfaces.slack.security import verify_slack_signature
8
+ from agno.team.team import Team
9
+ from agno.tools.slack import SlackTools
10
+ from agno.utils.log import log_info
11
+ from agno.workflow.workflow import Workflow
12
+
13
+
14
+ class SlackEventResponse(BaseModel):
15
+ """Response model for Slack event processing"""
16
+
17
+ status: str = Field(default="ok", description="Processing status")
18
+
19
+
20
+ class SlackChallengeResponse(BaseModel):
21
+ """Response model for Slack URL verification challenge"""
22
+
23
+ challenge: str = Field(description="Challenge string to echo back to Slack")
24
+
25
+
26
+ def attach_routes(
27
+ router: APIRouter,
28
+ agent: Optional[Agent] = None,
29
+ team: Optional[Team] = None,
30
+ workflow: Optional[Workflow] = None,
31
+ reply_to_mentions_only: bool = True,
32
+ ) -> APIRouter:
33
+ # Determine entity type for documentation
34
+ entity_type = "agent" if agent else "team" if team else "workflow" if workflow else "unknown"
35
+
36
+ @router.post(
37
+ "/events",
38
+ operation_id=f"slack_events_{entity_type}",
39
+ name="slack_events",
40
+ description="Process incoming Slack events",
41
+ response_model=Union[SlackChallengeResponse, SlackEventResponse],
42
+ response_model_exclude_none=True,
43
+ responses={
44
+ 200: {"description": "Event processed successfully"},
45
+ 400: {"description": "Missing Slack headers"},
46
+ 403: {"description": "Invalid Slack signature"},
47
+ },
48
+ )
49
+ async def slack_events(request: Request, background_tasks: BackgroundTasks):
50
+ body = await request.body()
51
+ timestamp = request.headers.get("X-Slack-Request-Timestamp")
52
+ slack_signature = request.headers.get("X-Slack-Signature", "")
53
+
54
+ if not timestamp or not slack_signature:
55
+ raise HTTPException(status_code=400, detail="Missing Slack headers")
56
+
57
+ if not verify_slack_signature(body, timestamp, slack_signature):
58
+ raise HTTPException(status_code=403, detail="Invalid signature")
59
+
60
+ data = await request.json()
61
+
62
+ # Handle URL verification
63
+ if data.get("type") == "url_verification":
64
+ return SlackChallengeResponse(challenge=data.get("challenge"))
65
+
66
+ # Process other event types (e.g., message events) asynchronously
67
+ if "event" in data:
68
+ event = data["event"]
69
+ if event.get("bot_id"):
70
+ log_info("bot event")
71
+ pass
72
+ else:
73
+ background_tasks.add_task(_process_slack_event, event)
74
+
75
+ return SlackEventResponse(status="ok")
76
+
77
+ async def _process_slack_event(event: dict):
78
+ event_type = event.get("type")
79
+
80
+ # Only handle app_mention and message events
81
+ if event_type not in ("app_mention", "message"):
82
+ return
83
+
84
+ channel_type = event.get("channel_type", "")
85
+
86
+ # Handle duplicate replies
87
+ if not reply_to_mentions_only and event_type == "app_mention":
88
+ return
89
+
90
+ # If reply_to_mentions_only is True, ignore every message that is not a DM
91
+ if reply_to_mentions_only and event_type == "message" and channel_type != "im":
92
+ return
93
+
94
+ # Extract event data
95
+ user = None
96
+ message_text = event.get("text", "")
97
+ channel_id = event.get("channel", "")
98
+ user = event.get("user")
99
+ if event.get("thread_ts"):
100
+ ts = event.get("thread_ts", "")
101
+ else:
102
+ ts = event.get("ts", "")
103
+
104
+ # Use the timestamp as the session id, so that each thread is a separate session
105
+ session_id = ts
106
+
107
+ if agent:
108
+ response = await agent.arun(message_text, user_id=user, session_id=session_id)
109
+ elif team:
110
+ response = await team.arun(message_text, user_id=user, session_id=session_id) # type: ignore
111
+ elif workflow:
112
+ response = await workflow.arun(message_text, user_id=user, session_id=session_id) # type: ignore
113
+
114
+ if response:
115
+ if hasattr(response, "reasoning_content") and response.reasoning_content:
116
+ _send_slack_message(
117
+ channel=channel_id,
118
+ message=f"Reasoning: \n{response.reasoning_content}",
119
+ thread_ts=ts,
120
+ italics=True,
121
+ )
122
+
123
+ _send_slack_message(channel=channel_id, message=response.content or "", thread_ts=ts)
124
+
125
+ def _send_slack_message(channel: str, thread_ts: str, message: str, italics: bool = False):
126
+ if len(message) <= 40000:
127
+ if italics:
128
+ # Handle multi-line messages by making each line italic
129
+ formatted_message = "\n".join([f"_{line}_" for line in message.split("\n")])
130
+ SlackTools().send_message_thread(channel=channel, text=formatted_message or "", thread_ts=thread_ts)
131
+ else:
132
+ SlackTools().send_message_thread(channel=channel, text=message or "", thread_ts=thread_ts)
133
+ return
134
+
135
+ # Split message into batches of 4000 characters (WhatsApp message limit is 4096)
136
+ message_batches = [message[i : i + 40000] for i in range(0, len(message), 40000)]
137
+
138
+ # Add a prefix with the batch number
139
+ for i, batch in enumerate(message_batches, 1):
140
+ batch_message = f"[{i}/{len(message_batches)}] {batch}"
141
+ if italics:
142
+ # Handle multi-line messages by making each line italic
143
+ formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
144
+ SlackTools().send_message_thread(channel=channel, text=formatted_batch or "", thread_ts=thread_ts)
145
+ else:
146
+ SlackTools().send_message_thread(channel=channel, text=batch_message or "", thread_ts=thread_ts)
147
+
148
+ return router
@@ -0,0 +1,30 @@
1
+ import hashlib
2
+ import hmac
3
+ import time
4
+ from os import getenv
5
+
6
+ from fastapi import HTTPException
7
+
8
+ from agno.utils.log import log_error
9
+
10
+ try:
11
+ SLACK_SIGNING_SECRET = getenv("SLACK_SIGNING_SECRET")
12
+ except Exception as e:
13
+ log_error(f"Slack signin secret missing: {e}")
14
+
15
+
16
+ def verify_slack_signature(body: bytes, timestamp: str, slack_signature: str) -> bool:
17
+ if not SLACK_SIGNING_SECRET:
18
+ raise HTTPException(status_code=500, detail="SLACK_SIGNING_SECRET is not set")
19
+
20
+ # Ensure the request timestamp is recent (e.g., to prevent replay attacks)
21
+ if abs(time.time() - int(timestamp)) > 60 * 5:
22
+ return False
23
+
24
+ sig_basestring = f"v0:{timestamp}:{body.decode('utf-8')}"
25
+ my_signature = (
26
+ "v0="
27
+ + hmac.new(SLACK_SIGNING_SECRET.encode("utf-8"), sig_basestring.encode("utf-8"), hashlib.sha256).hexdigest()
28
+ )
29
+
30
+ return hmac.compare_digest(my_signature, slack_signature)
@@ -0,0 +1,47 @@
1
+ from typing import List, Optional
2
+
3
+ from fastapi.routing import APIRouter
4
+
5
+ from agno.agent.agent import Agent
6
+ from agno.os.interfaces.base import BaseInterface
7
+ from agno.os.interfaces.slack.router import attach_routes
8
+ from agno.team.team import Team
9
+ from agno.workflow.workflow import Workflow
10
+
11
+
12
+ class Slack(BaseInterface):
13
+ type = "slack"
14
+
15
+ router: APIRouter
16
+
17
+ def __init__(
18
+ self,
19
+ agent: Optional[Agent] = None,
20
+ team: Optional[Team] = None,
21
+ workflow: Optional[Workflow] = None,
22
+ prefix: str = "/slack",
23
+ tags: Optional[List[str]] = None,
24
+ reply_to_mentions_only: bool = True,
25
+ ):
26
+ self.agent = agent
27
+ self.team = team
28
+ self.workflow = workflow
29
+ self.prefix = prefix
30
+ self.tags = tags or ["Slack"]
31
+ self.reply_to_mentions_only = reply_to_mentions_only
32
+
33
+ if not (self.agent or self.team or self.workflow):
34
+ raise ValueError("Slack requires an agent, team or workflow")
35
+
36
+ def get_router(self) -> APIRouter:
37
+ self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
38
+
39
+ self.router = attach_routes(
40
+ router=self.router,
41
+ agent=self.agent,
42
+ team=self.team,
43
+ workflow=self.workflow,
44
+ reply_to_mentions_only=self.reply_to_mentions_only,
45
+ )
46
+
47
+ return self.router
@@ -0,0 +1,3 @@
1
+ from agno.os.interfaces.whatsapp.whatsapp import Whatsapp
2
+
3
+ __all__ = ["Whatsapp"]
@@ -0,0 +1,210 @@
1
+ import base64
2
+ from os import getenv
3
+ from typing import Optional
4
+
5
+ from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
6
+ from fastapi.responses import PlainTextResponse
7
+
8
+ from agno.agent.agent import Agent
9
+ from agno.media import Audio, File, Image, Video
10
+ from agno.team.team import Team
11
+ from agno.tools.whatsapp import WhatsAppTools
12
+ from agno.utils.log import log_error, log_info, log_warning
13
+ from agno.utils.whatsapp import get_media_async, send_image_message_async, typing_indicator_async, upload_media_async
14
+
15
+ from .security import validate_webhook_signature
16
+
17
+
18
+ def attach_routes(router: APIRouter, agent: Optional[Agent] = None, team: Optional[Team] = None) -> APIRouter:
19
+ if agent is None and team is None:
20
+ raise ValueError("Either agent or team must be provided.")
21
+
22
+ # Create WhatsApp tools instance once for reuse
23
+ whatsapp_tools = WhatsAppTools(async_mode=True)
24
+
25
+ @router.get("/status")
26
+ async def status():
27
+ return {"status": "available"}
28
+
29
+ @router.get("/webhook")
30
+ async def verify_webhook(request: Request):
31
+ """Handle WhatsApp webhook verification"""
32
+ mode = request.query_params.get("hub.mode")
33
+ token = request.query_params.get("hub.verify_token")
34
+ challenge = request.query_params.get("hub.challenge")
35
+
36
+ verify_token = getenv("WHATSAPP_VERIFY_TOKEN")
37
+ if not verify_token:
38
+ raise HTTPException(status_code=500, detail="WHATSAPP_VERIFY_TOKEN is not set")
39
+
40
+ if mode == "subscribe" and token == verify_token:
41
+ if not challenge:
42
+ raise HTTPException(status_code=400, detail="No challenge received")
43
+ return PlainTextResponse(content=challenge)
44
+
45
+ raise HTTPException(status_code=403, detail="Invalid verify token or mode")
46
+
47
+ @router.post("/webhook")
48
+ async def webhook(request: Request, background_tasks: BackgroundTasks):
49
+ """Handle incoming WhatsApp messages"""
50
+ try:
51
+ # Get raw payload for signature validation
52
+ payload = await request.body()
53
+ signature = request.headers.get("X-Hub-Signature-256")
54
+
55
+ # Validate webhook signature
56
+ if not validate_webhook_signature(payload, signature):
57
+ log_warning("Invalid webhook signature")
58
+ raise HTTPException(status_code=403, detail="Invalid signature")
59
+
60
+ body = await request.json()
61
+
62
+ # Validate webhook data
63
+ if body.get("object") != "whatsapp_business_account":
64
+ log_warning(f"Received non-WhatsApp webhook object: {body.get('object')}")
65
+ return {"status": "ignored"}
66
+
67
+ # Process messages in background
68
+ for entry in body.get("entry", []):
69
+ for change in entry.get("changes", []):
70
+ messages = change.get("value", {}).get("messages", [])
71
+
72
+ if not messages:
73
+ continue
74
+
75
+ message = messages[0]
76
+ background_tasks.add_task(process_message, message, agent, team)
77
+
78
+ return {"status": "processing"}
79
+
80
+ except Exception as e:
81
+ log_error(f"Error processing webhook: {str(e)}")
82
+ raise HTTPException(status_code=500, detail=str(e))
83
+
84
+ async def process_message(message: dict, agent: Optional[Agent], team: Optional[Team]):
85
+ """Process a single WhatsApp message in the background"""
86
+ try:
87
+ message_image = None
88
+ message_video = None
89
+ message_audio = None
90
+ message_doc = None
91
+
92
+ message_id = message.get("id")
93
+ await typing_indicator_async(message_id)
94
+
95
+ if message.get("type") == "text":
96
+ message_text = message["text"]["body"]
97
+ elif message.get("type") == "image":
98
+ try:
99
+ message_text = message["image"]["caption"]
100
+ except Exception:
101
+ message_text = "Describe the image"
102
+ message_image = message["image"]["id"]
103
+ elif message.get("type") == "video":
104
+ try:
105
+ message_text = message["video"]["caption"]
106
+ except Exception:
107
+ message_text = "Describe the video"
108
+ message_video = message["video"]["id"]
109
+ elif message.get("type") == "audio":
110
+ message_text = "Reply to audio"
111
+ message_audio = message["audio"]["id"]
112
+ elif message.get("type") == "document":
113
+ message_text = "Process the document"
114
+ message_doc = message["document"]["id"]
115
+ else:
116
+ return
117
+
118
+ phone_number = message["from"]
119
+ log_info(f"Processing message from {phone_number}: {message_text}")
120
+
121
+ # Generate and send response
122
+ if agent:
123
+ response = await agent.arun(
124
+ message_text,
125
+ user_id=phone_number,
126
+ session_id=f"wa:{phone_number}",
127
+ images=[Image(content=await get_media_async(message_image))] if message_image else None,
128
+ files=[File(content=await get_media_async(message_doc))] if message_doc else None,
129
+ videos=[Video(content=await get_media_async(message_video))] if message_video else None,
130
+ audio=[Audio(content=await get_media_async(message_audio))] if message_audio else None,
131
+ )
132
+ elif team:
133
+ response = await team.arun( # type: ignore
134
+ message_text,
135
+ user_id=phone_number,
136
+ session_id=f"wa:{phone_number}",
137
+ files=[File(content=await get_media_async(message_doc))] if message_doc else None,
138
+ images=[Image(content=await get_media_async(message_image))] if message_image else None,
139
+ videos=[Video(content=await get_media_async(message_video))] if message_video else None,
140
+ audio=[Audio(content=await get_media_async(message_audio))] if message_audio else None,
141
+ )
142
+
143
+ if response.reasoning_content:
144
+ await _send_whatsapp_message(phone_number, f"Reasoning: \n{response.reasoning_content}", italics=True)
145
+
146
+ if response.images:
147
+ number_of_images = len(response.images)
148
+ log_info(f"images generated: f{number_of_images}")
149
+ for i in range(number_of_images):
150
+ image_content = response.images[i].content
151
+ image_bytes = None
152
+ if isinstance(image_content, bytes):
153
+ try:
154
+ decoded_string = image_content.decode("utf-8")
155
+
156
+ image_bytes = base64.b64decode(decoded_string)
157
+ except UnicodeDecodeError:
158
+ image_bytes = image_content
159
+ elif isinstance(image_content, str):
160
+ image_bytes = base64.b64decode(image_content)
161
+ else:
162
+ log_error(f"Unexpected image content type: {type(image_content)} for user {phone_number}")
163
+
164
+ if image_bytes:
165
+ media_id = await upload_media_async(
166
+ media_data=image_bytes, mime_type="image/png", filename="image.png"
167
+ )
168
+ await send_image_message_async(media_id=media_id, recipient=phone_number, text=response.content)
169
+ else:
170
+ log_warning(
171
+ f"Could not process image content for user {phone_number}. Type: {type(image_content)}"
172
+ )
173
+ await _send_whatsapp_message(phone_number, response.content) # type: ignore
174
+ else:
175
+ await _send_whatsapp_message(phone_number, response.content) # type: ignore
176
+
177
+ except Exception as e:
178
+ log_error(f"Error processing message: {str(e)}")
179
+
180
+ try:
181
+ await _send_whatsapp_message(
182
+ phone_number, "Sorry, there was an error processing your message. Please try again later."
183
+ )
184
+ except Exception as send_error:
185
+ log_error(f"Error sending error message: {str(send_error)}")
186
+
187
+ async def _send_whatsapp_message(recipient: str, message: str, italics: bool = False):
188
+ if len(message) <= 4096:
189
+ if italics:
190
+ # Handle multi-line messages by making each line italic
191
+ formatted_message = "\n".join([f"_{line}_" for line in message.split("\n")])
192
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_message)
193
+ else:
194
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=message)
195
+ return
196
+
197
+ # Split message into batches of 4000 characters (WhatsApp message limit is 4096)
198
+ message_batches = [message[i : i + 4000] for i in range(0, len(message), 4000)]
199
+
200
+ # Add a prefix with the batch number
201
+ for i, batch in enumerate(message_batches, 1):
202
+ batch_message = f"[{i}/{len(message_batches)}] {batch}"
203
+ if italics:
204
+ # Handle multi-line messages by making each line italic
205
+ formatted_batch = "\n".join([f"_{line}_" for line in batch_message.split("\n")])
206
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=formatted_batch)
207
+ else:
208
+ await whatsapp_tools.send_text_message_async(recipient=recipient, text=batch_message)
209
+
210
+ return router
@@ -0,0 +1,55 @@
1
+ import hashlib
2
+ import hmac
3
+ import os
4
+ from typing import Optional
5
+
6
+ from agno.utils.log import log_warning
7
+
8
+
9
+ def is_development_mode() -> bool:
10
+ """Check if the application is running in development mode."""
11
+ return os.getenv("APP_ENV", "development").lower() == "development"
12
+
13
+
14
+ def get_app_secret() -> str:
15
+ """
16
+ Get the WhatsApp app secret from environment variables.
17
+ In development mode, returns a dummy secret if WHATSAPP_APP_SECRET is not set.
18
+ """
19
+ app_secret = os.getenv("WHATSAPP_APP_SECRET")
20
+
21
+ if not app_secret:
22
+ raise ValueError("WHATSAPP_APP_SECRET environment variable is not set in production mode")
23
+
24
+ return app_secret
25
+
26
+
27
+ def validate_webhook_signature(payload: bytes, signature_header: Optional[str]) -> bool:
28
+ """
29
+ Validate the webhook payload using SHA256 signature.
30
+ In development mode, signature validation can be bypassed.
31
+
32
+ Args:
33
+ payload: The raw request payload bytes
34
+ signature_header: The X-Hub-Signature-256 header value
35
+
36
+ Returns:
37
+ bool: True if signature is valid or in development mode, False otherwise
38
+ """
39
+ # In development mode, we can bypass signature validation
40
+ if is_development_mode():
41
+ log_warning("Bypassing signature validation in development mode")
42
+ return True
43
+
44
+ if not signature_header or not signature_header.startswith("sha256="):
45
+ return False
46
+
47
+ app_secret = get_app_secret()
48
+ expected_signature = signature_header.split("sha256=")[1]
49
+
50
+ # Calculate signature
51
+ hmac_obj = hmac.new(app_secret.encode("utf-8"), payload, hashlib.sha256)
52
+ calculated_signature = hmac_obj.hexdigest()
53
+
54
+ # Compare signatures using constant-time comparison
55
+ return hmac.compare_digest(calculated_signature, expected_signature)
@@ -0,0 +1,36 @@
1
+ from typing import List, Optional
2
+
3
+ from fastapi.routing import APIRouter
4
+
5
+ from agno.agent import Agent
6
+ from agno.os.interfaces.base import BaseInterface
7
+ from agno.os.interfaces.whatsapp.router import attach_routes
8
+ from agno.team import Team
9
+
10
+
11
+ class Whatsapp(BaseInterface):
12
+ type = "whatsapp"
13
+
14
+ router: APIRouter
15
+
16
+ def __init__(
17
+ self,
18
+ agent: Optional[Agent] = None,
19
+ team: Optional[Team] = None,
20
+ prefix: str = "/whatsapp",
21
+ tags: Optional[List[str]] = None,
22
+ ):
23
+ self.agent = agent
24
+ self.team = team
25
+ self.prefix = prefix
26
+ self.tags = tags or ["Whatsapp"]
27
+
28
+ if not (self.agent or self.team):
29
+ raise ValueError("Whatsapp requires an agent or a team")
30
+
31
+ def get_router(self) -> APIRouter:
32
+ self.router = APIRouter(prefix=self.prefix, tags=self.tags) # type: ignore
33
+
34
+ self.router = attach_routes(router=self.router, agent=self.agent, team=self.team)
35
+
36
+ return self.router