agno 2.2.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (575) hide show
  1. agno/__init__.py +8 -0
  2. agno/agent/__init__.py +51 -0
  3. agno/agent/agent.py +10405 -0
  4. agno/api/__init__.py +0 -0
  5. agno/api/agent.py +28 -0
  6. agno/api/api.py +40 -0
  7. agno/api/evals.py +22 -0
  8. agno/api/os.py +17 -0
  9. agno/api/routes.py +13 -0
  10. agno/api/schemas/__init__.py +9 -0
  11. agno/api/schemas/agent.py +16 -0
  12. agno/api/schemas/evals.py +16 -0
  13. agno/api/schemas/os.py +14 -0
  14. agno/api/schemas/response.py +6 -0
  15. agno/api/schemas/team.py +16 -0
  16. agno/api/schemas/utils.py +21 -0
  17. agno/api/schemas/workflows.py +16 -0
  18. agno/api/settings.py +53 -0
  19. agno/api/team.py +30 -0
  20. agno/api/workflow.py +28 -0
  21. agno/cloud/aws/base.py +214 -0
  22. agno/cloud/aws/s3/__init__.py +2 -0
  23. agno/cloud/aws/s3/api_client.py +43 -0
  24. agno/cloud/aws/s3/bucket.py +195 -0
  25. agno/cloud/aws/s3/object.py +57 -0
  26. agno/culture/__init__.py +3 -0
  27. agno/culture/manager.py +956 -0
  28. agno/db/__init__.py +24 -0
  29. agno/db/async_postgres/__init__.py +3 -0
  30. agno/db/base.py +598 -0
  31. agno/db/dynamo/__init__.py +3 -0
  32. agno/db/dynamo/dynamo.py +2042 -0
  33. agno/db/dynamo/schemas.py +314 -0
  34. agno/db/dynamo/utils.py +743 -0
  35. agno/db/firestore/__init__.py +3 -0
  36. agno/db/firestore/firestore.py +1795 -0
  37. agno/db/firestore/schemas.py +140 -0
  38. agno/db/firestore/utils.py +376 -0
  39. agno/db/gcs_json/__init__.py +3 -0
  40. agno/db/gcs_json/gcs_json_db.py +1335 -0
  41. agno/db/gcs_json/utils.py +228 -0
  42. agno/db/in_memory/__init__.py +3 -0
  43. agno/db/in_memory/in_memory_db.py +1160 -0
  44. agno/db/in_memory/utils.py +230 -0
  45. agno/db/json/__init__.py +3 -0
  46. agno/db/json/json_db.py +1328 -0
  47. agno/db/json/utils.py +230 -0
  48. agno/db/migrations/__init__.py +0 -0
  49. agno/db/migrations/v1_to_v2.py +635 -0
  50. agno/db/mongo/__init__.py +17 -0
  51. agno/db/mongo/async_mongo.py +2026 -0
  52. agno/db/mongo/mongo.py +1982 -0
  53. agno/db/mongo/schemas.py +87 -0
  54. agno/db/mongo/utils.py +259 -0
  55. agno/db/mysql/__init__.py +3 -0
  56. agno/db/mysql/mysql.py +2308 -0
  57. agno/db/mysql/schemas.py +138 -0
  58. agno/db/mysql/utils.py +355 -0
  59. agno/db/postgres/__init__.py +4 -0
  60. agno/db/postgres/async_postgres.py +1927 -0
  61. agno/db/postgres/postgres.py +2260 -0
  62. agno/db/postgres/schemas.py +139 -0
  63. agno/db/postgres/utils.py +442 -0
  64. agno/db/redis/__init__.py +3 -0
  65. agno/db/redis/redis.py +1660 -0
  66. agno/db/redis/schemas.py +123 -0
  67. agno/db/redis/utils.py +346 -0
  68. agno/db/schemas/__init__.py +4 -0
  69. agno/db/schemas/culture.py +120 -0
  70. agno/db/schemas/evals.py +33 -0
  71. agno/db/schemas/knowledge.py +40 -0
  72. agno/db/schemas/memory.py +46 -0
  73. agno/db/schemas/metrics.py +0 -0
  74. agno/db/singlestore/__init__.py +3 -0
  75. agno/db/singlestore/schemas.py +130 -0
  76. agno/db/singlestore/singlestore.py +2272 -0
  77. agno/db/singlestore/utils.py +384 -0
  78. agno/db/sqlite/__init__.py +4 -0
  79. agno/db/sqlite/async_sqlite.py +2293 -0
  80. agno/db/sqlite/schemas.py +133 -0
  81. agno/db/sqlite/sqlite.py +2288 -0
  82. agno/db/sqlite/utils.py +431 -0
  83. agno/db/surrealdb/__init__.py +3 -0
  84. agno/db/surrealdb/metrics.py +292 -0
  85. agno/db/surrealdb/models.py +309 -0
  86. agno/db/surrealdb/queries.py +71 -0
  87. agno/db/surrealdb/surrealdb.py +1353 -0
  88. agno/db/surrealdb/utils.py +147 -0
  89. agno/db/utils.py +116 -0
  90. agno/debug.py +18 -0
  91. agno/eval/__init__.py +14 -0
  92. agno/eval/accuracy.py +834 -0
  93. agno/eval/performance.py +773 -0
  94. agno/eval/reliability.py +306 -0
  95. agno/eval/utils.py +119 -0
  96. agno/exceptions.py +161 -0
  97. agno/filters.py +354 -0
  98. agno/guardrails/__init__.py +6 -0
  99. agno/guardrails/base.py +19 -0
  100. agno/guardrails/openai.py +144 -0
  101. agno/guardrails/pii.py +94 -0
  102. agno/guardrails/prompt_injection.py +52 -0
  103. agno/integrations/__init__.py +0 -0
  104. agno/integrations/discord/__init__.py +3 -0
  105. agno/integrations/discord/client.py +203 -0
  106. agno/knowledge/__init__.py +5 -0
  107. agno/knowledge/chunking/__init__.py +0 -0
  108. agno/knowledge/chunking/agentic.py +79 -0
  109. agno/knowledge/chunking/document.py +91 -0
  110. agno/knowledge/chunking/fixed.py +57 -0
  111. agno/knowledge/chunking/markdown.py +151 -0
  112. agno/knowledge/chunking/recursive.py +63 -0
  113. agno/knowledge/chunking/row.py +39 -0
  114. agno/knowledge/chunking/semantic.py +86 -0
  115. agno/knowledge/chunking/strategy.py +165 -0
  116. agno/knowledge/content.py +74 -0
  117. agno/knowledge/document/__init__.py +5 -0
  118. agno/knowledge/document/base.py +58 -0
  119. agno/knowledge/embedder/__init__.py +5 -0
  120. agno/knowledge/embedder/aws_bedrock.py +343 -0
  121. agno/knowledge/embedder/azure_openai.py +210 -0
  122. agno/knowledge/embedder/base.py +23 -0
  123. agno/knowledge/embedder/cohere.py +323 -0
  124. agno/knowledge/embedder/fastembed.py +62 -0
  125. agno/knowledge/embedder/fireworks.py +13 -0
  126. agno/knowledge/embedder/google.py +258 -0
  127. agno/knowledge/embedder/huggingface.py +94 -0
  128. agno/knowledge/embedder/jina.py +182 -0
  129. agno/knowledge/embedder/langdb.py +22 -0
  130. agno/knowledge/embedder/mistral.py +206 -0
  131. agno/knowledge/embedder/nebius.py +13 -0
  132. agno/knowledge/embedder/ollama.py +154 -0
  133. agno/knowledge/embedder/openai.py +195 -0
  134. agno/knowledge/embedder/sentence_transformer.py +63 -0
  135. agno/knowledge/embedder/together.py +13 -0
  136. agno/knowledge/embedder/vllm.py +262 -0
  137. agno/knowledge/embedder/voyageai.py +165 -0
  138. agno/knowledge/knowledge.py +1988 -0
  139. agno/knowledge/reader/__init__.py +7 -0
  140. agno/knowledge/reader/arxiv_reader.py +81 -0
  141. agno/knowledge/reader/base.py +95 -0
  142. agno/knowledge/reader/csv_reader.py +166 -0
  143. agno/knowledge/reader/docx_reader.py +82 -0
  144. agno/knowledge/reader/field_labeled_csv_reader.py +292 -0
  145. agno/knowledge/reader/firecrawl_reader.py +201 -0
  146. agno/knowledge/reader/json_reader.py +87 -0
  147. agno/knowledge/reader/markdown_reader.py +137 -0
  148. agno/knowledge/reader/pdf_reader.py +431 -0
  149. agno/knowledge/reader/pptx_reader.py +101 -0
  150. agno/knowledge/reader/reader_factory.py +313 -0
  151. agno/knowledge/reader/s3_reader.py +89 -0
  152. agno/knowledge/reader/tavily_reader.py +194 -0
  153. agno/knowledge/reader/text_reader.py +115 -0
  154. agno/knowledge/reader/web_search_reader.py +372 -0
  155. agno/knowledge/reader/website_reader.py +455 -0
  156. agno/knowledge/reader/wikipedia_reader.py +59 -0
  157. agno/knowledge/reader/youtube_reader.py +78 -0
  158. agno/knowledge/remote_content/__init__.py +0 -0
  159. agno/knowledge/remote_content/remote_content.py +88 -0
  160. agno/knowledge/reranker/__init__.py +3 -0
  161. agno/knowledge/reranker/base.py +14 -0
  162. agno/knowledge/reranker/cohere.py +64 -0
  163. agno/knowledge/reranker/infinity.py +195 -0
  164. agno/knowledge/reranker/sentence_transformer.py +54 -0
  165. agno/knowledge/types.py +39 -0
  166. agno/knowledge/utils.py +189 -0
  167. agno/media.py +462 -0
  168. agno/memory/__init__.py +3 -0
  169. agno/memory/manager.py +1327 -0
  170. agno/models/__init__.py +0 -0
  171. agno/models/aimlapi/__init__.py +5 -0
  172. agno/models/aimlapi/aimlapi.py +45 -0
  173. agno/models/anthropic/__init__.py +5 -0
  174. agno/models/anthropic/claude.py +757 -0
  175. agno/models/aws/__init__.py +15 -0
  176. agno/models/aws/bedrock.py +701 -0
  177. agno/models/aws/claude.py +378 -0
  178. agno/models/azure/__init__.py +18 -0
  179. agno/models/azure/ai_foundry.py +485 -0
  180. agno/models/azure/openai_chat.py +131 -0
  181. agno/models/base.py +2175 -0
  182. agno/models/cerebras/__init__.py +12 -0
  183. agno/models/cerebras/cerebras.py +501 -0
  184. agno/models/cerebras/cerebras_openai.py +112 -0
  185. agno/models/cohere/__init__.py +5 -0
  186. agno/models/cohere/chat.py +389 -0
  187. agno/models/cometapi/__init__.py +5 -0
  188. agno/models/cometapi/cometapi.py +57 -0
  189. agno/models/dashscope/__init__.py +5 -0
  190. agno/models/dashscope/dashscope.py +91 -0
  191. agno/models/deepinfra/__init__.py +5 -0
  192. agno/models/deepinfra/deepinfra.py +28 -0
  193. agno/models/deepseek/__init__.py +5 -0
  194. agno/models/deepseek/deepseek.py +61 -0
  195. agno/models/defaults.py +1 -0
  196. agno/models/fireworks/__init__.py +5 -0
  197. agno/models/fireworks/fireworks.py +26 -0
  198. agno/models/google/__init__.py +5 -0
  199. agno/models/google/gemini.py +1085 -0
  200. agno/models/groq/__init__.py +5 -0
  201. agno/models/groq/groq.py +556 -0
  202. agno/models/huggingface/__init__.py +5 -0
  203. agno/models/huggingface/huggingface.py +491 -0
  204. agno/models/ibm/__init__.py +5 -0
  205. agno/models/ibm/watsonx.py +422 -0
  206. agno/models/internlm/__init__.py +3 -0
  207. agno/models/internlm/internlm.py +26 -0
  208. agno/models/langdb/__init__.py +1 -0
  209. agno/models/langdb/langdb.py +48 -0
  210. agno/models/litellm/__init__.py +14 -0
  211. agno/models/litellm/chat.py +468 -0
  212. agno/models/litellm/litellm_openai.py +25 -0
  213. agno/models/llama_cpp/__init__.py +5 -0
  214. agno/models/llama_cpp/llama_cpp.py +22 -0
  215. agno/models/lmstudio/__init__.py +5 -0
  216. agno/models/lmstudio/lmstudio.py +25 -0
  217. agno/models/message.py +434 -0
  218. agno/models/meta/__init__.py +12 -0
  219. agno/models/meta/llama.py +475 -0
  220. agno/models/meta/llama_openai.py +78 -0
  221. agno/models/metrics.py +120 -0
  222. agno/models/mistral/__init__.py +5 -0
  223. agno/models/mistral/mistral.py +432 -0
  224. agno/models/nebius/__init__.py +3 -0
  225. agno/models/nebius/nebius.py +54 -0
  226. agno/models/nexus/__init__.py +3 -0
  227. agno/models/nexus/nexus.py +22 -0
  228. agno/models/nvidia/__init__.py +5 -0
  229. agno/models/nvidia/nvidia.py +28 -0
  230. agno/models/ollama/__init__.py +5 -0
  231. agno/models/ollama/chat.py +441 -0
  232. agno/models/openai/__init__.py +9 -0
  233. agno/models/openai/chat.py +883 -0
  234. agno/models/openai/like.py +27 -0
  235. agno/models/openai/responses.py +1050 -0
  236. agno/models/openrouter/__init__.py +5 -0
  237. agno/models/openrouter/openrouter.py +66 -0
  238. agno/models/perplexity/__init__.py +5 -0
  239. agno/models/perplexity/perplexity.py +187 -0
  240. agno/models/portkey/__init__.py +3 -0
  241. agno/models/portkey/portkey.py +81 -0
  242. agno/models/requesty/__init__.py +5 -0
  243. agno/models/requesty/requesty.py +52 -0
  244. agno/models/response.py +199 -0
  245. agno/models/sambanova/__init__.py +5 -0
  246. agno/models/sambanova/sambanova.py +28 -0
  247. agno/models/siliconflow/__init__.py +5 -0
  248. agno/models/siliconflow/siliconflow.py +25 -0
  249. agno/models/together/__init__.py +5 -0
  250. agno/models/together/together.py +25 -0
  251. agno/models/utils.py +266 -0
  252. agno/models/vercel/__init__.py +3 -0
  253. agno/models/vercel/v0.py +26 -0
  254. agno/models/vertexai/__init__.py +0 -0
  255. agno/models/vertexai/claude.py +70 -0
  256. agno/models/vllm/__init__.py +3 -0
  257. agno/models/vllm/vllm.py +78 -0
  258. agno/models/xai/__init__.py +3 -0
  259. agno/models/xai/xai.py +113 -0
  260. agno/os/__init__.py +3 -0
  261. agno/os/app.py +876 -0
  262. agno/os/auth.py +57 -0
  263. agno/os/config.py +104 -0
  264. agno/os/interfaces/__init__.py +1 -0
  265. agno/os/interfaces/a2a/__init__.py +3 -0
  266. agno/os/interfaces/a2a/a2a.py +42 -0
  267. agno/os/interfaces/a2a/router.py +250 -0
  268. agno/os/interfaces/a2a/utils.py +924 -0
  269. agno/os/interfaces/agui/__init__.py +3 -0
  270. agno/os/interfaces/agui/agui.py +47 -0
  271. agno/os/interfaces/agui/router.py +144 -0
  272. agno/os/interfaces/agui/utils.py +534 -0
  273. agno/os/interfaces/base.py +25 -0
  274. agno/os/interfaces/slack/__init__.py +3 -0
  275. agno/os/interfaces/slack/router.py +148 -0
  276. agno/os/interfaces/slack/security.py +30 -0
  277. agno/os/interfaces/slack/slack.py +47 -0
  278. agno/os/interfaces/whatsapp/__init__.py +3 -0
  279. agno/os/interfaces/whatsapp/router.py +211 -0
  280. agno/os/interfaces/whatsapp/security.py +53 -0
  281. agno/os/interfaces/whatsapp/whatsapp.py +36 -0
  282. agno/os/mcp.py +292 -0
  283. agno/os/middleware/__init__.py +7 -0
  284. agno/os/middleware/jwt.py +233 -0
  285. agno/os/router.py +1763 -0
  286. agno/os/routers/__init__.py +3 -0
  287. agno/os/routers/evals/__init__.py +3 -0
  288. agno/os/routers/evals/evals.py +430 -0
  289. agno/os/routers/evals/schemas.py +142 -0
  290. agno/os/routers/evals/utils.py +162 -0
  291. agno/os/routers/health.py +31 -0
  292. agno/os/routers/home.py +52 -0
  293. agno/os/routers/knowledge/__init__.py +3 -0
  294. agno/os/routers/knowledge/knowledge.py +997 -0
  295. agno/os/routers/knowledge/schemas.py +178 -0
  296. agno/os/routers/memory/__init__.py +3 -0
  297. agno/os/routers/memory/memory.py +515 -0
  298. agno/os/routers/memory/schemas.py +62 -0
  299. agno/os/routers/metrics/__init__.py +3 -0
  300. agno/os/routers/metrics/metrics.py +190 -0
  301. agno/os/routers/metrics/schemas.py +47 -0
  302. agno/os/routers/session/__init__.py +3 -0
  303. agno/os/routers/session/session.py +997 -0
  304. agno/os/schema.py +1055 -0
  305. agno/os/settings.py +43 -0
  306. agno/os/utils.py +630 -0
  307. agno/py.typed +0 -0
  308. agno/reasoning/__init__.py +0 -0
  309. agno/reasoning/anthropic.py +80 -0
  310. agno/reasoning/azure_ai_foundry.py +67 -0
  311. agno/reasoning/deepseek.py +63 -0
  312. agno/reasoning/default.py +97 -0
  313. agno/reasoning/gemini.py +73 -0
  314. agno/reasoning/groq.py +71 -0
  315. agno/reasoning/helpers.py +63 -0
  316. agno/reasoning/ollama.py +67 -0
  317. agno/reasoning/openai.py +86 -0
  318. agno/reasoning/step.py +31 -0
  319. agno/reasoning/vertexai.py +76 -0
  320. agno/run/__init__.py +6 -0
  321. agno/run/agent.py +787 -0
  322. agno/run/base.py +229 -0
  323. agno/run/cancel.py +81 -0
  324. agno/run/messages.py +32 -0
  325. agno/run/team.py +753 -0
  326. agno/run/workflow.py +708 -0
  327. agno/session/__init__.py +10 -0
  328. agno/session/agent.py +295 -0
  329. agno/session/summary.py +265 -0
  330. agno/session/team.py +392 -0
  331. agno/session/workflow.py +205 -0
  332. agno/team/__init__.py +37 -0
  333. agno/team/team.py +8793 -0
  334. agno/tools/__init__.py +10 -0
  335. agno/tools/agentql.py +120 -0
  336. agno/tools/airflow.py +69 -0
  337. agno/tools/api.py +122 -0
  338. agno/tools/apify.py +314 -0
  339. agno/tools/arxiv.py +127 -0
  340. agno/tools/aws_lambda.py +53 -0
  341. agno/tools/aws_ses.py +66 -0
  342. agno/tools/baidusearch.py +89 -0
  343. agno/tools/bitbucket.py +292 -0
  344. agno/tools/brandfetch.py +213 -0
  345. agno/tools/bravesearch.py +106 -0
  346. agno/tools/brightdata.py +367 -0
  347. agno/tools/browserbase.py +209 -0
  348. agno/tools/calcom.py +255 -0
  349. agno/tools/calculator.py +151 -0
  350. agno/tools/cartesia.py +187 -0
  351. agno/tools/clickup.py +244 -0
  352. agno/tools/confluence.py +240 -0
  353. agno/tools/crawl4ai.py +158 -0
  354. agno/tools/csv_toolkit.py +185 -0
  355. agno/tools/dalle.py +110 -0
  356. agno/tools/daytona.py +475 -0
  357. agno/tools/decorator.py +262 -0
  358. agno/tools/desi_vocal.py +108 -0
  359. agno/tools/discord.py +161 -0
  360. agno/tools/docker.py +716 -0
  361. agno/tools/duckdb.py +379 -0
  362. agno/tools/duckduckgo.py +91 -0
  363. agno/tools/e2b.py +703 -0
  364. agno/tools/eleven_labs.py +196 -0
  365. agno/tools/email.py +67 -0
  366. agno/tools/evm.py +129 -0
  367. agno/tools/exa.py +396 -0
  368. agno/tools/fal.py +127 -0
  369. agno/tools/file.py +240 -0
  370. agno/tools/file_generation.py +350 -0
  371. agno/tools/financial_datasets.py +288 -0
  372. agno/tools/firecrawl.py +143 -0
  373. agno/tools/function.py +1187 -0
  374. agno/tools/giphy.py +93 -0
  375. agno/tools/github.py +1760 -0
  376. agno/tools/gmail.py +922 -0
  377. agno/tools/google_bigquery.py +117 -0
  378. agno/tools/google_drive.py +270 -0
  379. agno/tools/google_maps.py +253 -0
  380. agno/tools/googlecalendar.py +674 -0
  381. agno/tools/googlesearch.py +98 -0
  382. agno/tools/googlesheets.py +377 -0
  383. agno/tools/hackernews.py +77 -0
  384. agno/tools/jina.py +101 -0
  385. agno/tools/jira.py +170 -0
  386. agno/tools/knowledge.py +218 -0
  387. agno/tools/linear.py +426 -0
  388. agno/tools/linkup.py +58 -0
  389. agno/tools/local_file_system.py +90 -0
  390. agno/tools/lumalab.py +183 -0
  391. agno/tools/mcp/__init__.py +10 -0
  392. agno/tools/mcp/mcp.py +331 -0
  393. agno/tools/mcp/multi_mcp.py +347 -0
  394. agno/tools/mcp/params.py +24 -0
  395. agno/tools/mcp_toolbox.py +284 -0
  396. agno/tools/mem0.py +193 -0
  397. agno/tools/memori.py +339 -0
  398. agno/tools/memory.py +419 -0
  399. agno/tools/mlx_transcribe.py +139 -0
  400. agno/tools/models/__init__.py +0 -0
  401. agno/tools/models/azure_openai.py +190 -0
  402. agno/tools/models/gemini.py +203 -0
  403. agno/tools/models/groq.py +158 -0
  404. agno/tools/models/morph.py +186 -0
  405. agno/tools/models/nebius.py +124 -0
  406. agno/tools/models_labs.py +195 -0
  407. agno/tools/moviepy_video.py +349 -0
  408. agno/tools/neo4j.py +134 -0
  409. agno/tools/newspaper.py +46 -0
  410. agno/tools/newspaper4k.py +93 -0
  411. agno/tools/notion.py +204 -0
  412. agno/tools/openai.py +202 -0
  413. agno/tools/openbb.py +160 -0
  414. agno/tools/opencv.py +321 -0
  415. agno/tools/openweather.py +233 -0
  416. agno/tools/oxylabs.py +385 -0
  417. agno/tools/pandas.py +102 -0
  418. agno/tools/parallel.py +314 -0
  419. agno/tools/postgres.py +257 -0
  420. agno/tools/pubmed.py +188 -0
  421. agno/tools/python.py +205 -0
  422. agno/tools/reasoning.py +283 -0
  423. agno/tools/reddit.py +467 -0
  424. agno/tools/replicate.py +117 -0
  425. agno/tools/resend.py +62 -0
  426. agno/tools/scrapegraph.py +222 -0
  427. agno/tools/searxng.py +152 -0
  428. agno/tools/serpapi.py +116 -0
  429. agno/tools/serper.py +255 -0
  430. agno/tools/shell.py +53 -0
  431. agno/tools/slack.py +136 -0
  432. agno/tools/sleep.py +20 -0
  433. agno/tools/spider.py +116 -0
  434. agno/tools/sql.py +154 -0
  435. agno/tools/streamlit/__init__.py +0 -0
  436. agno/tools/streamlit/components.py +113 -0
  437. agno/tools/tavily.py +254 -0
  438. agno/tools/telegram.py +48 -0
  439. agno/tools/todoist.py +218 -0
  440. agno/tools/tool_registry.py +1 -0
  441. agno/tools/toolkit.py +146 -0
  442. agno/tools/trafilatura.py +388 -0
  443. agno/tools/trello.py +274 -0
  444. agno/tools/twilio.py +186 -0
  445. agno/tools/user_control_flow.py +78 -0
  446. agno/tools/valyu.py +228 -0
  447. agno/tools/visualization.py +467 -0
  448. agno/tools/webbrowser.py +28 -0
  449. agno/tools/webex.py +76 -0
  450. agno/tools/website.py +54 -0
  451. agno/tools/webtools.py +45 -0
  452. agno/tools/whatsapp.py +286 -0
  453. agno/tools/wikipedia.py +63 -0
  454. agno/tools/workflow.py +278 -0
  455. agno/tools/x.py +335 -0
  456. agno/tools/yfinance.py +257 -0
  457. agno/tools/youtube.py +184 -0
  458. agno/tools/zendesk.py +82 -0
  459. agno/tools/zep.py +454 -0
  460. agno/tools/zoom.py +382 -0
  461. agno/utils/__init__.py +0 -0
  462. agno/utils/agent.py +820 -0
  463. agno/utils/audio.py +49 -0
  464. agno/utils/certs.py +27 -0
  465. agno/utils/code_execution.py +11 -0
  466. agno/utils/common.py +132 -0
  467. agno/utils/dttm.py +13 -0
  468. agno/utils/enum.py +22 -0
  469. agno/utils/env.py +11 -0
  470. agno/utils/events.py +696 -0
  471. agno/utils/format_str.py +16 -0
  472. agno/utils/functions.py +166 -0
  473. agno/utils/gemini.py +426 -0
  474. agno/utils/hooks.py +57 -0
  475. agno/utils/http.py +74 -0
  476. agno/utils/json_schema.py +234 -0
  477. agno/utils/knowledge.py +36 -0
  478. agno/utils/location.py +19 -0
  479. agno/utils/log.py +255 -0
  480. agno/utils/mcp.py +214 -0
  481. agno/utils/media.py +352 -0
  482. agno/utils/merge_dict.py +41 -0
  483. agno/utils/message.py +118 -0
  484. agno/utils/models/__init__.py +0 -0
  485. agno/utils/models/ai_foundry.py +43 -0
  486. agno/utils/models/claude.py +358 -0
  487. agno/utils/models/cohere.py +87 -0
  488. agno/utils/models/llama.py +78 -0
  489. agno/utils/models/mistral.py +98 -0
  490. agno/utils/models/openai_responses.py +140 -0
  491. agno/utils/models/schema_utils.py +153 -0
  492. agno/utils/models/watsonx.py +41 -0
  493. agno/utils/openai.py +257 -0
  494. agno/utils/pickle.py +32 -0
  495. agno/utils/pprint.py +178 -0
  496. agno/utils/print_response/__init__.py +0 -0
  497. agno/utils/print_response/agent.py +842 -0
  498. agno/utils/print_response/team.py +1724 -0
  499. agno/utils/print_response/workflow.py +1668 -0
  500. agno/utils/prompts.py +111 -0
  501. agno/utils/reasoning.py +108 -0
  502. agno/utils/response.py +163 -0
  503. agno/utils/response_iterator.py +17 -0
  504. agno/utils/safe_formatter.py +24 -0
  505. agno/utils/serialize.py +32 -0
  506. agno/utils/shell.py +22 -0
  507. agno/utils/streamlit.py +487 -0
  508. agno/utils/string.py +231 -0
  509. agno/utils/team.py +139 -0
  510. agno/utils/timer.py +41 -0
  511. agno/utils/tools.py +102 -0
  512. agno/utils/web.py +23 -0
  513. agno/utils/whatsapp.py +305 -0
  514. agno/utils/yaml_io.py +25 -0
  515. agno/vectordb/__init__.py +3 -0
  516. agno/vectordb/base.py +127 -0
  517. agno/vectordb/cassandra/__init__.py +5 -0
  518. agno/vectordb/cassandra/cassandra.py +501 -0
  519. agno/vectordb/cassandra/extra_param_mixin.py +11 -0
  520. agno/vectordb/cassandra/index.py +13 -0
  521. agno/vectordb/chroma/__init__.py +5 -0
  522. agno/vectordb/chroma/chromadb.py +929 -0
  523. agno/vectordb/clickhouse/__init__.py +9 -0
  524. agno/vectordb/clickhouse/clickhousedb.py +835 -0
  525. agno/vectordb/clickhouse/index.py +9 -0
  526. agno/vectordb/couchbase/__init__.py +3 -0
  527. agno/vectordb/couchbase/couchbase.py +1442 -0
  528. agno/vectordb/distance.py +7 -0
  529. agno/vectordb/lancedb/__init__.py +6 -0
  530. agno/vectordb/lancedb/lance_db.py +995 -0
  531. agno/vectordb/langchaindb/__init__.py +5 -0
  532. agno/vectordb/langchaindb/langchaindb.py +163 -0
  533. agno/vectordb/lightrag/__init__.py +5 -0
  534. agno/vectordb/lightrag/lightrag.py +388 -0
  535. agno/vectordb/llamaindex/__init__.py +3 -0
  536. agno/vectordb/llamaindex/llamaindexdb.py +166 -0
  537. agno/vectordb/milvus/__init__.py +4 -0
  538. agno/vectordb/milvus/milvus.py +1182 -0
  539. agno/vectordb/mongodb/__init__.py +9 -0
  540. agno/vectordb/mongodb/mongodb.py +1417 -0
  541. agno/vectordb/pgvector/__init__.py +12 -0
  542. agno/vectordb/pgvector/index.py +23 -0
  543. agno/vectordb/pgvector/pgvector.py +1462 -0
  544. agno/vectordb/pineconedb/__init__.py +5 -0
  545. agno/vectordb/pineconedb/pineconedb.py +747 -0
  546. agno/vectordb/qdrant/__init__.py +5 -0
  547. agno/vectordb/qdrant/qdrant.py +1134 -0
  548. agno/vectordb/redis/__init__.py +9 -0
  549. agno/vectordb/redis/redisdb.py +694 -0
  550. agno/vectordb/search.py +7 -0
  551. agno/vectordb/singlestore/__init__.py +10 -0
  552. agno/vectordb/singlestore/index.py +41 -0
  553. agno/vectordb/singlestore/singlestore.py +763 -0
  554. agno/vectordb/surrealdb/__init__.py +3 -0
  555. agno/vectordb/surrealdb/surrealdb.py +699 -0
  556. agno/vectordb/upstashdb/__init__.py +5 -0
  557. agno/vectordb/upstashdb/upstashdb.py +718 -0
  558. agno/vectordb/weaviate/__init__.py +8 -0
  559. agno/vectordb/weaviate/index.py +15 -0
  560. agno/vectordb/weaviate/weaviate.py +1005 -0
  561. agno/workflow/__init__.py +23 -0
  562. agno/workflow/agent.py +299 -0
  563. agno/workflow/condition.py +738 -0
  564. agno/workflow/loop.py +735 -0
  565. agno/workflow/parallel.py +824 -0
  566. agno/workflow/router.py +702 -0
  567. agno/workflow/step.py +1432 -0
  568. agno/workflow/steps.py +592 -0
  569. agno/workflow/types.py +520 -0
  570. agno/workflow/workflow.py +4321 -0
  571. agno-2.2.13.dist-info/METADATA +614 -0
  572. agno-2.2.13.dist-info/RECORD +575 -0
  573. agno-2.2.13.dist-info/WHEEL +5 -0
  574. agno-2.2.13.dist-info/licenses/LICENSE +201 -0
  575. agno-2.2.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1005 @@
