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,233 @@
1
+ import json
2
+ from os import getenv
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from agno.tools import Toolkit
6
+ from agno.utils.log import log_info, logger
7
+
8
+ try:
9
+ import requests
10
+ except ImportError:
11
+ raise ImportError("`requests` not installed. Please install using `pip install requests`")
12
+
13
+
14
+ class OpenWeatherTools(Toolkit):
15
+ """
16
+ OpenWeather is a toolkit for accessing weather data from OpenWeatherMap API.
17
+
18
+ Args:
19
+ api_key (Optional[str]): OpenWeatherMap API key. If not provided, will try to get from OPENWEATHER_API_KEY env var.
20
+ units (str): Units of measurement. Options are 'standard', 'metric', and 'imperial'. Default is 'metric'.
21
+ enable_current_weather (bool): Enable current weather function. Default is True.
22
+ enable_forecast (bool): Enable forecast function. Default is True.
23
+ enable_air_pollution (bool): Enable air pollution function. Default is True.
24
+ enable_geocoding (bool): Enable geocoding function. Default is True.
25
+ all (bool): Enable all functions. Default is False.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: Optional[str] = None,
31
+ units: str = "metric",
32
+ enable_current_weather: bool = True,
33
+ enable_forecast: bool = True,
34
+ enable_air_pollution: bool = True,
35
+ enable_geocoding: bool = True,
36
+ all: bool = False,
37
+ **kwargs,
38
+ ):
39
+ self.api_key = api_key or getenv("OPENWEATHER_API_KEY")
40
+ if not self.api_key:
41
+ raise ValueError(
42
+ "OpenWeather API key is required. Provide it as an argument or set the OPENWEATHER_API_KEY environment variable."
43
+ )
44
+
45
+ self.units = units
46
+ self.base_url = "https://api.openweathermap.org/data/2.5"
47
+ self.geo_url = "https://api.openweathermap.org/geo/1.0"
48
+
49
+ tools: List[Any] = []
50
+ if enable_current_weather or all:
51
+ tools.append(self.get_current_weather)
52
+ if enable_forecast or all:
53
+ tools.append(self.get_forecast)
54
+ if enable_air_pollution or all:
55
+ tools.append(self.get_air_pollution)
56
+ if enable_geocoding or all:
57
+ tools.append(self.geocode_location)
58
+
59
+ super().__init__(name="openweather_tools", tools=tools, **kwargs)
60
+
61
+ def _make_request(self, url: str, params: Dict) -> Dict:
62
+ """Make a request to the OpenWeatherMap API.
63
+
64
+ Args:
65
+ url (str): The API endpoint URL.
66
+ params (Dict): Query parameters for the request.
67
+
68
+ Returns:
69
+ Dict: The JSON response from the API.
70
+ """
71
+ try:
72
+ params["appid"] = self.api_key
73
+ response = requests.get(url, params=params)
74
+ response.raise_for_status()
75
+ return response.json()
76
+ except requests.exceptions.RequestException as e:
77
+ logger.error(f"Error making request to {url}: {e}")
78
+ return {"error": str(e)}
79
+
80
+ def geocode_location(self, location: str, limit: int = 1) -> str:
81
+ """Convert a location name to geographic coordinates.
82
+
83
+ Args:
84
+ location (str): The name of the city, e.g., "London", "Paris", "New York".
85
+ limit (int): Maximum number of location results. Default is 1.
86
+
87
+ Returns:
88
+ str: JSON string containing location data with coordinates.
89
+ """
90
+ try:
91
+ log_info(f"Geocoding location: {location}")
92
+ url = f"{self.geo_url}/direct"
93
+ params = {"q": location, "limit": limit}
94
+
95
+ result = self._make_request(url, params)
96
+
97
+ if "error" in result:
98
+ return json.dumps(result)
99
+
100
+ if not result:
101
+ return json.dumps({"error": f"No location found for '{location}'"})
102
+
103
+ return json.dumps(result, indent=2)
104
+ except Exception as e:
105
+ logger.error(f"Error geocoding location: {e}")
106
+ return json.dumps({"error": str(e)})
107
+
108
+ def get_current_weather(self, location: str) -> str:
109
+ """Get current weather data for a location.
110
+
111
+ Args:
112
+ location (str): The name of the city, e.g., "London", "Paris", "New York".
113
+
114
+ Returns:
115
+ str: JSON string containing current weather data.
116
+ """
117
+ try:
118
+ log_info(f"Getting current weather for: {location}")
119
+
120
+ # First geocode the location to get coordinates
121
+ geocode_result = json.loads(self.geocode_location(location))
122
+ if "error" in geocode_result:
123
+ return json.dumps(geocode_result)
124
+
125
+ if not geocode_result:
126
+ return json.dumps({"error": f"No location found for '{location}'"})
127
+
128
+ # Get the first location result
129
+ loc_data = geocode_result[0]
130
+ lat, lon = loc_data["lat"], loc_data["lon"]
131
+
132
+ # Get current weather using coordinates
133
+ url = f"{self.base_url}/weather"
134
+ params = {"lat": lat, "lon": lon, "units": self.units}
135
+
136
+ result = self._make_request(url, params)
137
+
138
+ # Add the location name to the result
139
+ if "error" not in result:
140
+ result["location_name"] = loc_data.get("name", location)
141
+ result["country"] = loc_data.get("country", "")
142
+
143
+ return json.dumps(result, indent=2)
144
+ except Exception as e:
145
+ logger.error(f"Error getting current weather: {e}")
146
+ return json.dumps({"error": str(e)})
147
+
148
+ def get_forecast(self, location: str, days: int = 5) -> str:
149
+ """Get weather forecast for a location.
150
+
151
+ Args:
152
+ location (str): The name of the city, e.g., "London", "Paris", "New York".
153
+ days (int): Number of days for forecast (max 5). Default is 5.
154
+
155
+ Returns:
156
+ str: JSON string containing forecast data.
157
+ """
158
+ try:
159
+ log_info(f"Getting {days}-day forecast for: {location}")
160
+
161
+ # First geocode the location to get coordinates
162
+ geocode_result = json.loads(self.geocode_location(location))
163
+ if "error" in geocode_result:
164
+ return json.dumps(geocode_result)
165
+
166
+ if not geocode_result:
167
+ return json.dumps({"error": f"No location found for '{location}'"})
168
+
169
+ # Get the first location result
170
+ loc_data = geocode_result[0]
171
+ lat, lon = loc_data["lat"], loc_data["lon"]
172
+
173
+ # Get forecast using coordinates
174
+ url = f"{self.base_url}/forecast"
175
+ params = {
176
+ "lat": lat,
177
+ "lon": lon,
178
+ "units": self.units,
179
+ # Each day has 8 3-hour forecasts, max 5 days (40 entries)
180
+ "cnt": min(days * 8, 40),
181
+ }
182
+
183
+ result = self._make_request(url, params)
184
+
185
+ # Add the location name to the result
186
+ if "error" not in result:
187
+ result["location_name"] = loc_data.get("name", location)
188
+ result["country"] = loc_data.get("country", "")
189
+
190
+ return json.dumps(result, indent=2)
191
+ except Exception as e:
192
+ logger.error(f"Error getting forecast: {e}")
193
+ return json.dumps({"error": str(e)})
194
+
195
+ def get_air_pollution(self, location: str) -> str:
196
+ """Get current air pollution data for a location.
197
+
198
+ Args:
199
+ location (str): The name of the city, e.g., "London", "Paris", "New York".
200
+
201
+ Returns:
202
+ str: JSON string containing air pollution data.
203
+ """
204
+ try:
205
+ log_info(f"Getting air pollution data for: {location}")
206
+
207
+ # First geocode the location to get coordinates
208
+ geocode_result = json.loads(self.geocode_location(location))
209
+ if "error" in geocode_result:
210
+ return json.dumps(geocode_result)
211
+
212
+ if not geocode_result:
213
+ return json.dumps({"error": f"No location found for '{location}'"})
214
+
215
+ # Get the first location result
216
+ loc_data = geocode_result[0]
217
+ lat, lon = loc_data["lat"], loc_data["lon"]
218
+
219
+ # Get air pollution data using coordinates
220
+ url = f"{self.base_url}/air_pollution"
221
+ params = {"lat": lat, "lon": lon}
222
+
223
+ result = self._make_request(url, params)
224
+
225
+ # Add the location name to the result
226
+ if "error" not in result:
227
+ result["location_name"] = loc_data.get("name", location)
228
+ result["country"] = loc_data.get("country", "")
229
+
230
+ return json.dumps(result, indent=2)
231
+ except Exception as e:
232
+ logger.error(f"Error getting air pollution data: {e}")
233
+ return json.dumps({"error": str(e)})
agno/tools/oxylabs.py ADDED
@@ -0,0 +1,385 @@
1
+ import json
2
+ from os import getenv
3
+ from typing import Any, Callable, Dict, List, Optional
4
+ from urllib.parse import urlparse
5
+
6
+ from agno.tools import Toolkit
7
+ from agno.utils.log import log_debug, log_error, log_info
8
+
9
+ try:
10
+ from oxylabs import RealtimeClient
11
+ from oxylabs.sources.response import Response
12
+ from oxylabs.utils.types import render
13
+ except ImportError:
14
+ raise ImportError("Oxylabs SDK not found. Please install it with: pip install oxylabs")
15
+
16
+
17
+ class OxylabsTools(Toolkit):
18
+ def __init__(
19
+ self,
20
+ username: Optional[str] = None,
21
+ password: Optional[str] = None,
22
+ **kwargs,
23
+ ):
24
+ self.username = username or getenv("OXYLABS_USERNAME")
25
+ self.password = password or getenv("OXYLABS_PASSWORD")
26
+
27
+ if not self.username or not self.password:
28
+ raise ValueError(
29
+ "No Oxylabs credentials provided. Please set the OXYLABS_USERNAME and OXYLABS_PASSWORD environment variables or pass them to the OxylabsTools constructor."
30
+ )
31
+
32
+ try:
33
+ log_debug(f"Initializing Oxylabs client with username: {self.username[:5]}...")
34
+ self.client = RealtimeClient(self.username, self.password)
35
+ log_debug("Oxylabs client initialized successfully")
36
+ except Exception as e:
37
+ log_debug(f"Failed to initialize Oxylabs client: {e}")
38
+ raise
39
+
40
+ tools: List[Callable[..., str]] = [
41
+ self.search_google,
42
+ self.get_amazon_product,
43
+ self.search_amazon_products,
44
+ self.scrape_website,
45
+ ]
46
+
47
+ super().__init__(name="oxylabs_web_scraping", tools=tools, **kwargs)
48
+
49
+ def search_google(self, query: str, domain_code: str = "com") -> str:
50
+ """Search Google for a query.
51
+
52
+ Args:
53
+ query: Search query
54
+ domain_code: Google domain to search (e.g., "com", "co.uk", "de", default: "com")
55
+
56
+ Returns:
57
+ JSON of search results
58
+ """
59
+ try:
60
+ if not query or not isinstance(query, str) or len(query.strip()) == 0:
61
+ return self._error_response("search_google", "Query cannot be empty", {"query": query})
62
+
63
+ if not isinstance(domain_code, str) or len(domain_code) > 10:
64
+ return self._error_response("search_google", "Domain must be a valid string (e.g., 'com', 'co.uk')")
65
+
66
+ query = query.strip()
67
+ log_debug(f"Google search: '{query}' on google.{domain_code}")
68
+
69
+ response: Response = self.client.google.scrape_search(query=query, domain=domain_code, parse=True)
70
+
71
+ # Extract search results
72
+ search_results = []
73
+
74
+ if response.results and len(response.results) > 0:
75
+ result = response.results[0]
76
+
77
+ # Try parsed content first
78
+ if hasattr(result, "content_parsed") and result.content_parsed:
79
+ content = result.content_parsed
80
+ if hasattr(content, "results") and content.results:
81
+ raw_results = content.results.raw if hasattr(content.results, "raw") else {}
82
+ organic_results = raw_results.get("organic", [])
83
+
84
+ for item in organic_results:
85
+ search_results.append(
86
+ {
87
+ "title": item.get("title", "").strip(),
88
+ "url": item.get("url", "").strip(),
89
+ "description": item.get("desc", "").strip(),
90
+ "position": item.get("pos", 0),
91
+ }
92
+ )
93
+
94
+ if not search_results and hasattr(result, "content"):
95
+ raw_content = result.content
96
+ if isinstance(raw_content, dict) and "results" in raw_content:
97
+ organic_results = raw_content["results"].get("organic", [])
98
+ for item in organic_results:
99
+ search_results.append(
100
+ {
101
+ "title": item.get("title", "").strip(),
102
+ "url": item.get("url", "").strip(),
103
+ "description": item.get("desc", "").strip(),
104
+ "position": item.get("pos", 0),
105
+ }
106
+ )
107
+
108
+ response_data = {
109
+ "tool": "search_google",
110
+ "query": query,
111
+ "results": search_results,
112
+ }
113
+
114
+ log_info(f"Google search completed. Found {len(search_results)} results")
115
+ return json.dumps(response_data, indent=2)
116
+
117
+ except Exception as e:
118
+ error_msg = f"Google search failed: {str(e)}"
119
+ log_error(error_msg)
120
+ return self._error_response("search_google", error_msg, {"query": query})
121
+
122
+ def get_amazon_product(self, asin: str, domain_code: str = "com") -> str:
123
+ """Get detailed information about an Amazon product by ASIN.
124
+
125
+ Args:
126
+ asin: Amazon Standard Identification Number (10 alphanumeric characters, e.g., "B07FZ8S74R")
127
+ domain_code: Amazon domain (e.g., "com", "co.uk", "de", default: "com")
128
+
129
+ Returns:
130
+ JSON of product details
131
+ """
132
+ try:
133
+ if not asin or not isinstance(asin, str):
134
+ return self._error_response("get_amazon_product", "ASIN is required and must be a string")
135
+
136
+ asin = asin.strip().upper()
137
+ if len(asin) != 10 or not asin.isalnum():
138
+ return self._error_response(
139
+ "get_amazon_product",
140
+ f"Invalid ASIN format: {asin}. Must be 10 alphanumeric characters (e.g., 'B07FZ8S74R')",
141
+ )
142
+
143
+ if not isinstance(domain_code, str) or len(domain_code) > 10:
144
+ return self._error_response(
145
+ "get_amazon_product", "Domain must be a valid string (e.g., 'com', 'co.uk')"
146
+ )
147
+
148
+ log_debug(f"Amazon product lookup: ASIN {asin} on amazon.{domain_code}")
149
+
150
+ response: Response = self.client.amazon.scrape_product(query=asin, domain=domain_code, parse=True)
151
+
152
+ product_info = {"found": False, "asin": asin, "domain": f"amazon.{domain_code}"}
153
+
154
+ if response.results and len(response.results) > 0:
155
+ result = response.results[0]
156
+
157
+ if hasattr(result, "content") and result.content:
158
+ content = result.content
159
+ if isinstance(content, dict):
160
+ product_info.update(
161
+ {
162
+ "found": True,
163
+ "title": content.get("title", "").strip(),
164
+ "price": content.get("price", 0),
165
+ "currency": content.get("currency", ""),
166
+ "rating": content.get("rating", 0),
167
+ "reviews_count": content.get("reviews_count", 0),
168
+ "url": content.get("url", ""),
169
+ "description": content.get("description", "").strip(),
170
+ "stock_status": content.get("stock", "").strip(),
171
+ "brand": content.get("brand", "").strip(),
172
+ "images": content.get("images", [])[:3],
173
+ "bullet_points": content.get("bullet_points", [])[:5]
174
+ if content.get("bullet_points")
175
+ else [],
176
+ }
177
+ )
178
+
179
+ elif hasattr(result, "content_parsed") and result.content_parsed:
180
+ content = result.content_parsed
181
+ product_info.update(
182
+ {
183
+ "found": True,
184
+ "title": getattr(content, "title", "").strip(),
185
+ "price": getattr(content, "price", 0),
186
+ "currency": getattr(content, "currency", ""),
187
+ "rating": getattr(content, "rating", 0),
188
+ "reviews_count": getattr(content, "reviews_count", 0),
189
+ "url": getattr(content, "url", ""),
190
+ "description": getattr(content, "description", "").strip(),
191
+ "stock_status": getattr(content, "stock", "").strip(),
192
+ "brand": getattr(content, "brand", "").strip(),
193
+ "images": getattr(content, "images", [])[:3],
194
+ "bullet_points": getattr(content, "bullet_points", [])[:5]
195
+ if getattr(content, "bullet_points", None)
196
+ else [],
197
+ }
198
+ )
199
+
200
+ response_data = {
201
+ "tool": "get_amazon_product",
202
+ "asin": asin,
203
+ "product_info": product_info,
204
+ }
205
+
206
+ log_info(f"Amazon product lookup completed for ASIN {asin}")
207
+ return json.dumps(response_data, indent=2)
208
+
209
+ except Exception as e:
210
+ error_msg = f"Amazon product lookup failed: {str(e)}"
211
+ log_error(error_msg)
212
+ return self._error_response("get_amazon_product", error_msg, {"asin": asin})
213
+
214
+ def search_amazon_products(self, query: str, domain_code: str = "com") -> str:
215
+ """Search Amazon for products and return search results.
216
+
217
+ Args:
218
+ query: Product search query
219
+ domain_code: Amazon domain (e.g., "com", "co.uk", "de", default: "com")
220
+
221
+ Returns:
222
+ JSON string with search results containing:
223
+ - success: boolean indicating if search was successful
224
+ - query: the original search query
225
+ - total_products: number of products found
226
+ - products: list of product results with title, asin, price, rating, etc.
227
+ """
228
+ try:
229
+ if not query or not isinstance(query, str) or len(query.strip()) == 0:
230
+ return self._error_response("search_amazon_products", "Query cannot be empty")
231
+
232
+ if not isinstance(domain_code, str) or len(domain_code) > 10:
233
+ return self._error_response(
234
+ "search_amazon_products", "Domain must be a valid string (e.g., 'com', 'co.uk')"
235
+ )
236
+
237
+ query = query.strip()
238
+ log_info(f"Amazon search: '{query}' on amazon.{domain_code}")
239
+
240
+ response: Response = self.client.amazon.scrape_search(query=query, domain=domain_code, parse=True)
241
+
242
+ # Extract search results
243
+ products = []
244
+
245
+ if response.results and len(response.results) > 0:
246
+ result = response.results[0]
247
+
248
+ if hasattr(result, "content") and result.content:
249
+ content = result.content
250
+ if isinstance(content, dict) and "results" in content:
251
+ organic_results = content["results"].get("organic", [])
252
+
253
+ for item in organic_results:
254
+ products.append(
255
+ {
256
+ "title": item.get("title", "").strip(),
257
+ "asin": item.get("asin", "").strip(),
258
+ "price": item.get("price", 0),
259
+ "currency": item.get("currency", ""),
260
+ "rating": item.get("rating", 0),
261
+ "reviews_count": item.get("reviews_count", 0),
262
+ "url": item.get("url", "").strip(),
263
+ "position": item.get("pos", 0),
264
+ "image": item.get("image", "").strip(),
265
+ }
266
+ )
267
+
268
+ elif hasattr(result, "content_parsed") and result.content_parsed:
269
+ content = result.content_parsed
270
+ if hasattr(content, "results") and content.results:
271
+ if hasattr(content.results, "organic"):
272
+ organic_results = content.results.organic
273
+ for item in organic_results:
274
+ products.append(
275
+ {
276
+ "title": getattr(item, "title", "").strip(),
277
+ "asin": getattr(item, "asin", "").strip(),
278
+ "price": getattr(item, "price", 0),
279
+ "currency": getattr(item, "currency", ""),
280
+ "rating": getattr(item, "rating", 0),
281
+ "reviews_count": getattr(item, "reviews_count", 0),
282
+ "url": getattr(item, "url", "").strip(),
283
+ "position": getattr(item, "pos", 0),
284
+ "image": getattr(item, "image", "").strip(),
285
+ }
286
+ )
287
+
288
+ response_data = {
289
+ "tool": "search_amazon_products",
290
+ "query": query,
291
+ "products": products,
292
+ }
293
+
294
+ log_debug(f"Amazon search completed. Found {len(products)} products")
295
+ return json.dumps(response_data, indent=2)
296
+
297
+ except Exception as e:
298
+ error_msg = f"Amazon search failed: {str(e)}"
299
+ log_error(error_msg)
300
+ return self._error_response("search_amazon_products", error_msg, {"query": query})
301
+
302
+ def scrape_website(self, url: str, render_javascript: bool = False) -> str:
303
+ """Scrape content from any website URL.
304
+
305
+ Args:
306
+ url: Website URL to scrape (must start with http:// or ht ps://)
307
+ render_javascript: Whether to enable JavaScript rendering for dynamic content (default: False)
308
+
309
+ Returns:
310
+ JSON of results
311
+ """
312
+ try:
313
+ if not url or not isinstance(url, str):
314
+ return self._error_response("scrape_website", "URL is required and must be a string")
315
+
316
+ url = url.strip()
317
+ if not url.startswith(("http://", "https://")):
318
+ return self._error_response(
319
+ "scrape_website", f"Invalid URL format: {url}. Must start with http:// or https://"
320
+ )
321
+
322
+ try:
323
+ parsed_url = urlparse(url)
324
+ if not parsed_url.netloc:
325
+ return self._error_response("scrape_website", f"Invalid URL format: {url}. Missing domain name")
326
+ except Exception:
327
+ return self._error_response("scrape_website", f"Invalid URL format: {url}")
328
+
329
+ if not isinstance(render_javascript, bool):
330
+ return self._error_response("scrape_website", "render_javascript must be a boolean (True/False)")
331
+
332
+ log_debug(f"Website scraping: {url} (JS rendering: {render_javascript})")
333
+
334
+ response: Response = self.client.universal.scrape_url(
335
+ url=url, render=render.HTML if render_javascript else None, parse=True
336
+ )
337
+
338
+ content_info = {"url": url, "javascript_rendered": render_javascript}
339
+
340
+ if response.results and len(response.results) > 0:
341
+ result = response.results[0]
342
+ content = result.content
343
+ status_code = getattr(result, "status_code", None)
344
+
345
+ content_preview = ""
346
+ content_length = 0
347
+
348
+ if content:
349
+ try:
350
+ content_str = str(content)
351
+ content_length = len(content_str)
352
+ content_preview = content_str[:1000] if content_length > 1000 else content_str
353
+ content_info["scraped"] = True
354
+ except Exception as e:
355
+ log_debug(f"Could not process content: {e}")
356
+ content_preview = "Content available but processing failed"
357
+ content_info["scraped"] = False
358
+
359
+ content_info.update(
360
+ {
361
+ "status_code": status_code,
362
+ "content_length": content_length,
363
+ "content_preview": content_preview.strip(),
364
+ "has_content": content_length > 0,
365
+ }
366
+ )
367
+
368
+ response_data = {
369
+ "tool": "scrape_website",
370
+ "url": url,
371
+ "content_info": content_info,
372
+ }
373
+
374
+ log_debug(f"Website scraping completed for {url}")
375
+ return json.dumps(response_data, indent=2)
376
+
377
+ except Exception as e:
378
+ error_msg = f"Website scraping failed: {str(e)}"
379
+ log_error(error_msg)
380
+ return self._error_response("scrape_website", error_msg, {"url": url})
381
+
382
+ def _error_response(self, tool_name: str, error_message: str, context: Optional[Dict[str, Any]] = None) -> str:
383
+ """Generate a standardized error response."""
384
+ error_data = {"tool": tool_name, "error": error_message, "context": context or {}}
385
+ return json.dumps(error_data, indent=2)