coco-framework 0.1.0__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 (400) hide show
  1. coco/__init__.py +3 -0
  2. coco/agents/__init__.py +9 -0
  3. coco/agents/_subagent_transformer.py +307 -0
  4. coco/agents/factory.py +2164 -0
  5. coco/agents/middleware/__init__.py +100 -0
  6. coco/agents/middleware/_execution.py +385 -0
  7. coco/agents/middleware/_redaction.py +454 -0
  8. coco/agents/middleware/_retry.py +134 -0
  9. coco/agents/middleware/_trace_policy.py +61 -0
  10. coco/agents/middleware/context_editing.py +306 -0
  11. coco/agents/middleware/file_search.py +433 -0
  12. coco/agents/middleware/human_in_the_loop.py +680 -0
  13. coco/agents/middleware/internal_call_transformer.py +108 -0
  14. coco/agents/middleware/model_call_limit.py +267 -0
  15. coco/agents/middleware/model_fallback.py +417 -0
  16. coco/agents/middleware/model_retry.py +327 -0
  17. coco/agents/middleware/pii.py +878 -0
  18. coco/agents/middleware/provider_tool_search.py +307 -0
  19. coco/agents/middleware/shell_tool.py +949 -0
  20. coco/agents/middleware/summarization.py +910 -0
  21. coco/agents/middleware/todo.py +357 -0
  22. coco/agents/middleware/tool_call_limit.py +495 -0
  23. coco/agents/middleware/tool_emulator.py +251 -0
  24. coco/agents/middleware/tool_error.py +209 -0
  25. coco/agents/middleware/tool_retry.py +421 -0
  26. coco/agents/middleware/tool_selection.py +515 -0
  27. coco/agents/middleware/types.py +2201 -0
  28. coco/agents/structured_output.py +472 -0
  29. coco/chat_models/__init__.py +7 -0
  30. coco/chat_models/base.py +1082 -0
  31. coco/embeddings/__init__.py +18 -0
  32. coco/embeddings/base.py +279 -0
  33. coco/mcp/__init__.py +36 -0
  34. coco/mcp/adapter.py +245 -0
  35. coco/mcp/elicitation.py +394 -0
  36. coco/mcp/tools.py +307 -0
  37. coco/messages/__init__.py +73 -0
  38. coco/py.typed +0 -0
  39. coco/rate_limiters/__init__.py +13 -0
  40. coco/tools/__init__.py +22 -0
  41. coco/tools/tool_node.py +20 -0
  42. coco_anthropic/__init__.py +15 -0
  43. coco_anthropic/_client_utils.py +81 -0
  44. coco_anthropic/_compat.py +228 -0
  45. coco_anthropic/_sdk_compat.py +124 -0
  46. coco_anthropic/_version.py +3 -0
  47. coco_anthropic/chat_models.py +3349 -0
  48. coco_anthropic/data/__init__.py +1 -0
  49. coco_anthropic/data/_profiles.py +491 -0
  50. coco_anthropic/data/profile_augmentations.toml +69 -0
  51. coco_anthropic/experimental.py +140 -0
  52. coco_anthropic/llms.py +428 -0
  53. coco_anthropic/middleware/__init__.py +25 -0
  54. coco_anthropic/middleware/anthropic_tools.py +1197 -0
  55. coco_anthropic/middleware/bash.py +84 -0
  56. coco_anthropic/middleware/file_search.py +360 -0
  57. coco_anthropic/middleware/prompt_caching.py +276 -0
  58. coco_anthropic/output_parsers.py +93 -0
  59. coco_anthropic/py.typed +0 -0
  60. coco_core/__init__.py +20 -0
  61. coco_core/_api/__init__.py +87 -0
  62. coco_core/_api/beta_decorator.py +266 -0
  63. coco_core/_api/deprecation.py +637 -0
  64. coco_core/_api/internal.py +23 -0
  65. coco_core/_api/path.py +50 -0
  66. coco_core/_import_utils.py +41 -0
  67. coco_core/_protocol.py +625 -0
  68. coco_core/_security/__init__.py +36 -0
  69. coco_core/_security/_exceptions.py +9 -0
  70. coco_core/_security/_policy.py +313 -0
  71. coco_core/_security/_ssrf_protection.py +155 -0
  72. coco_core/_security/_transport.py +254 -0
  73. coco_core/agents.py +258 -0
  74. coco_core/caches.py +272 -0
  75. coco_core/callbacks/__init__.py +132 -0
  76. coco_core/callbacks/base.py +1229 -0
  77. coco_core/callbacks/file.py +266 -0
  78. coco_core/callbacks/manager.py +2826 -0
  79. coco_core/callbacks/stdout.py +123 -0
  80. coco_core/callbacks/streaming_stdout.py +154 -0
  81. coco_core/callbacks/usage.py +124 -0
  82. coco_core/chat_history.py +267 -0
  83. coco_core/chat_loaders.py +26 -0
  84. coco_core/chat_sessions.py +19 -0
  85. coco_core/cross_encoders.py +18 -0
  86. coco_core/document_loaders/__init__.py +36 -0
  87. coco_core/document_loaders/base.py +155 -0
  88. coco_core/document_loaders/blob_loaders.py +38 -0
  89. coco_core/documents/__init__.py +55 -0
  90. coco_core/documents/base.py +347 -0
  91. coco_core/documents/compressor.py +74 -0
  92. coco_core/documents/transformers.py +79 -0
  93. coco_core/embeddings/__init__.py +31 -0
  94. coco_core/embeddings/embeddings.py +78 -0
  95. coco_core/embeddings/fake.py +129 -0
  96. coco_core/env.py +22 -0
  97. coco_core/example_selectors/__init__.py +47 -0
  98. coco_core/example_selectors/base.py +60 -0
  99. coco_core/example_selectors/length_based.py +131 -0
  100. coco_core/example_selectors/semantic_similarity.py +366 -0
  101. coco_core/exceptions.py +166 -0
  102. coco_core/globals.py +72 -0
  103. coco_core/indexing/__init__.py +53 -0
  104. coco_core/indexing/api.py +954 -0
  105. coco_core/indexing/base.py +661 -0
  106. coco_core/indexing/in_memory.py +104 -0
  107. coco_core/language_models/__init__.py +116 -0
  108. coco_core/language_models/_compat_bridge.py +844 -0
  109. coco_core/language_models/_utils.py +343 -0
  110. coco_core/language_models/base.py +500 -0
  111. coco_core/language_models/chat_model_stream.py +1525 -0
  112. coco_core/language_models/chat_models.py +2748 -0
  113. coco_core/language_models/fake.py +137 -0
  114. coco_core/language_models/fake_chat_models.py +396 -0
  115. coco_core/language_models/llms.py +1569 -0
  116. coco_core/language_models/model_profile.py +183 -0
  117. coco_core/load/__init__.py +44 -0
  118. coco_core/load/_validation.py +191 -0
  119. coco_core/load/dump.py +120 -0
  120. coco_core/load/load.py +828 -0
  121. coco_core/load/mapping.py +1097 -0
  122. coco_core/load/serializable.py +406 -0
  123. coco_core/messages/__init__.py +198 -0
  124. coco_core/messages/ai.py +854 -0
  125. coco_core/messages/base.py +518 -0
  126. coco_core/messages/block_translators/__init__.py +112 -0
  127. coco_core/messages/block_translators/anthropic.py +534 -0
  128. coco_core/messages/block_translators/bedrock.py +108 -0
  129. coco_core/messages/block_translators/bedrock_converse.py +329 -0
  130. coco_core/messages/block_translators/coco_v0.py +303 -0
  131. coco_core/messages/block_translators/google_genai.py +610 -0
  132. coco_core/messages/block_translators/google_vertexai.py +21 -0
  133. coco_core/messages/block_translators/groq.py +157 -0
  134. coco_core/messages/block_translators/openai.py +1089 -0
  135. coco_core/messages/chat.py +64 -0
  136. coco_core/messages/content.py +1488 -0
  137. coco_core/messages/function.py +62 -0
  138. coco_core/messages/human.py +70 -0
  139. coco_core/messages/modifier.py +33 -0
  140. coco_core/messages/system.py +70 -0
  141. coco_core/messages/tool.py +418 -0
  142. coco_core/messages/utils.py +2442 -0
  143. coco_core/output_parsers/__init__.py +101 -0
  144. coco_core/output_parsers/base.py +361 -0
  145. coco_core/output_parsers/format_instructions.py +16 -0
  146. coco_core/output_parsers/json.py +139 -0
  147. coco_core/output_parsers/list.py +249 -0
  148. coco_core/output_parsers/openai_functions.py +324 -0
  149. coco_core/output_parsers/openai_tools.py +383 -0
  150. coco_core/output_parsers/pydantic.py +141 -0
  151. coco_core/output_parsers/string.py +63 -0
  152. coco_core/output_parsers/transform.py +175 -0
  153. coco_core/output_parsers/xml.py +301 -0
  154. coco_core/outputs/__init__.py +66 -0
  155. coco_core/outputs/chat_generation.py +147 -0
  156. coco_core/outputs/chat_result.py +39 -0
  157. coco_core/outputs/generation.py +80 -0
  158. coco_core/outputs/llm_result.py +112 -0
  159. coco_core/outputs/run_info.py +22 -0
  160. coco_core/prompt_values.py +161 -0
  161. coco_core/prompts/__init__.py +101 -0
  162. coco_core/prompts/base.py +508 -0
  163. coco_core/prompts/chat.py +1495 -0
  164. coco_core/prompts/dict.py +177 -0
  165. coco_core/prompts/few_shot.py +483 -0
  166. coco_core/prompts/few_shot_with_templates.py +239 -0
  167. coco_core/prompts/image.py +177 -0
  168. coco_core/prompts/loading.py +296 -0
  169. coco_core/prompts/message.py +97 -0
  170. coco_core/prompts/prompt.py +312 -0
  171. coco_core/prompts/string.py +399 -0
  172. coco_core/prompts/structured.py +214 -0
  173. coco_core/py.typed +0 -0
  174. coco_core/rate_limiters.py +254 -0
  175. coco_core/retrievers.py +328 -0
  176. coco_core/runnables/__init__.py +136 -0
  177. coco_core/runnables/base.py +6725 -0
  178. coco_core/runnables/branch.py +457 -0
  179. coco_core/runnables/config.py +712 -0
  180. coco_core/runnables/configurable.py +710 -0
  181. coco_core/runnables/fallbacks.py +663 -0
  182. coco_core/runnables/graph.py +741 -0
  183. coco_core/runnables/graph_ascii.py +366 -0
  184. coco_core/runnables/graph_mermaid.py +503 -0
  185. coco_core/runnables/graph_png.py +215 -0
  186. coco_core/runnables/history.py +631 -0
  187. coco_core/runnables/passthrough.py +849 -0
  188. coco_core/runnables/retry.py +379 -0
  189. coco_core/runnables/router.py +239 -0
  190. coco_core/runnables/schema.py +188 -0
  191. coco_core/runnables/utils.py +798 -0
  192. coco_core/stores.py +291 -0
  193. coco_core/structured_query.py +203 -0
  194. coco_core/sys_info.py +154 -0
  195. coco_core/tools/__init__.py +95 -0
  196. coco_core/tools/base.py +1948 -0
  197. coco_core/tools/convert.py +497 -0
  198. coco_core/tools/render.py +67 -0
  199. coco_core/tools/retriever.py +94 -0
  200. coco_core/tools/simple.py +208 -0
  201. coco_core/tools/structured.py +322 -0
  202. coco_core/tracers/__init__.py +47 -0
  203. coco_core/tracers/_compat.py +95 -0
  204. coco_core/tracers/_context.py +337 -0
  205. coco_core/tracers/_streaming.py +53 -0
  206. coco_core/tracers/base.py +955 -0
  207. coco_core/tracers/coco.py +279 -0
  208. coco_core/tracers/context.py +205 -0
  209. coco_core/tracers/core.py +766 -0
  210. coco_core/tracers/event_stream.py +1101 -0
  211. coco_core/tracers/log_stream.py +765 -0
  212. coco_core/tracers/memory_stream.py +200 -0
  213. coco_core/tracers/root_listeners.py +130 -0
  214. coco_core/tracers/run_collector.py +40 -0
  215. coco_core/tracers/schemas.py +14 -0
  216. coco_core/tracers/stdout.py +205 -0
  217. coco_core/utils/__init__.py +111 -0
  218. coco_core/utils/_gateway.py +274 -0
  219. coco_core/utils/_merge.py +218 -0
  220. coco_core/utils/aiter.py +357 -0
  221. coco_core/utils/env.py +86 -0
  222. coco_core/utils/formatting.py +80 -0
  223. coco_core/utils/function_calling.py +869 -0
  224. coco_core/utils/html.py +132 -0
  225. coco_core/utils/image.py +15 -0
  226. coco_core/utils/input.py +82 -0
  227. coco_core/utils/interactive_env.py +12 -0
  228. coco_core/utils/iter.py +229 -0
  229. coco_core/utils/json.py +230 -0
  230. coco_core/utils/json_schema.py +273 -0
  231. coco_core/utils/mustache.py +706 -0
  232. coco_core/utils/pydantic.py +631 -0
  233. coco_core/utils/strings.py +69 -0
  234. coco_core/utils/usage.py +61 -0
  235. coco_core/utils/utils.py +528 -0
  236. coco_core/utils/uuid.py +57 -0
  237. coco_core/vectorstores/__init__.py +53 -0
  238. coco_core/vectorstores/base.py +1111 -0
  239. coco_core/vectorstores/in_memory.py +546 -0
  240. coco_core/vectorstores/utils.py +163 -0
  241. coco_core/version.py +3 -0
  242. coco_framework-0.1.0.dist-info/METADATA +184 -0
  243. coco_framework-0.1.0.dist-info/RECORD +400 -0
  244. coco_framework-0.1.0.dist-info/WHEEL +4 -0
  245. coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
  246. coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
  247. coco_graph/_internal/__init__.py +4 -0
  248. coco_graph/_internal/_cache.py +31 -0
  249. coco_graph/_internal/_config.py +474 -0
  250. coco_graph/_internal/_constants.py +140 -0
  251. coco_graph/_internal/_fields.py +213 -0
  252. coco_graph/_internal/_future.py +220 -0
  253. coco_graph/_internal/_pydantic.py +275 -0
  254. coco_graph/_internal/_queue.py +124 -0
  255. coco_graph/_internal/_replay.py +90 -0
  256. coco_graph/_internal/_retry.py +29 -0
  257. coco_graph/_internal/_runnable.py +969 -0
  258. coco_graph/_internal/_scratchpad.py +19 -0
  259. coco_graph/_internal/_serde.py +253 -0
  260. coco_graph/_internal/_timeout.py +25 -0
  261. coco_graph/_internal/_typing.py +54 -0
  262. coco_graph/cache/base/__init__.py +48 -0
  263. coco_graph/cache/base/py.typed +0 -0
  264. coco_graph/cache/memory/__init__.py +73 -0
  265. coco_graph/cache/redis/__init__.py +144 -0
  266. coco_graph/cache/sqlite/__init__.py +120 -0
  267. coco_graph/callbacks.py +394 -0
  268. coco_graph/channels/__init__.py +29 -0
  269. coco_graph/channels/any_value.py +72 -0
  270. coco_graph/channels/base.py +121 -0
  271. coco_graph/channels/binop.py +155 -0
  272. coco_graph/channels/delta.py +202 -0
  273. coco_graph/channels/ephemeral_value.py +79 -0
  274. coco_graph/channels/last_value.py +151 -0
  275. coco_graph/channels/named_barrier_value.py +167 -0
  276. coco_graph/channels/topic.py +94 -0
  277. coco_graph/channels/untracked_value.py +73 -0
  278. coco_graph/checkpoint/base/__init__.py +861 -0
  279. coco_graph/checkpoint/base/id.py +109 -0
  280. coco_graph/checkpoint/base/py.typed +0 -0
  281. coco_graph/checkpoint/memory/__init__.py +698 -0
  282. coco_graph/checkpoint/memory/py.typed +0 -0
  283. coco_graph/checkpoint/postgres/__init__.py +602 -0
  284. coco_graph/checkpoint/postgres/_ainternal.py +23 -0
  285. coco_graph/checkpoint/postgres/_internal.py +21 -0
  286. coco_graph/checkpoint/postgres/aio.py +691 -0
  287. coco_graph/checkpoint/postgres/base.py +666 -0
  288. coco_graph/checkpoint/postgres/py.typed +0 -0
  289. coco_graph/checkpoint/postgres/shallow.py +967 -0
  290. coco_graph/checkpoint/serde/__init__.py +0 -0
  291. coco_graph/checkpoint/serde/_msgpack.py +97 -0
  292. coco_graph/checkpoint/serde/base.py +64 -0
  293. coco_graph/checkpoint/serde/encrypted.py +80 -0
  294. coco_graph/checkpoint/serde/event_hooks.py +52 -0
  295. coco_graph/checkpoint/serde/jsonplus.py +883 -0
  296. coco_graph/checkpoint/serde/py.typed +0 -0
  297. coco_graph/checkpoint/serde/types.py +68 -0
  298. coco_graph/checkpoint/sqlite/__init__.py +645 -0
  299. coco_graph/checkpoint/sqlite/_delta.py +172 -0
  300. coco_graph/checkpoint/sqlite/aio.py +738 -0
  301. coco_graph/checkpoint/sqlite/py.typed +0 -0
  302. coco_graph/checkpoint/sqlite/utils.py +116 -0
  303. coco_graph/config.py +196 -0
  304. coco_graph/constants.py +64 -0
  305. coco_graph/errors.py +241 -0
  306. coco_graph/func/__init__.py +620 -0
  307. coco_graph/graph/__init__.py +12 -0
  308. coco_graph/graph/_branch.py +225 -0
  309. coco_graph/graph/_node.py +103 -0
  310. coco_graph/graph/message.py +437 -0
  311. coco_graph/graph/state.py +1978 -0
  312. coco_graph/graph/ui.py +227 -0
  313. coco_graph/managed/__init__.py +3 -0
  314. coco_graph/managed/base.py +31 -0
  315. coco_graph/managed/is_last_step.py +24 -0
  316. coco_graph/prebuilt/__init__.py +23 -0
  317. coco_graph/prebuilt/_tool_call_stream.py +117 -0
  318. coco_graph/prebuilt/_tool_call_transformer.py +165 -0
  319. coco_graph/prebuilt/chat_agent_executor.py +1015 -0
  320. coco_graph/prebuilt/interrupt.py +105 -0
  321. coco_graph/prebuilt/py.typed +0 -0
  322. coco_graph/prebuilt/tool_node.py +2030 -0
  323. coco_graph/prebuilt/tool_validator.py +221 -0
  324. coco_graph/pregel/__init__.py +3 -0
  325. coco_graph/pregel/_algo.py +1460 -0
  326. coco_graph/pregel/_call.py +298 -0
  327. coco_graph/pregel/_checkpoint.py +331 -0
  328. coco_graph/pregel/_config.py +0 -0
  329. coco_graph/pregel/_draw.py +294 -0
  330. coco_graph/pregel/_executor.py +223 -0
  331. coco_graph/pregel/_io.py +174 -0
  332. coco_graph/pregel/_log.py +3 -0
  333. coco_graph/pregel/_loop.py +1988 -0
  334. coco_graph/pregel/_messages.py +461 -0
  335. coco_graph/pregel/_read.py +312 -0
  336. coco_graph/pregel/_retry.py +854 -0
  337. coco_graph/pregel/_runner.py +941 -0
  338. coco_graph/pregel/_tools.py +268 -0
  339. coco_graph/pregel/_utils.py +221 -0
  340. coco_graph/pregel/_validate.py +120 -0
  341. coco_graph/pregel/_write.py +192 -0
  342. coco_graph/pregel/debug.py +302 -0
  343. coco_graph/pregel/main.py +4364 -0
  344. coco_graph/pregel/protocol.py +288 -0
  345. coco_graph/pregel/types.py +38 -0
  346. coco_graph/py.typed +0 -0
  347. coco_graph/runtime.py +350 -0
  348. coco_graph/store/base/__init__.py +1322 -0
  349. coco_graph/store/base/batch.py +371 -0
  350. coco_graph/store/base/embed.py +436 -0
  351. coco_graph/store/base/py.typed +0 -0
  352. coco_graph/store/memory/__init__.py +592 -0
  353. coco_graph/store/memory/py.typed +0 -0
  354. coco_graph/store/postgres/__init__.py +4 -0
  355. coco_graph/store/postgres/aio.py +592 -0
  356. coco_graph/store/postgres/base.py +1480 -0
  357. coco_graph/store/postgres/py.typed +0 -0
  358. coco_graph/store/sqlite/__init__.py +4 -0
  359. coco_graph/store/sqlite/aio.py +632 -0
  360. coco_graph/store/sqlite/base.py +1514 -0
  361. coco_graph/stream/__init__.py +45 -0
  362. coco_graph/stream/_convert.py +32 -0
  363. coco_graph/stream/_mux.py +523 -0
  364. coco_graph/stream/_types.py +330 -0
  365. coco_graph/stream/run_stream.py +735 -0
  366. coco_graph/stream/stream_channel.py +341 -0
  367. coco_graph/stream/transformers.py +1039 -0
  368. coco_graph/types.py +1079 -0
  369. coco_graph/typing.py +48 -0
  370. coco_graph/utils/__init__.py +1 -0
  371. coco_graph/utils/config.py +4 -0
  372. coco_graph/utils/runnable.py +3 -0
  373. coco_graph/version.py +13 -0
  374. coco_graph/warnings.py +69 -0
  375. coco_openai/__init__.py +20 -0
  376. coco_openai/_compat.py +45 -0
  377. coco_openai/_version.py +3 -0
  378. coco_openai/chat_models/__init__.py +6 -0
  379. coco_openai/chat_models/_client_utils.py +684 -0
  380. coco_openai/chat_models/_compat.py +525 -0
  381. coco_openai/chat_models/azure.py +1227 -0
  382. coco_openai/chat_models/base.py +5654 -0
  383. coco_openai/chat_models/codex.py +557 -0
  384. coco_openai/chatgpt_oauth.py +1063 -0
  385. coco_openai/data/__init__.py +1 -0
  386. coco_openai/data/_profiles.py +1655 -0
  387. coco_openai/data/profile_augmentations.toml +127 -0
  388. coco_openai/embeddings/__init__.py +6 -0
  389. coco_openai/embeddings/azure.py +240 -0
  390. coco_openai/embeddings/base.py +820 -0
  391. coco_openai/llms/__init__.py +6 -0
  392. coco_openai/llms/azure.py +249 -0
  393. coco_openai/llms/base.py +919 -0
  394. coco_openai/middleware/__init__.py +11 -0
  395. coco_openai/middleware/openai_moderation.py +484 -0
  396. coco_openai/output_parsers/__init__.py +9 -0
  397. coco_openai/output_parsers/tools.py +9 -0
  398. coco_openai/py.typed +0 -0
  399. coco_openai/tools/__init__.py +5 -0
  400. coco_openai/tools/custom_tool.py +111 -0