1
+ import asyncio
2
+ import json
3
+ import uuid
4
+ from hashlib import md5
5
+ from os import getenv
6
+ from typing import Any, Dict, List, Optional, Tuple, Union
7
+
8
+ try:
9
+ from warnings import filterwarnings
10
+
11
+ import weaviate
12
+ from weaviate import WeaviateAsyncClient
13
+ from weaviate.classes.config import Configure, DataType, Property, Tokenization, VectorDistances
14
+ from weaviate.classes.init import Auth
15
+ from weaviate.classes.query import Filter
16
+
17
+ filterwarnings("ignore", category=ResourceWarning)
18
+ except ImportError:
19
+ raise ImportError("Weaviate is not installed. Install using 'pip install weaviate-client'.")
20
+
21
+ from agno.filters import FilterExpr
22
+ from agno.knowledge.document import Document
23
+ from agno.knowledge.embedder import Embedder
24
+ from agno.knowledge.reranker.base import Reranker
25
+ from agno.utils.log import log_debug, log_info, log_warning, logger
26
+ from agno.vectordb.base import VectorDb
27
+ from agno.vectordb.search import SearchType
28
+ from agno.vectordb.weaviate.index import Distance, VectorIndex
29
+
30
+
31
+ class Weaviate(VectorDb):
32
+ """
33
+ Weaviate class for managing vector operations with Weaviate vector database (v4 client).
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ # Connection/Client params
39
+ wcd_url: Optional[str] = None,
40
+ wcd_api_key: Optional[str] = None,
41
+ client: Optional[weaviate.WeaviateClient] = None,
42
+ local: bool = False,
43
+ # Collection params
44
+ collection: str = "default",
45
+ name: Optional[str] = None,
46
+ description: Optional[str] = None,
47
+ id: Optional[str] = None,
48
+ vector_index: VectorIndex = VectorIndex.HNSW,
49
+ distance: Distance = Distance.COSINE,
50
+ # Search/Embedding params
51
+ embedder: Optional[Embedder] = None,
52
+ search_type: SearchType = SearchType.vector,
53
+ reranker: Optional[Reranker] = None,
54
+ hybrid_search_alpha: float = 0.5,
55
+ ):
56
+ # Dynamic ID generation based on unique identifiers
57
+ if id is None:
58
+ from agno.utils.string import generate_id
59
+
60
+ connection_identifier = wcd_url or "local" if local else "default"
61
+ seed = f"{connection_identifier}#{collection}"
62
+ id = generate_id(seed)
63
+
64
+ # Initialize base class with name, description, and generated ID
65
+ super().__init__(id=id, name=name, description=description)
66
+
67
+ # Connection setup
68
+ self.wcd_url = wcd_url or getenv("WCD_URL")
69
+ self.wcd_api_key = wcd_api_key or getenv("WCD_API_KEY")
70
+ self.local = local
71
+ self.client = client
72
+ self.async_client = None
73
+
74
+ # Collection setup
75
+ self.collection = collection
76
+ self.vector_index = vector_index
77
+ self.distance = distance
78
+
79
+ # Embedder setup
80
+ if embedder is None:
81
+ from agno.knowledge.embedder.openai import OpenAIEmbedder
82
+
83
+ embedder = OpenAIEmbedder()
84
+ log_info("Embedder not provided, using OpenAIEmbedder as default.")
85
+ self.embedder: Embedder = embedder
86
+
87
+ # Search setup
88
+ self.search_type: SearchType = search_type
89
+ self.reranker: Optional[Reranker] = reranker
90
+ self.hybrid_search_alpha = hybrid_search_alpha
91
+
92
+ @staticmethod
93
+ def _get_doc_uuid(document: Document) -> Tuple[uuid.UUID, str]:
94
+ cleaned_content = document.content.replace("\x00", "\ufffd")
95
+ content_hash = md5(cleaned_content.encode()).hexdigest()
96
+ doc_uuid = uuid.UUID(hex=content_hash[:32])
97
+ return doc_uuid, cleaned_content
98
+
99
+ def get_client(self) -> weaviate.WeaviateClient:
100
+ """Initialize and return a Weaviate client instance.
101
+
102
+ Attempts to create a client using WCD (Weaviate Cloud Deployment) credentials if provided,
103
+ otherwise falls back to local connection. Maintains a singleton pattern by reusing
104
+ an existing client if already initialized.
105
+
106
+ Returns:
107
+ weaviate.WeaviateClient: An initialized Weaviate client instance.
108
+ """
109
+ if self.client is None:
110
+ if self.wcd_url and self.wcd_api_key and not self.local:
111
+ log_info("Initializing Weaviate Cloud client")
112
+ self.client = weaviate.connect_to_weaviate_cloud(
113
+ cluster_url=self.wcd_url, auth_credentials=Auth.api_key(self.wcd_api_key)
114
+ )
115
+ else:
116
+ log_info("Initializing local Weaviate client")
117
+ self.client = weaviate.connect_to_local()
118
+
119
+ if not self.client.is_connected(): # type: ignore
120
+ self.client.connect() # type: ignore
121
+
122
+ if not self.client.is_ready(): # type: ignore
123
+ raise Exception("Weaviate client is not ready")
124
+
125
+ return self.client
126
+
127
+ async def get_async_client(self) -> WeaviateAsyncClient:
128
+ """Get or create the async client."""
129
+ if self.async_client is None:
130
+ if self.wcd_url and self.wcd_api_key and not self.local:
131
+ log_info("Initializing Weaviate Cloud async client")
132
+ self.async_client = weaviate.use_async_with_weaviate_cloud(
133
+ cluster_url=self.wcd_url,
134
+ auth_credentials=Auth.api_key(self.wcd_api_key), # type: ignore
135
+ )
136
+ else:
137
+ log_info("Initializing local Weaviate async client")
138
+ self.async_client = weaviate.use_async_with_local() # type: ignore
139
+
140
+ if not self.async_client.is_connected(): # type: ignore
141
+ await self.async_client.connect() # type: ignore
142
+
143
+ if not await self.async_client.is_ready(): # type: ignore
144
+ raise ConnectionError("Weaviate async client is not ready")
145
+
146
+ return self.async_client # type: ignore
147
+
148
+ def create(self) -> None:
149
+ """Create the collection in Weaviate if it doesn't exist."""
150
+ if not self.exists():
151
+ log_debug(f"Creating collection '{self.collection}' in Weaviate.")
152
+ self.get_client().collections.create(
153
+ name=self.collection,
154
+ properties=[
155
+ Property(name="name", data_type=DataType.TEXT),
156
+ Property(name="content", data_type=DataType.TEXT, tokenization=Tokenization.LOWERCASE),
157
+ Property(name="meta_data", data_type=DataType.TEXT),
158
+ Property(name="content_id", data_type=DataType.TEXT),
159
+ Property(name="content_hash", data_type=DataType.TEXT),
160
+ ],
161
+ vectorizer_config=Configure.Vectorizer.none(),
162
+ vector_index_config=self.get_vector_index_config(self.vector_index, self.distance),
163
+ )
164
+ log_debug(f"Collection '{self.collection}' created in Weaviate.")
165
+
166
+ async def async_create(self) -> None:
167
+ client = await self.get_async_client()
168
+ try:
169
+ await client.collections.create(
170
+ name=self.collection,
171
+ properties=[
172
+ Property(name="name", data_type=DataType.TEXT),
173
+ Property(name="content", data_type=DataType.TEXT, tokenization=Tokenization.LOWERCASE),
174
+ Property(name="meta_data", data_type=DataType.TEXT),
175
+ Property(name="content_id", data_type=DataType.TEXT),
176
+ Property(name="content_hash", data_type=DataType.TEXT),
177
+ ],
178
+ vectorizer_config=Configure.Vectorizer.none(),
179
+ vector_index_config=self.get_vector_index_config(self.vector_index, self.distance),
180
+ )
181
+ log_debug(f"Collection '{self.collection}' created in Weaviate asynchronously.")
182
+ finally:
183
+ await client.close()
184
+
185
+ def content_hash_exists(self, content_hash: str) -> bool:
186
+ """Check if a document with the given content hash exists in the collection."""
187
+ collection = self.get_client().collections.get(self.collection)
188
+ result = collection.query.fetch_objects(
189
+ limit=1,
190
+ filters=Filter.by_property("content_hash").equal(content_hash),
191
+ )
192
+ return len(result.objects) > 0
193
+
194
+ def name_exists(self, name: str) -> bool:
195
+ """
196
+ Validate if a document with the given name exists in Weaviate.
197
+
198
+ Args:
199
+ name (str): The name of the document to check.
200
+
201
+ Returns:
202
+ bool: True if a document with the given name exists, False otherwise.
203
+ """
204
+ collection = self.get_client().collections.get(self.collection)
205
+ result = collection.query.fetch_objects(
206
+ limit=1,
207
+ filters=Filter.by_property("name").equal(name),
208
+ )
209
+ return len(result.objects) > 0
210
+
211
+ async def async_name_exists(self, name: str) -> bool:
212
+ """
213
+ Asynchronously validate if a document with the given name exists in Weaviate.
214
+
215
+ Args:
216
+ name (str): The name of the document to check.
217
+
218
+ Returns:
219
+ bool: True if a document with the given name exists, False otherwise.
220
+ """
221
+ client = await self.get_async_client()
222
+ try:
223
+ collection = client.collections.get(self.collection)
224
+ result = await collection.query.fetch_objects(
225
+ limit=1,
226
+ filters=Filter.by_property("name").equal(name),
227
+ )
228
+ return len(result.objects) > 0
229
+ finally:
230
+ await client.close()
231
+
232
+ def insert(self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
233
+ """
234
+ Insert documents into Weaviate.
235
+
236
+ Args:
237
+ documents (List[Document]): List of documents to insert
238
+ filters (Optional[Dict[str, Any]]): Filters to apply while inserting documents
239
+ """
240
+ log_debug(f"Inserting {len(documents)} documents into Weaviate.")
241
+ collection = self.get_client().collections.get(self.collection)
242
+
243
+ for document in documents:
244
+ document.embed(embedder=self.embedder)
245
+ if document.embedding is None:
246
+ logger.error(f"Document embedding is None: {document.name}")
247
+ continue
248
+
249
+ cleaned_content = document.content.replace("\x00", "\ufffd")
250
+ record_id = md5(cleaned_content.encode()).hexdigest()
251
+ doc_uuid = uuid.UUID(hex=record_id[:32])
252
+
253
+ # Merge filters with metadata
254
+ meta_data = document.meta_data or {}
255
+ if filters:
256
+ meta_data.update(filters)
257
+
258
+ # Serialize meta_data to JSON string
259
+ meta_data_str = json.dumps(meta_data) if meta_data else None
260
+
261
+ collection.data.insert(
262
+ properties={
263
+ "name": document.name,
264
+ "content": cleaned_content,
265
+ "meta_data": meta_data_str,
266
+ "content_id": document.content_id,
267
+ "content_hash": content_hash,
268
+ },
269
+ vector=document.embedding,
270
+ uuid=doc_uuid,
271
+ )
272
+ log_debug(f"Inserted document: {document.name} ({meta_data})")
273
+
274
+ async def async_insert(
275
+ self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None
276
+ ) -> None:
277
+ """
278
+ Insert documents into Weaviate asynchronously.
279
+
280
+ Args:
281
+ documents (List[Document]): List of documents to insert
282
+ filters (Optional[Dict[str, Any]]): Filters to apply while inserting documents
283
+ """
284
+ log_debug(f"Inserting {len(documents)} documents into Weaviate asynchronously.")
285
+ if not documents:
286
+ return
287
+
288
+ # Apply batch embedding logic
289
+ if self.embedder.enable_batch and hasattr(self.embedder, "async_get_embeddings_batch_and_usage"):
290
+ # Use batch embedding when enabled and supported
291
+ try:
292
+ # Extract content from all documents
293
+ doc_contents = [doc.content for doc in documents]
294
+
295
+ # Get batch embeddings and usage
296
+ embeddings, usages = await self.embedder.async_get_embeddings_batch_and_usage(doc_contents)
297
+
298
+ # Process documents with pre-computed embeddings
299
+ for j, doc in enumerate(documents):
300
+ try:
301
+ if j < len(embeddings):
302
+ doc.embedding = embeddings[j]
303
+ doc.usage = usages[j] if j < len(usages) else None
304
+ except Exception as e:
305
+ logger.error(f"Error assigning batch embedding to document '{doc.name}': {e}")
306
+
307
+ except Exception as e:
308
+ # Check if this is a rate limit error - don't fall back as it would make things worse
309
+ error_str = str(e).lower()
310
+ is_rate_limit = any(
311
+ phrase in error_str
312
+ for phrase in ["rate limit", "too many requests", "429", "trial key", "api calls / minute"]
313
+ )
314
+
315
+ if is_rate_limit:
316
+ logger.error(f"Rate limit detected during batch embedding. {e}")
317
+ raise e
318
+ else:
319
+ logger.warning(f"Async batch embedding failed, falling back to individual embeddings: {e}")
320
+ # Fall back to individual embedding
321
+ embed_tasks = [doc.async_embed(embedder=self.embedder) for doc in documents]
322
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
323
+ else:
324
+ # Use individual embedding
325
+ embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
326
+ await asyncio.gather(*embed_tasks, return_exceptions=True)
327
+
328
+ client = await self.get_async_client()
329
+ try:
330
+ collection = client.collections.get(self.collection)
331
+
332
+ # Process documents first
333
+ for document in documents:
334
+ try:
335
+ if document.embedding is None:
336
+ logger.error(f"Document embedding is None: {document.name}")
337
+ continue
338
+
339
+ # Clean content and generate UUID
340
+ cleaned_content = document.content.replace("\x00", "\ufffd")
341
+ record_id = md5(cleaned_content.encode()).hexdigest()
342
+ doc_uuid = uuid.UUID(hex=record_id[:32])
343
+
344
+ # Serialize meta_data to JSON string
345
+ meta_data_str = json.dumps(document.meta_data) if document.meta_data else None
346
+
347
+ # Insert properties and vector separately
348
+ properties = {
349
+ "name": document.name,
350
+ "content": cleaned_content,
351
+ "meta_data": meta_data_str,
352
+ "content_id": document.content_id,
353
+ "content_hash": content_hash,
354
+ }
355
+
356
+ # Use the API correctly - properties, vector and uuid are separate parameters
357
+ await collection.data.insert(properties=properties, vector=document.embedding, uuid=doc_uuid)
358
+
359
+ log_debug(f"Inserted document asynchronously: {document.name}")
360
+
361
+ except Exception as e:
362
+ logger.error(f"Error inserting document {document.name}: {str(e)}")
363
+ finally:
364
+ await client.close()
365
+
366
+ def upsert(self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
367
+ """
368
+ Upsert documents into Weaviate.
369
+
370
+ Args:
371
+ documents (List[Document]): List of documents to upsert
372
+ filters (Optional[Dict[str, Any]]): Filters to apply while upserting
373
+ """
374
+ log_debug(f"Upserting {len(documents)} documents into Weaviate.")
375
+ if self.content_hash_exists(content_hash):
376
+ self._delete_by_content_hash(content_hash)
377
+ self.insert(content_hash=content_hash, documents=documents, filters=filters)
378
+
379
+ async def async_upsert(
380
+ self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None
381
+ ) -> None:
382
+ """
383
+ Upsert documents into Weaviate asynchronously.
384
+ When documents with the same ID already exist, they will be replaced.
385
+ Otherwise, new documents will be created.
386
+
387
+ Args:
388
+ documents (List[Document]): List of documents to upsert
389
+ filters (Optional[Dict[str, Any]]): Filters to apply while upserting
390
+ """
391
+ if self.content_hash_exists(content_hash):
392
+ self._delete_by_content_hash(content_hash)
393
+ await self.async_insert(content_hash=content_hash, documents=documents, filters=filters)
394
+ return
395
+
396
+ def search(
397
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
398
+ ) -> List[Document]:
399
+ """
400
+ Perform a search based on the configured search type.
401
+
402
+ Args:
403
+ query (str): The search query.
404
+ limit (int): Maximum number of results to return.
405
+ filters (Optional[Dict[str, Any]]): Filters to apply to the search.
406
+
407
+ Returns:
408
+ List[Document]: List of matching documents.
409
+ """
410
+ if isinstance(filters, List):
411
+ log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
412
+ filters = None
413
+ if self.search_type == SearchType.vector:
414
+ return self.vector_search(query, limit, filters)
415
+ elif self.search_type == SearchType.keyword:
416
+ return self.keyword_search(query, limit, filters)
417
+ elif self.search_type == SearchType.hybrid:
418
+ return self.hybrid_search(query, limit, filters)
419
+ else:
420
+ logger.error(f"Invalid search type '{self.search_type}'.")
421
+ return []
422
+
423
+ async def async_search(
424
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
425
+ ) -> List[Document]:
426
+ """
427
+ Perform a search based on the configured search type asynchronously.
428
+
429
+ Args:
430
+ query (str): The search query.
431
+ limit (int): Maximum number of results to return.
432
+ filters (Optional[Dict[str, Any]]): Filters to apply to the search.
433
+
434
+ Returns:
435
+ List[Document]: List of matching documents.
436
+ """
437
+ if isinstance(filters, List):
438
+ log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
439
+ filters = None
440
+ if self.search_type == SearchType.vector:
441
+ return await self.async_vector_search(query, limit, filters)
442
+ elif self.search_type == SearchType.keyword:
443
+ return await self.async_keyword_search(query, limit, filters)
444
+ elif self.search_type == SearchType.hybrid:
445
+ return await self.async_hybrid_search(query, limit, filters)
446
+ else:
447
+ logger.error(f"Invalid search type '{self.search_type}'.")
448
+ return []
449
+
450
+ def vector_search(
451
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
452
+ ) -> List[Document]:
453
+ try:
454
+ query_embedding = self.embedder.get_embedding(query)
455
+ if query_embedding is None:
456
+ logger.error(f"Error getting embedding for query: {query}")
457
+ return []
458
+
459
+ collection = self.get_client().collections.get(self.collection)
460
+ filter_expr = self._build_filter_expression(filters)
461
+
462
+ response = collection.query.near_vector(
463
+ near_vector=query_embedding,
464
+ limit=limit,
465
+ return_properties=["name", "content", "meta_data", "content_id"],
466
+ include_vector=True,
467
+ filters=filter_expr,
468
+ )
469
+
470
+ search_results: List[Document] = self.get_search_results(response)
471
+
472
+ if self.reranker:
473
+ search_results = self.reranker.rerank(query=query, documents=search_results)
474
+
475
+ log_info(f"Found {len(search_results)} documents")
476
+
477
+ return search_results
478
+
479
+ except Exception as e:
480
+ logger.error(f"Error searching for documents: {e}")
481
+ return []
482
+
483
+ finally:
484
+ self.get_client().close()
485
+
486
+ async def async_vector_search(
487
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
488
+ ) -> List[Document]:
489
+ """
490
+ Perform a vector search in Weaviate asynchronously.
491
+
492
+ Args:
493
+ query (str): The search query.
494
+ limit (int): Maximum number of results to return.
495
+
496
+ Returns:
497
+ List[Document]: List of matching documents.
498
+ """
499
+ query_embedding = self.embedder.get_embedding(query)
500
+ if query_embedding is None:
501
+ logger.error(f"Error getting embedding for query: {query}")
502
+ return []
503
+
504
+ search_results = []
505
+ client = await self.get_async_client()
506
+ try:
507
+ collection = client.collections.get(self.collection)
508
+ filter_expr = self._build_filter_expression(filters)
509
+
510
+ response = await collection.query.near_vector(
511
+ near_vector=query_embedding,
512
+ limit=limit,
513
+ return_properties=["name", "content", "meta_data", "content_id"],
514
+ include_vector=True,
515
+ filters=filter_expr,
516
+ )
517
+
518
+ search_results = self.get_search_results(response)
519
+
520
+ if self.reranker:
521
+ search_results = self.reranker.rerank(query=query, documents=search_results)
522
+
523
+ log_info(f"Found {len(search_results)} documents")
524
+
525
+ await client.close()
526
+ return search_results
527
+
528
+ except Exception as e:
529
+ logger.error(f"Error searching for documents: {e}")
530
+ return []
531
+
532
+ def keyword_search(
533
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
534
+ ) -> List[Document]:
535
+ try:
536
+ collection = self.get_client().collections.get(self.collection)
537
+ filter_expr = self._build_filter_expression(filters)
538
+
539
+ response = collection.query.bm25(
540
+ query=query,
541
+ query_properties=["content"],
542
+ limit=limit,
543
+ return_properties=["name", "content", "meta_data", "content_id"],
544
+ include_vector=True,
545
+ filters=filter_expr,
546
+ )
547
+
548
+ search_results: List[Document] = self.get_search_results(response)
549
+
550
+ if self.reranker:
551
+ search_results = self.reranker.rerank(query=query, documents=search_results)
552
+
553
+ log_info(f"Found {len(search_results)} documents")
554
+
555
+ return search_results
556
+
557
+ except Exception as e:
558
+ logger.error(f"Error searching for documents: {e}")
559
+ return []
560
+
561
+ finally:
562
+ self.get_client().close()
563
+
564
+ async def async_keyword_search(
565
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
566
+ ) -> List[Document]:
567
+ """
568
+ Perform a keyword search in Weaviate asynchronously.
569
+
570
+ Args:
571
+ query (str): The search query.
572
+ limit (int): Maximum number of results to return.
573
+
574
+ Returns:
575
+ List[Document]: List of matching documents.
576
+ """
577
+ search_results = []
578
+ client = await self.get_async_client()
579
+ try:
580
+ collection = client.collections.get(self.collection)
581
+
582
+ filter_expr = self._build_filter_expression(filters)
583
+ response = await collection.query.bm25(
584
+ query=query,
585
+ query_properties=["content"],
586
+ limit=limit,
587
+ return_properties=["name", "content", "meta_data", "content_id"],
588
+ include_vector=True,
589
+ filters=filter_expr,
590
+ )
591
+
592
+ search_results = self.get_search_results(response)
593
+
594
+ if self.reranker:
595
+ search_results = self.reranker.rerank(query=query, documents=search_results)
596
+
597
+ log_info(f"Found {len(search_results)} documents")
598
+
599
+ await client.close()
600
+ return search_results
601
+
602
+ except Exception as e:
603
+ logger.error(f"Error searching for documents: {e}")
604
+ return []
605
+
606
+ def hybrid_search(
607
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
608
+ ) -> List[Document]:
609
+ try:
610
+ query_embedding = self.embedder.get_embedding(query)
611
+ if query_embedding is None:
612
+ logger.error(f"Error getting embedding for query: {query}")
613
+ return []
614
+
615
+ collection = self.get_client().collections.get(self.collection)
616
+ filter_expr = self._build_filter_expression(filters)
617
+
618
+ response = collection.query.hybrid(
619
+ query=query,
620
+ vector=query_embedding,
621
+ limit=limit,
622
+ return_properties=["name", "content", "meta_data", "content_id"],
623
+ include_vector=True,
624
+ query_properties=["content"],
625
+ alpha=self.hybrid_search_alpha,
626
+ filters=filter_expr,
627
+ )
628
+
629
+ search_results: List[Document] = self.get_search_results(response)
630
+
631
+ if self.reranker:
632
+ search_results = self.reranker.rerank(query=query, documents=search_results)
633
+
634
+ log_info(f"Found {len(search_results)} documents")
635
+
636
+ return search_results
637
+
638
+ except Exception as e:
639
+ logger.error(f"Error searching for documents: {e}")
640
+ return []
641
+
642
+ finally:
643
+ self.get_client().close()
644
+
645
+ async def async_hybrid_search(
646
+ self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
647
+ ) -> List[Document]:
648
+ """
649
+ Perform a hybrid search combining vector and keyword search in Weaviate asynchronously.
650
+
651
+ Args:
652
+ query (str): The keyword query.
653
+ limit (int): Maximum number of results to return.
654
+
655
+ Returns:
656
+ List[Document]: List of matching documents.
657
+ """
658
+ query_embedding = self.embedder.get_embedding(query)
659
+ if query_embedding is None:
660
+ logger.error(f"Error getting embedding for query: {query}")
661
+ return []
662
+
663
+ search_results = []
664
+ client = await self.get_async_client()
665
+ try:
666
+ collection = client.collections.get(self.collection)
667
+
668
+ filter_expr = self._build_filter_expression(filters)
669
+ response = await collection.query.hybrid(
670
+ query=query,
671
+ vector=query_embedding,
672
+ limit=limit,
673
+ return_properties=["name", "content", "meta_data", "content_id"],
674
+ include_vector=True,
675
+ query_properties=["content"],
676
+ alpha=self.hybrid_search_alpha,
677
+ filters=filter_expr,
678
+ )
679
+
680
+ search_results = self.get_search_results(response)
681
+
682
+ if self.reranker:
683
+ search_results = self.reranker.rerank(query=query, documents=search_results)
684
+
685
+ log_info(f"Found {len(search_results)} documents")
686
+
687
+ await client.close()
688
+ return search_results
689
+
690
+ except Exception as e:
691
+ logger.error(f"Error searching for documents: {e}")
692
+ return []
693
+
694
+ def exists(self) -> bool:
695
+ """Check if the collection exists in Weaviate."""
696
+ return self.get_client().collections.exists(self.collection)
697
+
698
+ async def async_exists(self) -> bool:
699
+ """Check if the collection exists in Weaviate asynchronously."""
700
+ client = await self.get_async_client()
701
+ try:
702
+ return await client.collections.exists(self.collection)
703
+ finally:
704
+ await client.close()
705
+
706
+ def drop(self) -> None:
707
+ """Delete the Weaviate collection."""
708
+ if self.exists():
709
+ log_debug(f"Deleting collection '{self.collection}' from Weaviate.")
710
+ self.get_client().collections.delete(self.collection)
711
+
712
+ async def async_drop(self) -> None:
713
+ """Delete the Weaviate collection asynchronously."""
714
+ if await self.async_exists():
715
+ log_debug(f"Deleting collection '{self.collection}' from Weaviate asynchronously.")
716
+ client = await self.get_async_client()
717
+ try:
718
+ await client.collections.delete(self.collection)
719
+ finally:
720
+ await client.close()
721
+
722
+ def optimize(self) -> None:
723
+ """Optimize the vector database (e.g., rebuild indexes)."""
724
+ pass
725
+
726
+ def delete(self) -> bool:
727
+ """Delete all records from the database."""
728
+ self.drop()
729
+ return True
730
+
731
+ def delete_by_id(self, id: str) -> bool:
732
+ """Delete document by ID."""
733
+ try:
734
+ try:
735
+ doc_uuid = uuid.UUID(hex=id[:32]) if len(id) == 32 else uuid.UUID(id)
736
+ except ValueError:
737
+ log_info(f"Invalid UUID format for ID '{id}' - treating as non-existent")
738
+ return True
739
+
740
+ collection = self.get_client().collections.get(self.collection)
741
+
742
+ if not collection.data.exists(doc_uuid):
743
+ log_info(f"Document with ID {id} does not exist")
744
+ return True
745
+
746
+ collection.data.delete_by_id(doc_uuid)
747
+ log_info(f"Deleted document with ID '{id}' from collection '{self.collection}'.")
748
+ return True
749
+ except Exception as e:
750
+ logger.error(f"Error deleting document by ID '{id}': {e}")
751
+ return False
752
+
753
+ def delete_by_name(self, name: str) -> bool:
754
+ """Delete content by name using direct filter deletion."""
755
+ try:
756
+ collection = self.get_client().collections.get(self.collection)
757
+
758
+ collection.data.delete_many(where=Filter.by_property("name").equal(name))
759
+
760
+ log_info(f"Deleted documents with name '{name}' from collection '{self.collection}'.")
761
+ return True
762
+
763
+ except Exception as e:
764
+ logger.error(f"Error deleting documents by name '{name}': {e}")
765
+ return False
766
+
767
+ def delete_by_metadata(self, metadata: Dict[str, Any]) -> bool:
768
+ """Delete content by metadata using direct filter deletion."""
769
+ try:
770
+ collection = self.get_client().collections.get(self.collection)
771
+
772
+ # Build filter for metadata search
773
+ filter_expr = self._build_filter_expression(metadata)
774
+ if filter_expr is None:
775
+ log_info(f"No valid filter could be built for metadata: {metadata}")
776
+ return False
777
+
778
+ collection.data.delete_many(where=filter_expr)
779
+
780
+ log_info(f"Deleted documents with metadata '{metadata}' from collection '{self.collection}'.")
781
+ return True
782
+
783
+ except Exception as e:
784
+ logger.error(f"Error deleting documents by metadata '{metadata}': {e}")
785
+ return False
786
+
787
+ def delete_by_content_id(self, content_id: str) -> bool:
788
+ """Delete content by content ID using direct filter deletion."""
789
+ try:
790
+ collection = self.get_client().collections.get(self.collection)
791
+
792
+ collection.data.delete_many(where=Filter.by_property("content_id").equal(content_id))
793
+
794
+ log_info(f"Deleted documents with content_id '{content_id}' from collection '{self.collection}'.")
795
+ return True
796
+
797
+ except Exception as e:
798
+ logger.error(f"Error deleting documents by content_id '{content_id}': {e}")
799
+ return False
800
+
801
+ def delete_by_content_hash(self, content_hash: str) -> bool:
802
+ """Delete content by content hash using direct filter deletion."""
803
+ try:
804
+ collection = self.get_client().collections.get(self.collection)
805
+ collection.data.delete_many(where=Filter.by_property("content_hash").equal(content_hash))
806
+ return True
807
+ except Exception as e:
808
+ logger.error(f"Error deleting documents by content_hash '{content_hash}': {e}")
809
+ return False
810
+
811
+ def get_vector_index_config(self, index_type: VectorIndex, distance_metric: Distance):
812
+ """
813
+ Returns the appropriate vector index configuration with the specified distance metric.
814
+
815
+ Args:
816
+ index_type (VectorIndex): Type of vector index (HNSW, FLAT, DYNAMIC).
817
+ distance_metric (Distance): Distance metric (COSINE, DOT, etc).
818
+
819
+ Returns:
820
+ Configure.VectorIndex: The configured vector index instance.
821
+ """
822
+ # Get the Weaviate distance metric
823
+ distance = getattr(VectorDistances, distance_metric.name)
824
+
825
+ # Define vector index configurations based on enum value
826
+ configs = {
827
+ VectorIndex.HNSW: Configure.VectorIndex.hnsw(distance_metric=distance),
828
+ VectorIndex.FLAT: Configure.VectorIndex.flat(distance_metric=distance),
829
+ VectorIndex.DYNAMIC: Configure.VectorIndex.dynamic(distance_metric=distance),
830
+ }
831
+
832
+ return configs[index_type]
833
+
834
+ def get_search_results(self, response: Any) -> List[Document]:
835
+ """
836
+ Create search results from the Weaviate response.
837
+
838
+ Args:
839
+ response (Any): The Weaviate response object.
840
+
841
+ Returns:
842
+ List[Document]: List of matching documents.
843
+ """
844
+ search_results: List[Document] = []
845
+ for obj in response.objects:
846
+ properties = obj.properties
847
+ meta_data = json.loads(properties["meta_data"]) if properties.get("meta_data") else {}
848
+ embedding = obj.vector["default"] if isinstance(obj.vector, dict) else obj.vector
849
+
850
+ search_results.append(
851
+ Document(
852
+ name=properties.get("name"),
853
+ meta_data=meta_data,
854
+ content=properties.get("content", ""),
855
+ embedder=self.embedder,
856
+ embedding=embedding,
857
+ content_id=properties.get("content_id"),
858
+ )
859
+ )
860
+
861
+ return search_results
862
+
863
+ def upsert_available(self) -> bool:
864
+ """Indicate that upsert functionality is available."""
865
+ return True
866
+
867
+ def _build_filter_expression(self, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]]):
868
+ """
869
+ Build a filter expression for Weaviate queries.
870
+
871
+ Args:
872
+ filters (Optional[Dict[str, Any]]): Dictionary of filters to apply.
873
+
874
+ Returns:
875
+ Optional[Filter]: The constructed filter expression, or None if no filters provided.
876
+ """
877
+ if not filters:
878
+ return None
879
+ if isinstance(filters, List):
880
+ log_warning("Filters Expressions are not supported in Weaviate. No filters will be applied.")
881
+ return None
882
+ try:
883
+ # Create a filter for each key-value pair
884
+ filter_conditions = []
885
+ for key, value in filters.items():
886
+ # Create a pattern to match in the JSON string
887
+ if isinstance(value, (list, tuple)):
888
+ # For list values
889
+ pattern = f'"{key}": {json.dumps(value)}'
890
+ else:
891
+ # For single values
892
+ pattern = f'"{key}": "{value}"'
893
+
894
+ # Add the filter condition using like operator
895
+ filter_conditions.append(Filter.by_property("meta_data").like(f"*{pattern}*"))
896
+
897
+ # If we have multiple conditions, combine them
898
+ if len(filter_conditions) > 1:
899
+ # Use the first condition as base and chain the rest
900
+ filter_expr = filter_conditions[0]
901
+ for condition in filter_conditions[1:]:
902
+ filter_expr = filter_expr & condition
903
+ return filter_expr
904
+ elif filter_conditions:
905
+ return filter_conditions[0]
906
+
907
+ except Exception as e:
908
+ logger.error(f"Error building filter expression: {e}")
909
+ return None
910
+
911
+ return None
912
+
913
+ def id_exists(self, id: str) -> bool:
914
+ """Check if a document with the given ID exists in the collection.
915
+
916
+ Args:
917
+ id (str): The document ID to check.
918
+
919
+ Returns:
920
+ bool: True if the document exists, False otherwise.
921
+ """
922
+ try:
923
+ doc_uuid = uuid.UUID(hex=id[:32]) if len(id) == 32 else uuid.UUID(id)
924
+ collection = self.get_client().collections.get(self.collection)
925
+ return collection.data.exists(doc_uuid)
926
+ except ValueError:
927
+ log_info(f"Invalid UUID format for ID '{id}' - treating as non-existent")
928
+ return False
929
+ except Exception as e:
930
+ logger.error(f"Error checking if ID '{id}' exists: {e}")
931
+ return False
932
+
933
+ def update_metadata(self, content_id: str, metadata: Dict[str, Any]) -> None:
934
+ """
935
+ Update the metadata for documents with the given content_id.
936
+
937
+ Args:
938
+ content_id (str): The content ID to update
939
+ metadata (Dict[str, Any]): The metadata to update
940
+ """
941
+ try:
942
+ weaviate_client = self.get_client()
943
+ collection = weaviate_client.collections.get(self.collection)
944
+
945
+ # Query for objects with the given content_id
946
+ query_result = collection.query.fetch_objects( # type: ignore
947
+ where=Filter.by_property("content_id").equal(content_id),
948
+ limit=1000, # Get all matching objects
949
+ )
950
+
951
+ if not query_result.objects:
952
+ logger.debug(f"No documents found with content_id: {content_id}")
953
+ return
954
+
955
+ # Update each matching object
956
+ updated_count = 0
957
+ for obj in query_result.objects:
958
+ # Get current properties
959
+ current_properties = obj.properties or {}
960
+
961
+ # Merge existing metadata with new metadata
962
+ updated_properties = current_properties.copy()
963
+
964
+ # Handle nested metadata updates
965
+ if "meta_data" in updated_properties and isinstance(updated_properties["meta_data"], dict):
966
+ updated_properties["meta_data"].update(metadata)
967
+ else:
968
+ # If no existing meta_data or it's not a dict, set it directly
969
+ updated_properties["meta_data"] = metadata
970
+
971
+ if "filters" in updated_properties and isinstance(updated_properties["filters"], dict):
972
+ updated_properties["filters"].update(metadata)
973
+ else:
974
+ updated_properties["filters"] = metadata
975
+
976
+ # Update the object
977
+ collection.data.update(uuid=obj.uuid, properties=updated_properties)
978
+ updated_count += 1
979
+
980
+ logger.debug(f"Updated metadata for {updated_count} documents with content_id: {content_id}")
981
+
982
+ except Exception as e:
983
+ logger.error(f"Error updating metadata for content_id '{content_id}': {e}")
984
+ raise
985
+
986
+ def _delete_by_content_hash(self, content_hash: str) -> bool:
987
+ """Delete documents by content hash using direct filter deletion."""
988
+ try:
989
+ collection = self.get_client().collections.get(self.collection)
990
+
991
+ # Build filter for content_hash search
992
+ filter_expr = Filter.by_property("content_hash").equal(content_hash)
993
+
994
+ collection.data.delete_many(where=filter_expr)
995
+
996
+ log_info(f"Deleted documents with content_hash '{content_hash}' from collection '{self.collection}'.")
997
+ return True
998
+
999
+ except Exception as e:
1000
+ logger.error(f"Error deleting documents by content_hash '{content_hash}': {e}")
1001
+ return False
1002
+
1003
+ def get_supported_search_types(self) -> List[str]:
1004
+ """Get the supported search types for this vector database."""
1005
+ return [SearchType.vector, SearchType.keyword, SearchType.hybrid]