agno 2.2.13__py3-none-any.whl → 2.4.3__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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
@@ -260,39 +260,6 @@ class LanceDb(VectorDb):
260
260
  tbl = self.connection.create_table(name=self.table_name, schema=schema, mode="overwrite", exist_ok=True) # type: ignore
261
261
  return tbl # type: ignore
262
262
 
263
- def doc_exists(self, document: Document) -> bool:
264
- """
265
- Validating if the document exists or not
266
-
267
- Args:
268
- document (Document): Document to validate
269
- """
270
- try:
271
- if self.table is not None:
272
- cleaned_content = document.content.replace("\x00", "\ufffd")
273
- doc_id = md5(cleaned_content.encode()).hexdigest()
274
- result = self.table.search().where(f"{self._id}='{doc_id}'").to_arrow()
275
- return len(result) > 0
276
- except Exception:
277
- # Search sometimes fails with stale cache data, it means the doc doesn't exist
278
- return False
279
-
280
- return False
281
-
282
- async def async_doc_exists(self, document: Document) -> bool:
283
- """
284
- Asynchronously validate if the document exists
285
-
286
- Args:
287
- document (Document): Document to validate
288
-
289
- Returns:
290
- bool: True if document exists, False otherwise
291
- """
292
- if self.connection:
293
- self.table = self.connection.open_table(name=self.table_name)
294
- return self.doc_exists(document)
295
-
296
263
  def insert(self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None) -> None:
297
264
  """
298
265
  Insert documents into the database.
@@ -309,18 +276,20 @@ class LanceDb(VectorDb):
309
276
  data = []
310
277
 
311
278
  for document in documents:
312
- if self.doc_exists(document):
313
- continue
314
-
315
279
  # Add filters to document metadata if provided
316
280
  if filters:
317
281
  meta_data = document.meta_data.copy() if document.meta_data else {}
318
282
  meta_data.update(filters)
319
283
  document.meta_data = meta_data
320
284
 
321
- document.embed(embedder=self.embedder)
285
+ # Only embed if the document doesn't already have an embedding
286
+ # This prevents duplicate embedding when called from async_insert or async_upsert
287
+ if document.embedding is None:
288
+ document.embed(embedder=self.embedder)
322
289
  cleaned_content = document.content.replace("\x00", "\ufffd")
323
- doc_id = str(md5(cleaned_content.encode()).hexdigest())
290
+ # Include content_hash in ID to ensure uniqueness across different content hashes
291
+ base_id = document.id or md5(cleaned_content.encode()).hexdigest()
292
+ doc_id = str(md5(f"{base_id}_{content_hash}".encode()).hexdigest())
324
293
  payload = {
325
294
  "name": document.name,
326
295
  "meta_data": document.meta_data,
@@ -109,7 +109,7 @@ class LightRag(VectorDb):
109
109
  async with httpx.AsyncClient(timeout=30.0) as client:
110
110
  response = await client.post(
111
111
  f"{self.server_url}/query",
112
- json={"query": query, "mode": "hybrid"},
112
+ json={"query": query, "mode": "hybrid", "include_references": True},
113
113
  headers=self._get_headers(),
114
114
  )
115
115
 
@@ -322,7 +322,7 @@ class LightRag(VectorDb):
322
322
  async with httpx.AsyncClient(timeout=30.0) as client:
323
323
  response = await client.post(
324
324
  f"{self.server_url}/query",
325
- json={"query": query, "mode": "hybrid"},
325
+ json={"query": query, "mode": "hybrid", "include_references": True},
326
326
  headers=self._get_headers(),
327
327
  )
328
328
 
@@ -349,10 +349,11 @@ class LightRag(VectorDb):
349
349
  # LightRAG server returns a dict with 'response' key, but we expect a list of documents
350
350
  # Convert the response to the expected format
351
351
  if isinstance(result, dict) and "response" in result:
352
- # Wrap the response in a Document object
353
- return [
354
- Document(content=result["response"], meta_data={"source": "lightrag", "query": query, "mode": mode})
355
- ]
352
+ meta_data = {"source": "lightrag", "query": query, "mode": mode}
353
+ # Preserve references from LightRAG response for document citations
354
+ if "references" in result:
355
+ meta_data["references"] = result["references"]
356
+ return [Document(content=result["response"], meta_data=meta_data)]
356
357
  elif isinstance(result, list):
357
358
  # Convert list items to Document objects
358
359
  documents = []
@@ -89,7 +89,7 @@ class Milvus(VectorDb):
89
89
  from agno.knowledge.embedder.openai import OpenAIEmbedder
90
90
 
91
91
  embedder = OpenAIEmbedder()
92
- log_info("Embedder not provided, using OpenAIEmbedder as default.")
92
+ log_debug("Embedder not provided, using OpenAIEmbedder as default.")
93
93
  self.embedder: Embedder = embedder
94
94
  self.dimensions: Optional[int] = self.embedder.dimensions
95
95
 
@@ -229,7 +229,9 @@ class Milvus(VectorDb):
229
229
  """