@@ -0,0 +1,1111 @@
1
+ """A vector store stores embedded data and performs vector search.
2
+
3
+ One of the most common ways to store and search over unstructured data is to
4
+ embed it and store the resulting embedding vectors, and then query the store
5
+ and retrieve the data that are 'most similar' to the embedded query.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import math
12
+ import warnings
13
+ from abc import ABC, abstractmethod
14
+ from itertools import cycle
15
+ from typing import (
16
+ TYPE_CHECKING,
17
+ Any,
18
+ ClassVar,
19
+ TypeVar,
20
+ )
21
+
22
+ from pydantic import ConfigDict, Field, model_validator
23
+ from typing_extensions import Self, override
24
+
25
+ from coco_core.documents import Document
26
+ from coco_core.embeddings import Embeddings
27
+ from coco_core.retrievers import BaseRetriever, LangSmithRetrieverParams
28
+ from coco_core.runnables.config import run_in_executor
29
+
30
+ if TYPE_CHECKING:
31
+ from collections.abc import Callable, Collection, Iterable, Iterator, Sequence
32
+
33
+ from coco_core.callbacks.manager import (
34
+ AsyncCallbackManagerForRetrieverRun,
35
+ CallbackManagerForRetrieverRun,
36
+ )
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ VST = TypeVar("VST", bound="VectorStore")
41
+
42
+
43
+ class VectorStore(ABC):
44
+ """Interface for vector store."""
45
+
46
+ def add_texts(
47
+ self,
48
+ texts: Iterable[str],
49
+ metadatas: list[dict[str, Any]] | None = None,
50
+ *,
51
+ ids: list[str] | None = None,
52
+ **kwargs: Any,
53
+ ) -> list[str]:
54
+ """Run more texts through the embeddings and add to the `VectorStore`.
55
+
56
+ Args:
57
+ texts: Iterable of strings to add to the `VectorStore`.
58
+ metadatas: Optional list of metadatas associated with the texts.
59
+ ids: Optional list of IDs associated with the texts.
60
+ **kwargs: `VectorStore` specific parameters.
61
+
62
+ One of the kwargs should be `ids` which is a list of ids
63
+ associated with the texts.
64
+
65
+ Returns:
66
+ List of IDs from adding the texts into the `VectorStore`.
67
+
68
+ Raises:
69
+ ValueError: If the number of metadatas does not match the number of texts.
70
+ ValueError: If the number of IDs does not match the number of texts.
71
+ """
72
+ if type(self).add_documents != VectorStore.add_documents:
73
+ # This condition is triggered if the subclass has provided
74
+ # an implementation of the upsert method.
75
+ # The existing add_texts
76
+ texts_: Sequence[str] = (
77
+ texts if isinstance(texts, (list, tuple)) else list(texts)
78
+ )
79
+ if metadatas and len(metadatas) != len(texts_):
80
+ msg = (
81
+ "The number of metadatas must match the number of texts."
82
+ f"Got {len(metadatas)} metadatas and {len(texts_)} texts."
83
+ )
84
+ raise ValueError(msg)
85
+ metadatas_ = iter(metadatas) if metadatas else cycle([{}])
86
+ ids_: Iterator[str | None] = iter(ids) if ids else cycle([None])
87
+ docs = [
88
+ Document(id=id_, page_content=text, metadata=metadata_)
89
+ for text, metadata_, id_ in zip(texts, metadatas_, ids_, strict=False)
90
+ ]
91
+ if ids is not None:
92
+ # For backward compatibility
93
+ kwargs["ids"] = ids
94
+
95
+ return self.add_documents(docs, **kwargs)
96
+ msg = f"`add_texts` has not been implemented for {self.__class__.__name__} "
97
+ raise NotImplementedError(msg)
98
+
99
+ @property
100
+ def embeddings(self) -> Embeddings | None:
101
+ """Access the query embedding object if available."""
102
+ logger.debug(
103
+ "The embeddings property has not been implemented for %s",
104
+ self.__class__.__name__,
105
+ )
106
+ return None
107
+
108
+ def delete(self, ids: list[str] | None = None, **kwargs: Any) -> bool | None:
109
+ """Delete by vector ID or other criteria.
110
+
111
+ Args:
112
+ ids: List of IDs to delete. If `None`, delete all.
113
+ **kwargs: Other keyword arguments that subclasses might use.
114
+
115
+ Returns:
116
+ `True` if deletion is successful, `False` otherwise, `None` if not
117
+ implemented.
118
+ """
119
+ msg = "delete method must be implemented by subclass."
120
+ raise NotImplementedError(msg)
121
+
122
+ def get_by_ids(self, ids: Sequence[str], /) -> list[Document]:
123
+ """Get documents by their IDs.
124
+
125
+ The returned documents are expected to have the ID field set to the ID of the
126
+ document in the vector store.
127
+
128
+ Fewer documents may be returned than requested if some IDs are not found or
129
+ if there are duplicated IDs.
130
+
131
+ Users should not assume that the order of the returned documents matches
132
+ the order of the input IDs. Instead, users should rely on the ID field of the
133
+ returned documents.
134
+
135
+ This method should **NOT** raise exceptions if no documents are found for
136
+ some IDs.
137
+
138
+ Args:
139
+ ids: List of IDs to retrieve.
140
+
141
+ Returns:
142
+ List of `Document` objects.
143
+ """
144
+ msg = f"{self.__class__.__name__} does not yet support get_by_ids."
145
+ raise NotImplementedError(msg)
146
+
147
+ # Implementations should override this method to provide an async native version.
148
+ async def aget_by_ids(self, ids: Sequence[str], /) -> list[Document]:
149
+ """Async get documents by their IDs.
150
+
151
+ The returned documents are expected to have the ID field set to the ID of the
152
+ document in the vector store.
153
+
154
+ Fewer documents may be returned than requested if some IDs are not found or
155
+ if there are duplicated IDs.
156
+
157
+ Users should not assume that the order of the returned documents matches
158
+ the order of the input IDs. Instead, users should rely on the ID field of the
159
+ returned documents.
160
+
161
+ This method should **NOT** raise exceptions if no documents are found for
162
+ some IDs.
163
+
164
+ Args:
165
+ ids: List of IDs to retrieve.
166
+
167
+ Returns:
168
+ List of `Document` objects.
169
+ """
170
+ return await run_in_executor(None, self.get_by_ids, ids)
171
+
172
+ async def adelete(self, ids: list[str] | None = None, **kwargs: Any) -> bool | None:
173
+ """Async delete by vector ID or other criteria.
174
+
175
+ Args:
176
+ ids: List of IDs to delete. If `None`, delete all.
177
+ **kwargs: Other keyword arguments that subclasses might use.
178
+
179
+ Returns:
180
+ `True` if deletion is successful, `False` otherwise, `None` if not
181
+ implemented.
182
+ """
183
+ return await run_in_executor(None, self.delete, ids, **kwargs)
184
+
185
+ async def aadd_texts(
186
+ self,
187
+ texts: Iterable[str],
188
+ metadatas: list[dict[str, Any]] | None = None,
189
+ *,
190
+ ids: list[str] | None = None,
191
+ **kwargs: Any,
192
+ ) -> list[str]:
193
+ """Async run more texts through the embeddings and add to the `VectorStore`.
194
+
195
+ Args:
196
+ texts: Iterable of strings to add to the `VectorStore`.
197
+ metadatas: Optional list of metadatas associated with the texts.
198
+ ids: Optional list
199
+ **kwargs: `VectorStore` specific parameters.
200
+
201
+ Returns:
202
+ List of IDs from adding the texts into the `VectorStore`.
203
+
204
+ Raises:
205
+ ValueError: If the number of metadatas does not match the number of texts.
206
+ ValueError: If the number of IDs does not match the number of texts.
207
+ """
208
+ if ids is not None:
209
+ # For backward compatibility
210
+ kwargs["ids"] = ids
211
+ if type(self).aadd_documents != VectorStore.aadd_documents:
212
+ # This condition is triggered if the subclass has provided
213
+ # an implementation of the upsert method.
214
+ # The existing add_texts
215
+ texts_: Sequence[str] = (
216
+ texts if isinstance(texts, (list, tuple)) else list(texts)
217
+ )
218
+ if metadatas and len(metadatas) != len(texts_):
219
+ msg = (
220
+ "The number of metadatas must match the number of texts."
221
+ f"Got {len(metadatas)} metadatas and {len(texts_)} texts."
222
+ )
223
+ raise ValueError(msg)
224
+ metadatas_ = iter(metadatas) if metadatas else cycle([{}])
225
+ ids_: Iterator[str | None] = iter(ids) if ids else cycle([None])
226
+
227
+ docs = [
228
+ Document(id=id_, page_content=text, metadata=metadata_)
229
+ for text, metadata_, id_ in zip(texts, metadatas_, ids_, strict=False)
230
+ ]
231
+ return await self.aadd_documents(docs, **kwargs)
232
+ return await run_in_executor(None, self.add_texts, texts, metadatas, **kwargs)
233
+
234
+ def add_documents(self, documents: list[Document], **kwargs: Any) -> list[str]:
235
+ """Add or update documents in the `VectorStore`.
236
+
237
+ Args:
238
+ documents: Documents to add to the `VectorStore`.
239
+ **kwargs: Additional keyword arguments.
240
+
241
+ If kwargs contains IDs and documents contain ids, the IDs in the kwargs
242
+ will receive precedence.
243
+
244
+ Returns:
245
+ List of IDs of the added texts.
246
+ """
247
+ if type(self).add_texts != VectorStore.add_texts:
248
+ if "ids" not in kwargs:
249
+ ids = [doc.id for doc in documents]
250
+
251
+ # If there's at least one valid ID, we'll assume that IDs
252
+ # should be used.
253
+ if any(ids):
254
+ kwargs["ids"] = ids
255
+
256
+ texts = [doc.page_content for doc in documents]
257
+ metadatas = [doc.metadata for doc in documents]
258
+ return self.add_texts(texts, metadatas, **kwargs)
259
+ msg = (
260
+ f"`add_documents` and `add_texts` has not been implemented "
261
+ f"for {self.__class__.__name__} "
262
+ )
263
+ raise NotImplementedError(msg)
264
+
265
+ async def aadd_documents(
266
+ self, documents: list[Document], **kwargs: Any
267
+ ) -> list[str]:
268
+ """Async run more documents through the embeddings and add to the `VectorStore`.
269
+
270
+ Args:
271
+ documents: Documents to add to the `VectorStore`.
272
+ **kwargs: Additional keyword arguments.
273
+
274
+ Returns:
275
+ List of IDs of the added texts.
276
+ """
277
+ # If the async method has been overridden, we'll use that.
278
+ if type(self).aadd_texts != VectorStore.aadd_texts:
279
+ if "ids" not in kwargs:
280
+ ids = [doc.id for doc in documents]
281
+
282
+ # If there's at least one valid ID, we'll assume that IDs
283
+ # should be used.
284
+ if any(ids):
285
+ kwargs["ids"] = ids
286
+
287
+ texts = [doc.page_content for doc in documents]
288
+ metadatas = [doc.metadata for doc in documents]
289
+ return await self.aadd_texts(texts, metadatas, **kwargs)
290
+
291
+ return await run_in_executor(None, self.add_documents, documents, **kwargs)
292
+
293
+ def search(self, query: str, search_type: str, **kwargs: Any) -> list[Document]:
294
+ """Return docs most similar to query using a specified search type.
295
+
296
+ Args:
297
+ query: Input text.
298
+ search_type: Type of search to perform.
299
+
300
+ Can be `'similarity'`, `'mmr'`, or `'similarity_score_threshold'`.
301
+ **kwargs: Arguments to pass to the search method.
302
+
303
+ Returns:
304
+ List of `Document` objects most similar to the query.
305
+
306
+ Raises:
307
+ ValueError: If `search_type` is not one of `'similarity'`,
308
+ `'mmr'`, or `'similarity_score_threshold'`.
309
+ """
310
+ if search_type == "similarity":
311
+ return self.similarity_search(query, **kwargs)
312
+ if search_type == "similarity_score_threshold":
313
+ docs_and_similarities = self.similarity_search_with_relevance_scores(
314
+ query, **kwargs
315
+ )
316
+ return [doc for doc, _ in docs_and_similarities]
317
+ if search_type == "mmr":
318
+ return self.max_marginal_relevance_search(query, **kwargs)
319
+ msg = (
320
+ f"search_type of {search_type} not allowed. Expected "
321
+ "search_type to be 'similarity', 'similarity_score_threshold'"
322
+ " or 'mmr'."
323
+ )
324
+ raise ValueError(msg)
325
+
326
+ async def asearch(
327
+ self, query: str, search_type: str, **kwargs: Any
328
+ ) -> list[Document]:
329
+ """Async return docs most similar to query using a specified search type.
330
+
331
+ Args:
332
+ query: Input text.
333
+ search_type: Type of search to perform.
334
+
335
+ Can be `'similarity'`, `'mmr'`, or `'similarity_score_threshold'`.
336
+ **kwargs: Arguments to pass to the search method.
337
+
338
+ Returns:
339
+ List of `Document` objects most similar to the query.
340
+
341
+ Raises:
342
+ ValueError: If `search_type` is not one of `'similarity'`,
343
+ `'mmr'`, or `'similarity_score_threshold'`.
344
+ """
345
+ if search_type == "similarity":
346
+ return await self.asimilarity_search(query, **kwargs)
347
+ if search_type == "similarity_score_threshold":
348
+ docs_and_similarities = await self.asimilarity_search_with_relevance_scores(
349
+ query, **kwargs
350
+ )
351
+ return [doc for doc, _ in docs_and_similarities]
352
+ if search_type == "mmr":
353
+ return await self.amax_marginal_relevance_search(query, **kwargs)
354
+ msg = (
355
+ f"search_type of {search_type} not allowed. Expected "
356
+ "search_type to be 'similarity', 'similarity_score_threshold' or 'mmr'."
357
+ )
358
+ raise ValueError(msg)
359
+
360
+ @abstractmethod
361
+ def similarity_search(
362
+ self, query: str, k: int = 4, **kwargs: Any
363
+ ) -> list[Document]:
364
+ """Return docs most similar to query.
365
+
366
+ Args:
367
+ query: Input text.
368
+ k: Number of `Document` objects to return.
369
+ **kwargs: Arguments to pass to the search method.
370
+
371
+ Returns:
372
+ List of `Document` objects most similar to the query.
373
+ """
374
+
375
+ @staticmethod
376
+ def _euclidean_relevance_score_fn(distance: float) -> float:
377
+ """Return a similarity score on a scale [0, 1]."""
378
+ # The 'correct' relevance function
379
+ # may differ depending on a few things, including:
380
+ # - the distance / similarity metric used by the VectorStore
381
+ # - the scale of your embeddings (OpenAI's are unit normed. Many
382
+ # others are not!)
383
+ # - embedding dimensionality
384
+ # - etc.
385
+ # This function converts the Euclidean norm of normalized embeddings
386
+ # (0 is most similar, sqrt(2) most dissimilar)
387
+ # to a similarity function (0 to 1)
388
+ return 1.0 - distance / math.sqrt(2)
389
+
390
+ @staticmethod
391
+ def _cosine_relevance_score_fn(distance: float) -> float:
392
+ """Normalize the distance to a score on a scale [0, 1]."""
393
+ return 1.0 - distance
394
+
395
+ @staticmethod
396
+ def _max_inner_product_relevance_score_fn(distance: float) -> float:
397
+ """Normalize the distance to a score on a scale [0, 1]."""
398
+ if distance > 0:
399
+ return 1.0 - distance
400
+
401
+ return -1.0 * distance
402
+
403
+ def _select_relevance_score_fn(self) -> Callable[[float], float]:
404
+ """The 'correct' relevance function.
405
+
406
+ May differ depending on a few things, including:
407
+
408
+ - The distance / similarity metric used by the VectorStore
409
+ - The scale of your embeddings (OpenAI's are unit normed. Many others are not!)
410
+ - Embedding dimensionality
411
+ - etc.
412
+
413
+ Vectorstores should define their own selection-based method of relevance.
414
+ """
415
+ raise NotImplementedError
416
+
417
+ def similarity_search_with_score(
418
+ self, *args: Any, **kwargs: Any
419
+ ) -> list[tuple[Document, float]]:
420
+ """Run similarity search with distance.
421
+
422
+ Args:
423
+ *args: Arguments to pass to the search method.
424
+ **kwargs: Arguments to pass to the search method.
425
+
426
+ Returns:
427
+ List of tuples of `(doc, similarity_score)`.
428
+ """
429
+ raise NotImplementedError
430
+
431
+ async def asimilarity_search_with_score(
432
+ self, *args: Any, **kwargs: Any
433
+ ) -> list[tuple[Document, float]]:
434
+ """Async run similarity search with distance.
435
+
436
+ Args:
437
+ *args: Arguments to pass to the search method.
438
+ **kwargs: Arguments to pass to the search method.
439
+
440
+ Returns:
441
+ List of tuples of `(doc, similarity_score)`.
442
+ """
443
+ # This is a temporary workaround to make the similarity search
444
+ # asynchronous. The proper solution is to make the similarity search
445
+ # asynchronous in the vector store implementations.
446
+ return await run_in_executor(
447
+ None, self.similarity_search_with_score, *args, **kwargs
448
+ )
449
+
450
+ def _similarity_search_with_relevance_scores(
451
+ self,
452
+ query: str,
453
+ k: int = 4,
454
+ **kwargs: Any,
455
+ ) -> list[tuple[Document, float]]:
456
+ """Default similarity search with relevance scores.
457
+
458
+ Modify if necessary in subclass.
459
+ Return docs and relevance scores in the range `[0, 1]`.
460
+
461
+ `0` is dissimilar, `1` is most similar.
462
+
463
+ Args:
464
+ query: Input text.
465
+ k: Number of `Document` objects to return.
466
+ **kwargs: Kwargs to be passed to similarity search.
467
+
468
+ Should include `score_threshold`, an optional floating point value
469
+ between `0` to `1` to filter the resulting set of retrieved docs.
470
+
471
+ Returns:
472
+ List of tuples of `(doc, similarity_score)`
473
+ """
474
+ relevance_score_fn = self._select_relevance_score_fn()
475
+ docs_and_scores = self.similarity_search_with_score(query, k, **kwargs)
476
+ return [(doc, relevance_score_fn(score)) for doc, score in docs_and_scores]
477
+
478
+ async def _asimilarity_search_with_relevance_scores(
479
+ self,
480
+ query: str,
481
+ k: int = 4,
482
+ **kwargs: Any,
483
+ ) -> list[tuple[Document, float]]:
484
+ """Default similarity search with relevance scores.
485
+
486
+ Modify if necessary in subclass.
487
+ Return docs and relevance scores in the range `[0, 1]`.
488
+
489
+ `0` is dissimilar, `1` is most similar.
490
+
491
+ Args:
492
+ query: Input text.
493
+ k: Number of `Document` objects to return.
494
+ **kwargs: Kwargs to be passed to similarity search.
495
+
496
+ Should include `score_threshold`, an optional floating point value
497
+ between `0` to `1` to filter the resulting set of retrieved docs.
498
+
499
+ Returns:
500
+ List of tuples of `(doc, similarity_score)`
501
+ """
502
+ relevance_score_fn = self._select_relevance_score_fn()
503
+ docs_and_scores = await self.asimilarity_search_with_score(query, k, **kwargs)
504
+ return [(doc, relevance_score_fn(score)) for doc, score in docs_and_scores]
505
+
506
+ def similarity_search_with_relevance_scores(
507
+ self,
508
+ query: str,
509
+ k: int = 4,
510
+ **kwargs: Any,
511
+ ) -> list[tuple[Document, float]]:
512
+ """Return docs and relevance scores in the range `[0, 1]`.
513
+
514
+ `0` is dissimilar, `1` is most similar.
515
+
516
+ Args:
517
+ query: Input text.
518
+ k: Number of `Document` objects to return.
519
+ **kwargs: Kwargs to be passed to similarity search.
520
+
521
+ Should include `score_threshold`, an optional floating point value
522
+ between `0` to `1` to filter the resulting set of retrieved docs.
523
+
524
+ Returns:
525
+ List of tuples of `(doc, similarity_score)`.
526
+ """
527
+ score_threshold = kwargs.pop("score_threshold", None)
528
+
529
+ docs_and_similarities = self._similarity_search_with_relevance_scores(
530
+ query, k=k, **kwargs
531
+ )
532
+ if any(
533
+ similarity < 0.0 or similarity > 1.0
534
+ for _, similarity in docs_and_similarities
535
+ ):
536
+ warnings.warn(
537
+ "Relevance scores must be between"
538
+ f" 0 and 1, got {docs_and_similarities}",
539
+ stacklevel=2,
540
+ )
541
+
542
+ if score_threshold is not None:
543
+ docs_and_similarities = [
544
+ (doc, similarity)
545
+ for doc, similarity in docs_and_similarities
546
+ if similarity >= score_threshold
547
+ ]
548
+ if len(docs_and_similarities) == 0:
549
+ logger.warning(
550
+ "No relevant docs were retrieved using the "
551
+ "relevance score threshold %s",
552
+ score_threshold,
553
+ )
554
+ return docs_and_similarities
555
+
556
+ async def asimilarity_search_with_relevance_scores(
557
+ self,
558
+ query: str,
559
+ k: int = 4,
560
+ **kwargs: Any,
561
+ ) -> list[tuple[Document, float]]:
562
+ """Async return docs and relevance scores in the range `[0, 1]`.
563
+
564
+ `0` is dissimilar, `1` is most similar.
565
+
566
+ Args:
567
+ query: Input text.
568
+ k: Number of `Document` objects to return.
569
+ **kwargs: Kwargs to be passed to similarity search.
570
+
571
+ Should include `score_threshold`, an optional floating point value
572
+ between `0` to `1` to filter the resulting set of retrieved docs.
573
+
574
+ Returns:
575
+ List of tuples of `(doc, similarity_score)`
576
+ """
577
+ score_threshold = kwargs.pop("score_threshold", None)
578
+
579
+ docs_and_similarities = await self._asimilarity_search_with_relevance_scores(
580
+ query, k=k, **kwargs
581
+ )
582
+ if any(
583
+ similarity < 0.0 or similarity > 1.0
584
+ for _, similarity in docs_and_similarities
585
+ ):
586
+ warnings.warn(
587
+ "Relevance scores must be between"
588
+ f" 0 and 1, got {docs_and_similarities}",
589
+ stacklevel=2,
590
+ )
591
+
592
+ if score_threshold is not None:
593
+ docs_and_similarities = [
594
+ (doc, similarity)
595
+ for doc, similarity in docs_and_similarities
596
+ if similarity >= score_threshold
597
+ ]
598
+ if len(docs_and_similarities) == 0:
599
+ logger.warning(
600
+ "No relevant docs were retrieved using the "
601
+ "relevance score threshold %s",
602
+ score_threshold,
603
+ )
604
+ return docs_and_similarities
605
+
606
+ async def asimilarity_search(
607
+ self, query: str, k: int = 4, **kwargs: Any
608
+ ) -> list[Document]:
609
+ """Async return docs most similar to query.
610
+
611
+ Args:
612
+ query: Input text.
613
+ k: Number of `Document` objects to return.
614
+ **kwargs: Arguments to pass to the search method.
615
+
616
+ Returns:
617
+ List of `Document` objects most similar to the query.
618
+ """
619
+ # This is a temporary workaround to make the similarity search
620
+ # asynchronous. The proper solution is to make the similarity search
621
+ # asynchronous in the vector store implementations.
622
+ return await run_in_executor(None, self.similarity_search, query, k=k, **kwargs)
623
+
624
+ def similarity_search_by_vector(
625
+ self, embedding: list[float], k: int = 4, **kwargs: Any
626
+ ) -> list[Document]:
627
+ """Return docs most similar to embedding vector.
628
+
629
+ Args:
630
+ embedding: Embedding to look up documents similar to.
631
+ k: Number of `Document` objects to return.
632
+ **kwargs: Arguments to pass to the search method.
633
+
634
+ Returns:
635
+ List of `Document` objects most similar to the query vector.
636
+ """
637
+ raise NotImplementedError
638
+
639
+ async def asimilarity_search_by_vector(
640
+ self, embedding: list[float], k: int = 4, **kwargs: Any
641
+ ) -> list[Document]:
642
+ """Async return docs most similar to embedding vector.
643
+
644
+ Args:
645
+ embedding: Embedding to look up documents similar to.
646
+ k: Number of `Document` objects to return.
647
+ **kwargs: Arguments to pass to the search method.
648
+
649
+ Returns:
650
+ List of `Document` objects most similar to the query vector.
651
+ """
652
+ # This is a temporary workaround to make the similarity search
653
+ # asynchronous. The proper solution is to make the similarity search
654
+ # asynchronous in the vector store implementations.
655
+ return await run_in_executor(
656
+ None, self.similarity_search_by_vector, embedding, k=k, **kwargs
657
+ )
658
+
659
+ def max_marginal_relevance_search(
660
+ self,
661
+ query: str,
662
+ k: int = 4,
663
+ fetch_k: int = 20,
664
+ lambda_mult: float = 0.5,
665
+ **kwargs: Any,
666
+ ) -> list[Document]:
667
+ """Return docs selected using the maximal marginal relevance.
668
+
669
+ Maximal marginal relevance optimizes for similarity to query AND diversity
670
+ among selected documents.
671
+
672
+ Args:
673
+ query: Text to look up documents similar to.
674
+ k: Number of `Document` objects to return.
675
+ fetch_k: Number of `Document` objects to fetch to pass to MMR algorithm.
676
+ lambda_mult: Number between `0` and `1` that determines the degree of
677
+ diversity among the results with `0` corresponding to maximum diversity
678
+ and `1` to minimum diversity.
679
+ **kwargs: Arguments to pass to the search method.
680
+
681
+ Returns:
682
+ List of `Document` objects selected by maximal marginal relevance.
683
+ """
684
+ raise NotImplementedError
685
+
686
+ async def amax_marginal_relevance_search(
687
+ self,
688
+ query: str,
689
+ k: int = 4,
690
+ fetch_k: int = 20,
691
+ lambda_mult: float = 0.5,
692
+ **kwargs: Any,
693
+ ) -> list[Document]:
694
+ """Async return docs selected using the maximal marginal relevance.
695
+
696
+ Maximal marginal relevance optimizes for similarity to query AND diversity
697
+ among selected documents.
698
+
699
+ Args:
700
+ query: Text to look up documents similar to.
701
+ k: Number of `Document` objects to return.
702
+ fetch_k: Number of `Document` objects to fetch to pass to MMR algorithm.
703
+ lambda_mult: Number between `0` and `1` that determines the degree of
704
+ diversity among the results with `0` corresponding to maximum diversity
705
+ and `1` to minimum diversity.
706
+ **kwargs: Arguments to pass to the search method.
707
+
708
+ Returns:
709
+ List of `Document` objects selected by maximal marginal relevance.
710
+ """
711
+ # This is a temporary workaround to make the similarity search
712
+ # asynchronous. The proper solution is to make the similarity search
713
+ # asynchronous in the vector store implementations.
714
+ return await run_in_executor(
715
+ None,
716
+ self.max_marginal_relevance_search,
717
+ query,
718
+ k=k,
719
+ fetch_k=fetch_k,
720
+ lambda_mult=lambda_mult,
721
+ **kwargs,
722
+ )
723
+
724
+ def max_marginal_relevance_search_by_vector(
725
+ self,
726
+ embedding: list[float],
727
+ k: int = 4,
728
+ fetch_k: int = 20,
729
+ lambda_mult: float = 0.5,
730
+ **kwargs: Any,
731
+ ) -> list[Document]:
732
+ """Return docs selected using the maximal marginal relevance.
733
+
734
+ Maximal marginal relevance optimizes for similarity to query AND diversity
735
+ among selected documents.
736
+
737
+ Args:
738
+ embedding: Embedding to look up documents similar to.
739
+ k: Number of `Document` objects to return.
740
+ fetch_k: Number of `Document` objects to fetch to pass to MMR algorithm.
741
+ lambda_mult: Number between `0` and `1` that determines the degree of
742
+ diversity among the results with `0` corresponding to maximum diversity
743
+ and `1` to minimum diversity.
744
+ **kwargs: Arguments to pass to the search method.
745
+
746
+ Returns:
747
+ List of `Document` objects selected by maximal marginal relevance.
748
+ """
749
+ raise NotImplementedError
750
+
751
+ async def amax_marginal_relevance_search_by_vector(
752
+ self,
753
+ embedding: list[float],
754
+ k: int = 4,
755
+ fetch_k: int = 20,
756
+ lambda_mult: float = 0.5,
757
+ **kwargs: Any,
758
+ ) -> list[Document]:
759
+ """Async return docs selected using the maximal marginal relevance.
760
+
761
+ Maximal marginal relevance optimizes for similarity to query AND diversity
762
+ among selected documents.
763
+
764
+ Args:
765
+ embedding: Embedding to look up documents similar to.
766
+ k: Number of `Document` objects to return.
767
+ fetch_k: Number of `Document` objects to fetch to pass to MMR algorithm.
768
+ lambda_mult: Number between `0` and `1` that determines the degree of
769
+ diversity among the results with `0` corresponding to maximum diversity
770
+ and `1` to minimum diversity.
771
+ **kwargs: Arguments to pass to the search method.
772
+
773
+ Returns:
774
+ List of `Document` objects selected by maximal marginal relevance.
775
+ """
776
+ return await run_in_executor(
777
+ None,
778
+ self.max_marginal_relevance_search_by_vector,
779
+ embedding,
780
+ k=k,
781
+ fetch_k=fetch_k,
782
+ lambda_mult=lambda_mult,
783
+ **kwargs,
784
+ )
785
+
786
+ @classmethod
787
+ def from_documents(
788
+ cls,
789
+ documents: list[Document],
790
+ embedding: Embeddings,
791
+ **kwargs: Any,
792
+ ) -> Self:
793
+ """Return `VectorStore` initialized from documents and embeddings.
794
+
795
+ Args:
796
+ documents: List of `Document` objects to add to the `VectorStore`.
797
+ embedding: Embedding function to use.
798
+ **kwargs: Additional keyword arguments.
799
+
800
+ Returns:
801
+ `VectorStore` initialized from documents and embeddings.
802
+ """
803
+ texts = [d.page_content for d in documents]
804
+ metadatas = [d.metadata for d in documents]
805
+
806
+ if "ids" not in kwargs:
807
+ ids = [doc.id for doc in documents]
808
+
809
+ # If there's at least one valid ID, we'll assume that IDs
810
+ # should be used.
811
+ if any(ids):
812
+ kwargs["ids"] = ids
813
+
814
+ return cls.from_texts(texts, embedding, metadatas=metadatas, **kwargs)
815
+
816
+ @classmethod
817
+ async def afrom_documents(
818
+ cls,
819
+ documents: list[Document],
820
+ embedding: Embeddings,
821
+ **kwargs: Any,
822
+ ) -> Self:
823
+ """Async return `VectorStore` initialized from documents and embeddings.
824
+
825
+ Args:
826
+ documents: List of `Document` objects to add to the `VectorStore`.
827
+ embedding: Embedding function to use.
828
+ **kwargs: Additional keyword arguments.
829
+
830
+ Returns:
831
+ `VectorStore` initialized from documents and embeddings.
832
+ """
833
+ texts = [d.page_content for d in documents]
834
+ metadatas = [d.metadata for d in documents]
835
+
836
+ if "ids" not in kwargs:
837
+ ids = [doc.id for doc in documents]
838
+
839
+ # If there's at least one valid ID, we'll assume that IDs
840
+ # should be used.
841
+ if any(ids):
842
+ kwargs["ids"] = ids
843
+
844
+ return await cls.afrom_texts(texts, embedding, metadatas=metadatas, **kwargs)
845
+
846
+ @classmethod
847
+ @abstractmethod
848
+ def from_texts(
849
+ cls: type[VST],
850
+ texts: list[str],
851
+ embedding: Embeddings,
852
+ metadatas: list[dict[str, Any]] | None = None,
853
+ *,
854
+ ids: list[str] | None = None,
855
+ **kwargs: Any,
856
+ ) -> VST:
857
+ """Return `VectorStore` initialized from texts and embeddings.
858
+
859
+ Args:
860
+ texts: Texts to add to the `VectorStore`.
861
+ embedding: Embedding function to use.
862
+ metadatas: Optional list of metadatas associated with the texts.
863
+ ids: Optional list of IDs associated with the texts.
864
+ **kwargs: Additional keyword arguments.
865
+
866
+ Returns:
867
+ `VectorStore` initialized from texts and embeddings.
868
+ """
869
+
870
+ @classmethod
871
+ async def afrom_texts(
872
+ cls,
873
+ texts: list[str],
874
+ embedding: Embeddings,
875
+ metadatas: list[dict[str, Any]] | None = None,
876
+ *,
877
+ ids: list[str] | None = None,
878
+ **kwargs: Any,
879
+ ) -> Self:
880
+ """Async return `VectorStore` initialized from texts and embeddings.
881
+
882
+ Args:
883
+ texts: Texts to add to the `VectorStore`.
884
+ embedding: Embedding function to use.
885
+ metadatas: Optional list of metadatas associated with the texts.
886
+ ids: Optional list of IDs associated with the texts.
887
+ **kwargs: Additional keyword arguments.
888
+
889
+ Returns:
890
+ `VectorStore` initialized from texts and embeddings.
891
+ """
892
+ if ids is not None:
893
+ kwargs["ids"] = ids
894
+ return await run_in_executor(
895
+ None, cls.from_texts, texts, embedding, metadatas, **kwargs
896
+ )
897
+
898
+ def _get_retriever_tags(self) -> list[str]:
899
+ """Get tags for retriever."""
900
+ tags = [self.__class__.__name__]
901
+ if self.embeddings:
902
+ tags.append(self.embeddings.__class__.__name__)
903
+ return tags
904
+
905
+ def as_retriever(self, **kwargs: Any) -> VectorStoreRetriever:
906
+ """Return `VectorStoreRetriever` initialized from this `VectorStore`.
907
+
908
+ Args:
909
+ **kwargs: Keyword arguments to pass to the search function.
910
+
911
+ Can include:
912
+
913
+ * `search_type`: Defines the type of search that the Retriever should
914
+ perform. Can be `'similarity'` (default), `'mmr'`, or
915
+ `'similarity_score_threshold'`.
916
+ * `search_kwargs`: Keyword arguments to pass to the search function.
917
+
918
+ Can include things like:
919
+
920
+ * `k`: Amount of documents to return (Default: `4`)
921
+ * `score_threshold`: Minimum relevance threshold
922
+ for `similarity_score_threshold`
923
+ * `fetch_k`: Amount of documents to pass to MMR algorithm
924
+ (Default: `20`)
925
+ * `lambda_mult`: Diversity of results returned by MMR;
926
+ `1` for minimum diversity and 0 for maximum. (Default: `0.5`)
927
+ * `filter`: Filter by document metadata
928
+
929
+ Returns:
930
+ Retriever class for `VectorStore`.
931
+
932
+ Examples:
933
+ ```python
934
+ # Retrieve more documents with higher diversity
935
+ # Useful if your dataset has many similar documents
936
+ docsearch.as_retriever(
937
+ search_type="mmr", search_kwargs={"k": 6, "lambda_mult": 0.25}
938
+ )
939
+
940
+ # Fetch more documents for the MMR algorithm to consider
941
+ # But only return the top 5
942
+ docsearch.as_retriever(search_type="mmr", search_kwargs={"k": 5, "fetch_k": 50})
943
+
944
+ # Only retrieve documents that have a relevance score
945
+ # Above a certain threshold
946
+ docsearch.as_retriever(
947
+ search_type="similarity_score_threshold",
948
+ search_kwargs={"score_threshold": 0.8},
949
+ )
950
+
951
+ # Only get the single most similar document from the dataset
952
+ docsearch.as_retriever(search_kwargs={"k": 1})
953
+
954
+ # Use a filter to only retrieve documents from a specific paper
955
+ docsearch.as_retriever(
956
+ search_kwargs={"filter": {"paper_title": "GPT-4 Technical Report"}}
957
+ )
958
+ ```
959
+ """
960
+ tags = kwargs.pop("tags", None) or [*self._get_retriever_tags()]
961
+ return VectorStoreRetriever(vectorstore=self, tags=tags, **kwargs)
962
+
963
+
964
+ class VectorStoreRetriever(BaseRetriever):
965
+ """Base Retriever class for VectorStore."""
966
+
967
+ vectorstore: VectorStore
968
+ """VectorStore to use for retrieval."""
969
+
970
+ search_type: str = "similarity"
971
+ """Type of search to perform."""
972
+
973
+ search_kwargs: dict[str, Any] = Field(default_factory=dict)
974
+ """Keyword arguments to pass to the search function."""
975
+
976
+ allowed_search_types: ClassVar[Collection[str]] = (
977
+ "similarity",
978
+ "similarity_score_threshold",
979
+ "mmr",
980
+ )
981
+
982
+ model_config = ConfigDict(
983
+ arbitrary_types_allowed=True,
984
+ )
985
+
986
+ @model_validator(mode="before")
987
+ @classmethod
988
+ def validate_search_type(cls, values: dict[str, Any]) -> Any:
989
+ """Validate search type.
990
+
991
+ Args:
992
+ values: Values to validate.
993
+
994
+ Returns:
995
+ Validated values.
996
+
997
+ Raises:
998
+ ValueError: If `search_type` is not one of the allowed search types.
999
+ ValueError: If `score_threshold` is not specified with a float value(`0~1`)
1000
+ """
1001
+ search_type = values.get("search_type", "similarity")
1002
+ if search_type not in cls.allowed_search_types:
1003
+ msg = (
1004
+ f"search_type of {search_type} not allowed. Valid values are: "
1005
+ f"{cls.allowed_search_types}"
1006
+ )
1007
+ raise ValueError(msg)
1008
+ if search_type == "similarity_score_threshold":
1009
+ score_threshold = values.get("search_kwargs", {}).get("score_threshold")
1010
+ if (score_threshold is None) or (not isinstance(score_threshold, float)):
1011
+ msg = (
1012
+ "`score_threshold` is not specified with a float value(0~1) "
1013
+ "in `search_kwargs`."
1014
+ )
1015
+ raise ValueError(msg)
1016
+ return values
1017
+
1018
+ def _get_ls_params(self, **kwargs: Any) -> LangSmithRetrieverParams:
1019
+ """Get standard params for tracing."""
1020
+ kwargs_ = self.search_kwargs | kwargs
1021
+
1022
+ ls_params = super()._get_ls_params(**kwargs_)
1023
+
1024
+ ls_params["ls_vector_store_provider"] = self.vectorstore.__class__.__name__
1025
+
1026
+ if self.vectorstore.embeddings:
1027
+ ls_params["ls_embedding_provider"] = (
1028
+ self.vectorstore.embeddings.__class__.__name__
1029
+ )
1030
+ elif hasattr(self.vectorstore, "embedding") and isinstance(
1031
+ self.vectorstore.embedding, Embeddings
1032
+ ):
1033
+ ls_params["ls_embedding_provider"] = (
1034
+ self.vectorstore.embedding.__class__.__name__
1035
+ )
1036
+
1037
+ return ls_params
1038
+
1039
+ @override
1040
+ def _get_relevant_documents(
1041
+ self, query: str, *, run_manager: CallbackManagerForRetrieverRun, **kwargs: Any
1042
+ ) -> list[Document]:
1043
+ kwargs_ = self.search_kwargs | kwargs
1044
+ if self.search_type == "similarity":
1045
+ docs = self.vectorstore.similarity_search(query, **kwargs_)
1046
+ elif self.search_type == "similarity_score_threshold":
1047
+ docs_and_similarities = (
1048
+ self.vectorstore.similarity_search_with_relevance_scores(
1049
+ query, **kwargs_
1050
+ )
1051
+ )
1052
+ docs = [doc for doc, _ in docs_and_similarities]
1053
+ elif self.search_type == "mmr":
1054
+ docs = self.vectorstore.max_marginal_relevance_search(query, **kwargs_)
1055
+ else:
1056
+ msg = f"search_type of {self.search_type} not allowed."
1057
+ raise ValueError(msg)
1058
+ return docs
1059
+
1060
+ @override
1061
+ async def _aget_relevant_documents(
1062
+ self,
1063
+ query: str,
1064
+ *,
1065
+ run_manager: AsyncCallbackManagerForRetrieverRun,
1066
+ **kwargs: Any,
1067
+ ) -> list[Document]:
1068
+ kwargs_ = self.search_kwargs | kwargs
1069
+ if self.search_type == "similarity":
1070
+ docs = await self.vectorstore.asimilarity_search(query, **kwargs_)
1071
+ elif self.search_type == "similarity_score_threshold":
1072
+ docs_and_similarities = (
1073
+ await self.vectorstore.asimilarity_search_with_relevance_scores(
1074
+ query, **kwargs_
1075
+ )
1076
+ )
1077
+ docs = [doc for doc, _ in docs_and_similarities]
1078
+ elif self.search_type == "mmr":
1079
+ docs = await self.vectorstore.amax_marginal_relevance_search(
1080
+ query, **kwargs_
1081
+ )
1082
+ else:
1083
+ msg = f"search_type of {self.search_type} not allowed."
1084
+ raise ValueError(msg)
1085
+ return docs
1086
+
1087
+ def add_documents(self, documents: list[Document], **kwargs: Any) -> list[str]:
1088
+ """Add documents to the `VectorStore`.
1089
+
1090
+ Args:
1091
+ documents: Documents to add to the `VectorStore`.
1092
+ **kwargs: Other keyword arguments that subclasses might use.
1093
+
1094
+ Returns:
1095
+ List of IDs of the added texts.
1096
+ """
1097
+ return self.vectorstore.add_documents(documents, **kwargs)
1098
+
1099
+ async def aadd_documents(
1100
+ self, documents: list[Document], **kwargs: Any
1101
+ ) -> list[str]:
1102
+ """Async add documents to the `VectorStore`.
1103
+
1104
+ Args:
1105
+ documents: Documents to add to the `VectorStore`.
1106
+ **kwargs: Other keyword arguments that subclasses might use.
1107
+
1108
+ Returns:
1109
+ List of IDs of the added texts.
1110
+ """
1111
+ return await self.vectorstore.aadd_documents(documents, **kwargs)