230
230
 
231
231
  cleaned_content = document.content.replace("\x00", "\ufffd")
232
- doc_id = md5(cleaned_content.encode()).hexdigest()
232
+ # Include content_hash in ID to ensure uniqueness across different content hashes
233
+ base_id = document.id or md5(cleaned_content.encode()).hexdigest()
234
+ doc_id = md5(f"{base_id}_{content_hash}".encode()).hexdigest()
233
235
 
234
236
  # Convert dictionary fields to JSON strings
235
237
  meta_data_str = json.dumps(document.meta_data) if document.meta_data else "{}"
@@ -239,7 +241,7 @@ class Milvus(VectorDb):
239
241
  "id": doc_id,
240
242
  "text": cleaned_content,
241
243
  "name": document.name,
242
- "content_id": document.content_id,
244
+ "content_id": document.content_id or "",
243
245
  "meta_data": meta_data_str,
244
246
  "content": cleaned_content,
245
247
  "usage": usage_str,
@@ -317,36 +319,6 @@ class Milvus(VectorDb):
317
319
  max_length=65_535,
318
320
  )
319
321
 
320
- def doc_exists(self, document: Document) -> bool:
321
- """
322
- Validating if the document exists or not
323
-
324
- Args:
325
- document (Document): Document to validate
326
- """
327
- if self.client:
328
- cleaned_content = document.content.replace("\x00", "\ufffd")
329
- doc_id = md5(cleaned_content.encode()).hexdigest()
330
- collection_points = self.client.get(
331
- collection_name=self.collection,
332
- ids=[doc_id],
333
- )
334
- return len(collection_points) > 0
335
- return False
336
-
337
- async def async_doc_exists(self, document: Document) -> bool:
338
- """
339
- Check if document exists asynchronously.
340
- AsyncMilvusClient supports get().
341
- """
342
- cleaned_content = document.content.replace("\x00", "\ufffd")
343
- doc_id = md5(cleaned_content.encode()).hexdigest()
344
- collection_points = await self.async_client.get(
345
- collection_name=self.collection,
346
- ids=[doc_id],
347
- )
348
- return len(collection_points) > 0
349
-
350
322
  def name_exists(self, name: str) -> bool:
351
323
  """
352
324
  Validates if a document with the given name exists in the collection.
@@ -362,6 +334,7 @@ class Milvus(VectorDb):
362
334
  scroll_result = self.client.query(
363
335
  collection_name=self.collection,
364
336
  filter=expr,
337
+ output_fields=["id"],
365
338
  limit=1,
366
339
  )
367
340
  return len(scroll_result) > 0 and len(scroll_result[0]) > 0
@@ -391,6 +364,7 @@ class Milvus(VectorDb):
391
364
  scroll_result = self.client.query(
392
365
  collection_name=self.collection,
393
366
  filter=expr,
367
+ output_fields=["id"],
394
368
  limit=1,
395
369
  )
396
370
  return len(scroll_result) > 0 and len(scroll_result[0]) > 0
@@ -457,7 +431,7 @@ class Milvus(VectorDb):
457
431
  "id": doc_id,
458
432
  "vector": document.embedding,
459
433
  "name": document.name,
460
- "content_id": document.content_id,
434
+ "content_id": document.content_id or "",
461
435
  "meta_data": meta_data,
462
436
  "content": cleaned_content,
463
437
  "usage": document.usage,
@@ -528,7 +502,9 @@ class Milvus(VectorDb):
528
502
  log_debug(f"Skipping document without embedding: {document.name} ({document.meta_data})")
529
503
  return None
530
504
  cleaned_content = document.content.replace("\x00", "\ufffd")
531
- doc_id = md5(cleaned_content.encode()).hexdigest()
505
+ # Include content_hash in ID to ensure uniqueness across different content hashes
506
+ base_id = document.id or md5(cleaned_content.encode()).hexdigest()
507
+ doc_id = md5(f"{base_id}_{content_hash}".encode()).hexdigest()
532
508
 
533
509
  meta_data = document.meta_data or {}
534
510
  if filters:
@@ -538,7 +514,7 @@ class Milvus(VectorDb):
538
514
  "id": doc_id,
539
515
  "vector": document.embedding,
540
516
  "name": document.name,
541
- "content_id": document.content_id,
517
+ "content_id": document.content_id or "",
542
518
  "meta_data": meta_data,
543
519
  "content": cleaned_content,
544
520
  "usage": document.usage,
@@ -573,30 +549,41 @@ class Milvus(VectorDb):
573
549
  filters (Optional[Dict[str, Any]]): Filters to apply while upserting
574
550
  """
575
551
  log_debug(f"Upserting {len(documents)} documents")
576
- for document in documents:
577
- document.embed(embedder=self.embedder)
578
- cleaned_content = document.content.replace("\x00", "\ufffd")
579
- doc_id = md5(cleaned_content.encode()).hexdigest()
580
-
581
- meta_data = document.meta_data or {}
582
- if filters:
583
- meta_data.update(filters)
584
-
585
- data = {
586
- "id": doc_id,
587
- "vector": document.embedding,
588
- "name": document.name,
589
- "content_id": document.content_id,
590
- "meta_data": document.meta_data,
591
- "content": cleaned_content,
592
- "usage": document.usage,
593
- "content_hash": content_hash,
594
- }
595
- self.client.upsert(
596
- collection_name=self.collection,
597
- data=data,
598
- )
599
- log_debug(f"Upserted document: {document.name} ({document.meta_data})")
552
+
553
+ if self.search_type == SearchType.hybrid:
554
+ for document in documents:
555
+ document.embed(embedder=self.embedder)
556
+ data = self._prepare_document_data(content_hash=content_hash, document=document, include_vectors=True)
557
+ self.client.upsert(
558
+ collection_name=self.collection,
559
+ data=data,
560
+ )
561
+ log_debug(f"Upserted hybrid document: {document.name} ({document.meta_data})")
562
+ else:
563
+ for document in documents:
564
+ document.embed(embedder=self.embedder)
565
+ cleaned_content = document.content.replace("\x00", "\ufffd")
566
+ doc_id = md5(cleaned_content.encode()).hexdigest()
567
+
568
+ meta_data = document.meta_data or {}
569
+ if filters:
570
+ meta_data.update(filters)
571
+
572
+ data = {
573
+ "id": doc_id,
574
+ "vector": document.embedding,
575
+ "name": document.name,
576
+ "content_id": document.content_id or "",
577
+ "meta_data": meta_data, # type: ignore[dict-item]
578
+ "content": cleaned_content,
579
+ "usage": document.usage, # type: ignore[dict-item]
580
+ "content_hash": content_hash,
581
+ }
582
+ self.client.upsert(
583
+ collection_name=self.collection,
584
+ data=data,
585
+ )
586
+ log_debug(f"Upserted document: {document.name} ({document.meta_data})")
600
587
 
601
588
  async def async_upsert(
602
589
  self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None
@@ -642,28 +629,46 @@ class Milvus(VectorDb):
642
629
  embed_tasks = [document.async_embed(embedder=self.embedder) for document in documents]
643
630
  await asyncio.gather(*embed_tasks, return_exceptions=True)
644
631
 
645
- async def process_document(document):
646
- cleaned_content = document.content.replace("\x00", "\ufffd")
647
- doc_id = md5(cleaned_content.encode()).hexdigest()
648
- data = {
649
- "id": doc_id,
650
- "vector": document.embedding,
651
- "name": document.name,
652
- "content_id": document.content_id,
653
- "meta_data": document.meta_data,
654
- "content": cleaned_content,
655
- "usage": document.usage,
656
- "content_hash": content_hash,
657
- }
658
- await self.async_client.upsert(
659
- collection_name=self.collection,
660
- data=data,
661
- )
662
- log_debug(f"Upserted document asynchronously: {document.name} ({document.meta_data})")
663
- return data
632
+ if self.search_type == SearchType.hybrid:
633
+
634
+ async def process_hybrid_document(document):
635
+ data = self._prepare_document_data(content_hash=content_hash, document=document, include_vectors=True)
636
+ await self.async_client.upsert(
637
+ collection_name=self.collection,
638
+ data=data,
639
+ )
640
+ log_debug(f"Upserted hybrid document asynchronously: {document.name} ({document.meta_data})")
641
+ return data
642
+
643
+ await asyncio.gather(*[process_hybrid_document(doc) for doc in documents])
644
+ else:
645
+
646
+ async def process_document(document):
647
+ cleaned_content = document.content.replace("\x00", "\ufffd")
648
+ doc_id = md5(cleaned_content.encode()).hexdigest()
649
+
650
+ meta_data = document.meta_data or {}
651
+ if filters:
652
+ meta_data.update(filters)
653
+
654
+ data = {
655
+ "id": doc_id,
656
+ "vector": document.embedding,
657
+ "name": document.name,
658
+ "content_id": document.content_id or "",
659
+ "meta_data": meta_data, # type: ignore[dict-item]
660
+ "content": cleaned_content,
661
+ "usage": document.usage, # type: ignore[dict-item]
662
+ "content_hash": content_hash,
663
+ }
664
+ await self.async_client.upsert(
665
+ collection_name=self.collection,
666
+ data=data,
667
+ )
668
+ log_debug(f"Upserted document asynchronously: {document.name} ({document.meta_data})")
669
+ return data
664
670
 
665
- # Process all documents in parallel
666
- await asyncio.gather(*[process_document(doc) for doc in documents])
671
+ await asyncio.gather(*[process_document(doc) for doc in documents])
667
672
 
668
673
  log_debug(f"Upserted {len(documents)} documents asynchronously in parallel")
669
674
 
@@ -677,7 +682,11 @@ class Milvus(VectorDb):
677
682
  return MILVUS_DISTANCE_MAP.get(self.distance, "COSINE")
678
683
 
679
684
  def search(
680
- self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
685
+ self,
686
+ query: str,
687
+ limit: int = 5,
688
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
689
+ search_params: Optional[Dict[str, Any]] = None,
681
690
  ) -> List[Document]:
682
691
  """
683
692
  Search for documents matching the query.
@@ -686,6 +695,10 @@ class Milvus(VectorDb):
686
695
  query (str): Query string to search for
687
696
  limit (int): Maximum number of results to return
688
697
  filters (Optional[Dict[str, Any]]): Filters to apply to the search
698
+ search_params (Optional[Dict[str, Any]]): Milvus search parameters including:
699
+ - radius (float): Minimum similarity threshold for range search
700
+ - range_filter (float): Maximum similarity threshold for range search
701
+ - params (dict): Index-specific search params (e.g., nprobe, ef)
689
702
 
690
703
  Returns:
691
704
  List[Document]: List of matching documents
@@ -707,6 +720,7 @@ class Milvus(VectorDb):
707
720
  filter=self._build_expr(filters),
708
721
  output_fields=["*"],
709
722
  limit=limit,
723
+ search_params=search_params,
710
724
  )
711
725
 
712
726
  # Build search results
@@ -734,8 +748,27 @@ class Milvus(VectorDb):
734
748
  return search_results
735
749
 
736
750
  async def async_search(
737
- self, query: str, limit: int = 5, filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None
751
+ self,
752
+ query: str,
753
+ limit: int = 5,
754
+ filters: Optional[Union[Dict[str, Any], List[FilterExpr]]] = None,
755
+ search_params: Optional[Dict[str, Any]] = None,
738
756
  ) -> List[Document]:
757
+ """
758
+ Asynchronously search for documents matching the query.
759
+
760
+ Args:
761
+ query (str): Query string to search for
762
+ limit (int): Maximum number of results to return
763
+ filters (Optional[Dict[str, Any]]): Filters to apply to the search
764
+ search_params (Optional[Dict[str, Any]]): Milvus search parameters including:
765
+ - radius (float): Minimum similarity threshold for range search
766
+ - range_filter (float): Maximum similarity threshold for range search
767
+ - params (dict): Index-specific search params (e.g., nprobe, ef)
768
+
769
+ Returns:
770
+ List[Document]: List of matching documents
771
+ """
739
772
  if isinstance(filters, List):
740
773
  log_warning("Filters Expressions are not supported in Milvus. No filters will be applied.")
741
774
  filters = None
@@ -753,6 +786,7 @@ class Milvus(VectorDb):
753
786
  filter=self._build_expr(filters),
754
787
  output_fields=["*"],
755
788
  limit=limit,
789
+ search_params=search_params,
756
790
  )
757
791
 
758
792
  # Build search results
@@ -1099,7 +1133,7 @@ class Milvus(VectorDb):
1099
1133
  if isinstance(v, (list, tuple)):
1100
1134
  # For array values, use json_contains_any
1101
1135
  values_str = json.dumps(v)
1102
- expr = f'json_contains_any(meta_data, {values_str}, "{k}")'
1136
+ expr = f'json_contains_any(meta_data["{k}"], {values_str})'
1103
1137
  elif isinstance(v, str):
1104
1138
  # For string values
1105
1139
  expr = f'meta_data["{k}"] == "{v}"'
@@ -1,4 +1,4 @@
1
- from agno.vectordb.mongodb.mongodb import MongoDb
1
+ from agno.vectordb.mongodb.mongodb import MongoDb, SearchType
2
2
 
3
3
  # Alias to avoid name collision with the main MongoDb class
4
4
  MongoVectorDb = MongoDb
@@ -6,4 +6,5 @@ MongoVectorDb = MongoDb
6
6
  __all__ = [
7
7
  "MongoVectorDb",
8
8
  "MongoDb",
9
+ "SearchType",
9
10
  ]
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import time
3
+ from importlib import metadata
3
4
  from typing import Any, Dict, List, Optional, Union
4
5
 
5
6
  from bson import ObjectId
@@ -20,11 +21,14 @@ except ImportError:
20
21
  try:
21
22
  from pymongo import AsyncMongoClient, MongoClient, errors
22
23
  from pymongo.collection import Collection
24
+ from pymongo.driver_info import DriverInfo
23
25
  from pymongo.operations import SearchIndexModel
24
26
 
25
27
  except ImportError:
26
28
  raise ImportError("`pymongo` not installed. Please install using `pip install pymongo`")
27
29
 
30
+ DRIVER_METADATA = DriverInfo(name="Agno", version=metadata.version("agno"))
31
+
28
32
 
29
33
  class MongoDb(VectorDb):
30
34
  """
@@ -110,7 +114,7 @@ class MongoDb(VectorDb):
110
114
  from agno.knowledge.embedder.openai import OpenAIEmbedder
111
115
 
112
116
  embedder = OpenAIEmbedder()
113
- log_info("Embedder not provided, using OpenAIEmbedder as default.")
117
+ log_debug("Embedder not provided, using OpenAIEmbedder as default.")
114
118
  self.embedder = embedder
115
119
 
116
120
  self.distance_metric = distance_metric
@@ -135,6 +139,11 @@ class MongoDb(VectorDb):
135
139
  self._async_db = None
136
140
  self._async_collection: Optional[Collection] = None
137
141
 
142
+ if self._client is not None:
143
+ # append_metadata was added in PyMongo 4.14.0, but is a valid database name on earlier versions
144
+ if callable(self._client.append_metadata):
145
+ self._client.append_metadata(DRIVER_METADATA)
146
+
138
147
  def _get_client(self) -> MongoClient:
139
148
  """Create or retrieve the MongoDB client."""
140
149
  if self._client is None:
@@ -157,7 +166,7 @@ class MongoDb(VectorDb):
157
166
  warnings.filterwarnings(
158
167
  "ignore", category=UserWarning, message=".*connected to a CosmosDB cluster.*"
159
168
  )
160
- self._client = MongoClient(self.connection_string, **cosmos_kwargs) # type: ignore
169
+ self._client = MongoClient(self.connection_string, **cosmos_kwargs, driver=DRIVER_METADATA) # type: ignore
161
170
 
162
171
  self._client.admin.command("ping")
163
172
 
@@ -173,7 +182,7 @@ class MongoDb(VectorDb):
173
182
  else:
174
183
  try:
175
184
  log_debug("Creating MongoDB Client")
176
- self._client = MongoClient(self.connection_string, **self.kwargs)
185
+ self._client = MongoClient(self.connection_string, **self.kwargs, driver=DRIVER_METADATA) # type: ignore
177
186
  # Trigger a connection to verify the client
178
187
  self._client.admin.command("ping")
179
188
  log_info("Connected to MongoDB successfully.")
@@ -195,6 +204,7 @@ class MongoDb(VectorDb):
195
204
  maxPoolSize=self.kwargs.get("maxPoolSize", 100),
196
205
  retryWrites=self.kwargs.get("retryWrites", True),
197
206
  serverSelectionTimeoutMS=5000,
207
+ driver=DRIVER_METADATA,
198
208
  )
199
209
  # Verify connection
200
210
  try:
@@ -471,20 +481,6 @@ class MongoDb(VectorDb):
471
481
  if self.wait_until_index_ready_in_seconds:
472
482
  await self._wait_for_index_ready_async()
473
483
 
474
- def doc_exists(self, document: Document) -> bool:
475
- """Check if a document exists in the MongoDB collection based on its content."""
476
- try:
477
- collection = self._get_collection()
478
- # Use content hash as document ID
479
- doc_id = md5(document.content.encode("utf-8")).hexdigest()
480
- result = collection.find_one({"_id": doc_id})
481
- exists = result is not None
482
- log_debug(f"Document {'exists' if exists else 'does not exist'}: {doc_id}")
483
- return exists
484
- except Exception as e:
485
- logger.error(f"Error checking document existence: {e}")
486
- return False
487
-
488
484
  def name_exists(self, name: str) -> bool:
489
485
  """Check if a document with a given name exists in the collection."""
490
486
  try:
@@ -1024,19 +1020,6 @@ class MongoDb(VectorDb):
1024
1020
  logger.error(f"Error getting document count: {e}")
1025
1021
  return 0
1026
1022
 
1027
- async def async_doc_exists(self, document: Document) -> bool:
1028
- """Check if a document exists asynchronously."""
1029
- try:
1030
- collection = await self._get_async_collection()
1031
- doc_id = md5(document.content.encode("utf-8")).hexdigest()
1032
- result = await collection.find_one({"_id": doc_id})
1033
- exists = result is not None
1034
- log_debug(f"Document {'exists' if exists else 'does not exist'}: {doc_id}")
1035
- return exists
1036
- except Exception as e:
1037
- logger.error(f"Error checking document existence asynchronously: {e}")
1038
- return False
1039
-
1040
1023
  async def async_insert(
1041
1024
  self, content_hash: str, documents: List[Document], filters: Optional[Dict[str, Any]] = None
1042
1025
  ) -> None:
@@ -1167,7 +1150,7 @@ class MongoDb(VectorDb):
1167
1150
  if isinstance(filters, List):
1168
1151
  log_warning("Filters Expressions are not supported in MongoDB. No filters will be applied.")
1169
1152
  filters = None
1170
- query_embedding = self.embedder.get_embedding(query)
1153
+ query_embedding = await self.embedder.async_get_embedding(query)
1171
1154
  if query_embedding is None:
1172
1155
  logger.error(f"Failed to generate embedding for query: {query}")
1173
1156
  return []