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.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- coco_openai/tools/custom_tool.py +111 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
"""In-memory vector store."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Any,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
|
|
15
|
+
from coco_core.documents import Document
|
|
16
|
+
from coco_core.load import dumpd, load
|
|
17
|
+
from coco_core.vectorstores import VectorStore
|
|
18
|
+
from coco_core.vectorstores.utils import _cosine_similarity as cosine_similarity
|
|
19
|
+
from coco_core.vectorstores.utils import maximal_marginal_relevance
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
23
|
+
|
|
24
|
+
from coco_core.embeddings import Embeddings
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
_HAS_NUMPY = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
_HAS_NUMPY = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InMemoryVectorStore(VectorStore):
|
|
35
|
+
"""In-memory vector store implementation.
|
|
36
|
+
|
|
37
|
+
Uses a dictionary, and computes cosine similarity for search using numpy.
|
|
38
|
+
|
|
39
|
+
Setup:
|
|
40
|
+
Install `trustcoco-core`.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -U trustcoco-core
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Key init args — indexing params:
|
|
47
|
+
|
|
48
|
+
* embedding_function: Embeddings
|
|
49
|
+
Embedding function to use.
|
|
50
|
+
|
|
51
|
+
Instantiate:
|
|
52
|
+
```python
|
|
53
|
+
from coco_core.vectorstores import InMemoryVectorStore
|
|
54
|
+
from coco_openai import OpenAIEmbeddings
|
|
55
|
+
|
|
56
|
+
vector_store = InMemoryVectorStore(OpenAIEmbeddings())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Add Documents:
|
|
60
|
+
```python
|
|
61
|
+
from coco_core.documents import Document
|
|
62
|
+
|
|
63
|
+
document_1 = Document(id="1", page_content="foo", metadata={"baz": "bar"})
|
|
64
|
+
document_2 = Document(id="2", page_content="thud", metadata={"bar": "baz"})
|
|
65
|
+
document_3 = Document(id="3", page_content="i will be deleted :(")
|
|
66
|
+
|
|
67
|
+
documents = [document_1, document_2, document_3]
|
|
68
|
+
vector_store.add_documents(documents=documents)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Inspect documents:
|
|
72
|
+
```python
|
|
73
|
+
top_n = 10
|
|
74
|
+
for index, (id, doc) in enumerate(vector_store.store.items()):
|
|
75
|
+
if index < top_n:
|
|
76
|
+
# docs have keys 'id', 'vector', 'text', 'metadata'
|
|
77
|
+
print(f"{id}: {doc['text']}")
|
|
78
|
+
else:
|
|
79
|
+
break
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Delete Documents:
|
|
83
|
+
```python
|
|
84
|
+
vector_store.delete(ids=["3"])
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Search:
|
|
88
|
+
```python
|
|
89
|
+
results = vector_store.similarity_search(query="thud", k=1)
|
|
90
|
+
for doc in results:
|
|
91
|
+
print(f"* {doc.page_content} [{doc.metadata}]")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```txt
|
|
95
|
+
* thud [{'bar': 'baz'}]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Search with filter:
|
|
99
|
+
```python
|
|
100
|
+
def _filter_function(doc: Document) -> bool:
|
|
101
|
+
return doc.metadata.get("bar") == "baz"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
results = vector_store.similarity_search(
|
|
105
|
+
query="thud", k=1, filter=_filter_function
|
|
106
|
+
)
|
|
107
|
+
for doc in results:
|
|
108
|
+
print(f"* {doc.page_content} [{doc.metadata}]")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```txt
|
|
112
|
+
* thud [{'bar': 'baz'}]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Search with score:
|
|
116
|
+
```python
|
|
117
|
+
results = vector_store.similarity_search_with_score(query="qux", k=1)
|
|
118
|
+
for doc, score in results:
|
|
119
|
+
print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```txt
|
|
123
|
+
* [SIM=0.832268] foo [{'baz': 'bar'}]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Async:
|
|
127
|
+
```python
|
|
128
|
+
# add documents
|
|
129
|
+
# await vector_store.aadd_documents(documents=documents)
|
|
130
|
+
|
|
131
|
+
# delete documents
|
|
132
|
+
# await vector_store.adelete(ids=["3"])
|
|
133
|
+
|
|
134
|
+
# search
|
|
135
|
+
# results = vector_store.asimilarity_search(query="thud", k=1)
|
|
136
|
+
|
|
137
|
+
# search with score
|
|
138
|
+
results = await vector_store.asimilarity_search_with_score(query="qux", k=1)
|
|
139
|
+
for doc, score in results:
|
|
140
|
+
print(f"* [SIM={score:3f}] {doc.page_content} [{doc.metadata}]")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```txt
|
|
144
|
+
* [SIM=0.832268] foo [{'baz': 'bar'}]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use as Retriever:
|
|
148
|
+
```python
|
|
149
|
+
retriever = vector_store.as_retriever(
|
|
150
|
+
search_type="mmr",
|
|
151
|
+
search_kwargs={"k": 1, "fetch_k": 2, "lambda_mult": 0.5},
|
|
152
|
+
)
|
|
153
|
+
retriever.invoke("thud")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```txt
|
|
157
|
+
[Document(id='2', metadata={'bar': 'baz'}, page_content='thud')]
|
|
158
|
+
```
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, embedding: Embeddings) -> None:
|
|
162
|
+
"""Initialize with the given embedding function.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
embedding: embedding function to use.
|
|
166
|
+
"""
|
|
167
|
+
# TODO: would be nice to change to
|
|
168
|
+
# dict[str, Document] at some point (will be a breaking change)
|
|
169
|
+
self.store: dict[str, dict[str, Any]] = {}
|
|
170
|
+
self.embedding = embedding
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
@override
|
|
174
|
+
def embeddings(self) -> Embeddings:
|
|
175
|
+
return self.embedding
|
|
176
|
+
|
|
177
|
+
@override
|
|
178
|
+
def delete(self, ids: Sequence[str] | None = None, **kwargs: Any) -> None:
|
|
179
|
+
if ids:
|
|
180
|
+
for id_ in ids:
|
|
181
|
+
self.store.pop(id_, None)
|
|
182
|
+
|
|
183
|
+
@override
|
|
184
|
+
async def adelete(self, ids: Sequence[str] | None = None, **kwargs: Any) -> None:
|
|
185
|
+
self.delete(ids)
|
|
186
|
+
|
|
187
|
+
@override
|
|
188
|
+
def add_documents(
|
|
189
|
+
self,
|
|
190
|
+
documents: list[Document],
|
|
191
|
+
ids: list[str] | None = None,
|
|
192
|
+
**kwargs: Any,
|
|
193
|
+
) -> list[str]:
|
|
194
|
+
texts = [doc.page_content for doc in documents]
|
|
195
|
+
vectors = self.embedding.embed_documents(texts)
|
|
196
|
+
|
|
197
|
+
if ids and len(ids) != len(texts):
|
|
198
|
+
msg = (
|
|
199
|
+
f"ids must be the same length as texts. "
|
|
200
|
+
f"Got {len(ids)} ids and {len(texts)} texts."
|
|
201
|
+
)
|
|
202
|
+
raise ValueError(msg)
|
|
203
|
+
|
|
204
|
+
id_iterator: Iterator[str | None] = (
|
|
205
|
+
iter(ids) if ids else iter(doc.id for doc in documents)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
ids_ = []
|
|
209
|
+
|
|
210
|
+
for doc, vector in zip(documents, vectors, strict=False):
|
|
211
|
+
doc_id = next(id_iterator)
|
|
212
|
+
doc_id_ = doc_id or str(uuid.uuid4())
|
|
213
|
+
ids_.append(doc_id_)
|
|
214
|
+
self.store[doc_id_] = {
|
|
215
|
+
"id": doc_id_,
|
|
216
|
+
"vector": vector,
|
|
217
|
+
"text": doc.page_content,
|
|
218
|
+
"metadata": doc.metadata,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return ids_
|
|
222
|
+
|
|
223
|
+
@override
|
|
224
|
+
async def aadd_documents(
|
|
225
|
+
self, documents: list[Document], ids: list[str] | None = None, **kwargs: Any
|
|
226
|
+
) -> list[str]:
|
|
227
|
+
texts = [doc.page_content for doc in documents]
|
|
228
|
+
vectors = await self.embedding.aembed_documents(texts)
|
|
229
|
+
|
|
230
|
+
if ids and len(ids) != len(texts):
|
|
231
|
+
msg = (
|
|
232
|
+
f"ids must be the same length as texts. "
|
|
233
|
+
f"Got {len(ids)} ids and {len(texts)} texts."
|
|
234
|
+
)
|
|
235
|
+
raise ValueError(msg)
|
|
236
|
+
|
|
237
|
+
id_iterator: Iterator[str | None] = (
|
|
238
|
+
iter(ids) if ids else iter(doc.id for doc in documents)
|
|
239
|
+
)
|
|
240
|
+
ids_: list[str] = []
|
|
241
|
+
|
|
242
|
+
for doc, vector in zip(documents, vectors, strict=False):
|
|
243
|
+
doc_id = next(id_iterator)
|
|
244
|
+
doc_id_ = doc_id or str(uuid.uuid4())
|
|
245
|
+
ids_.append(doc_id_)
|
|
246
|
+
self.store[doc_id_] = {
|
|
247
|
+
"id": doc_id_,
|
|
248
|
+
"vector": vector,
|
|
249
|
+
"text": doc.page_content,
|
|
250
|
+
"metadata": doc.metadata,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return ids_
|
|
254
|
+
|
|
255
|
+
@override
|
|
256
|
+
def get_by_ids(self, ids: Sequence[str], /) -> list[Document]:
|
|
257
|
+
"""Get documents by their ids.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
ids: The IDs of the documents to get.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
A list of `Document` objects.
|
|
264
|
+
"""
|
|
265
|
+
documents = []
|
|
266
|
+
|
|
267
|
+
for doc_id in ids:
|
|
268
|
+
doc = self.store.get(doc_id)
|
|
269
|
+
if doc:
|
|
270
|
+
documents.append(
|
|
271
|
+
Document(
|
|
272
|
+
id=doc["id"],
|
|
273
|
+
page_content=doc["text"],
|
|
274
|
+
metadata=doc["metadata"],
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
return documents
|
|
278
|
+
|
|
279
|
+
@override
|
|
280
|
+
async def aget_by_ids(self, ids: Sequence[str], /) -> list[Document]:
|
|
281
|
+
"""Async get documents by their ids.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
ids: The IDs of the documents to get.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
A list of `Document` objects.
|
|
288
|
+
"""
|
|
289
|
+
return self.get_by_ids(ids)
|
|
290
|
+
|
|
291
|
+
def _similarity_search_with_score_by_vector(
|
|
292
|
+
self,
|
|
293
|
+
embedding: list[float],
|
|
294
|
+
k: int = 4,
|
|
295
|
+
filter: Callable[[Document], bool] | None = None, # noqa: A002
|
|
296
|
+
) -> list[tuple[Document, float, list[float]]]:
|
|
297
|
+
# Get all docs with fixed order in list
|
|
298
|
+
docs = list(self.store.values())
|
|
299
|
+
|
|
300
|
+
if filter is not None:
|
|
301
|
+
docs = [
|
|
302
|
+
doc
|
|
303
|
+
for doc in docs
|
|
304
|
+
if filter(
|
|
305
|
+
Document(
|
|
306
|
+
id=doc["id"], page_content=doc["text"], metadata=doc["metadata"]
|
|
307
|
+
)
|
|
308
|
+
)
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
if not docs:
|
|
312
|
+
return []
|
|
313
|
+
|
|
314
|
+
similarity = cosine_similarity([embedding], [doc["vector"] for doc in docs])[0]
|
|
315
|
+
|
|
316
|
+
# Get the indices ordered by similarity score
|
|
317
|
+
top_k_idx = similarity.argsort()[::-1][:k]
|
|
318
|
+
|
|
319
|
+
return [
|
|
320
|
+
(
|
|
321
|
+
Document(
|
|
322
|
+
id=doc_dict["id"],
|
|
323
|
+
page_content=doc_dict["text"],
|
|
324
|
+
metadata=doc_dict["metadata"],
|
|
325
|
+
),
|
|
326
|
+
float(similarity[idx].item()),
|
|
327
|
+
doc_dict["vector"],
|
|
328
|
+
)
|
|
329
|
+
for idx in top_k_idx
|
|
330
|
+
# Assign using walrus operator to avoid multiple lookups
|
|
331
|
+
if (doc_dict := docs[idx])
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
def similarity_search_with_score_by_vector(
|
|
335
|
+
self,
|
|
336
|
+
embedding: list[float],
|
|
337
|
+
k: int = 4,
|
|
338
|
+
filter: Callable[[Document], bool] | None = None, # noqa: A002
|
|
339
|
+
**_kwargs: Any,
|
|
340
|
+
) -> list[tuple[Document, float]]:
|
|
341
|
+
"""Search for the most similar documents to the given embedding.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
embedding: The embedding to search for.
|
|
345
|
+
k: The number of documents to return.
|
|
346
|
+
filter: A function to filter the documents.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
A list of tuples of `Document` objects and their similarity scores.
|
|
350
|
+
"""
|
|
351
|
+
return [
|
|
352
|
+
(doc, similarity)
|
|
353
|
+
for doc, similarity, _ in self._similarity_search_with_score_by_vector(
|
|
354
|
+
embedding=embedding, k=k, filter=filter
|
|
355
|
+
)
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
@override
|
|
359
|
+
def similarity_search_with_score(
|
|
360
|
+
self,
|
|
361
|
+
query: str,
|
|
362
|
+
k: int = 4,
|
|
363
|
+
**kwargs: Any,
|
|
364
|
+
) -> list[tuple[Document, float]]:
|
|
365
|
+
embedding = self.embedding.embed_query(query)
|
|
366
|
+
return self.similarity_search_with_score_by_vector(
|
|
367
|
+
embedding,
|
|
368
|
+
k,
|
|
369
|
+
**kwargs,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
@override
|
|
373
|
+
async def asimilarity_search_with_score(
|
|
374
|
+
self, query: str, k: int = 4, **kwargs: Any
|
|
375
|
+
) -> list[tuple[Document, float]]:
|
|
376
|
+
embedding = await self.embedding.aembed_query(query)
|
|
377
|
+
return self.similarity_search_with_score_by_vector(
|
|
378
|
+
embedding,
|
|
379
|
+
k,
|
|
380
|
+
**kwargs,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
@override
|
|
384
|
+
def similarity_search_by_vector(
|
|
385
|
+
self,
|
|
386
|
+
embedding: list[float],
|
|
387
|
+
k: int = 4,
|
|
388
|
+
**kwargs: Any,
|
|
389
|
+
) -> list[Document]:
|
|
390
|
+
docs_and_scores = self.similarity_search_with_score_by_vector(
|
|
391
|
+
embedding,
|
|
392
|
+
k,
|
|
393
|
+
**kwargs,
|
|
394
|
+
)
|
|
395
|
+
return [doc for doc, _ in docs_and_scores]
|
|
396
|
+
|
|
397
|
+
@override
|
|
398
|
+
async def asimilarity_search_by_vector(
|
|
399
|
+
self, embedding: list[float], k: int = 4, **kwargs: Any
|
|
400
|
+
) -> list[Document]:
|
|
401
|
+
return self.similarity_search_by_vector(embedding, k, **kwargs)
|
|
402
|
+
|
|
403
|
+
@override
|
|
404
|
+
def similarity_search(
|
|
405
|
+
self, query: str, k: int = 4, **kwargs: Any
|
|
406
|
+
) -> list[Document]:
|
|
407
|
+
return [doc for doc, _ in self.similarity_search_with_score(query, k, **kwargs)]
|
|
408
|
+
|
|
409
|
+
@override
|
|
410
|
+
async def asimilarity_search(
|
|
411
|
+
self, query: str, k: int = 4, **kwargs: Any
|
|
412
|
+
) -> list[Document]:
|
|
413
|
+
return [
|
|
414
|
+
doc
|
|
415
|
+
for doc, _ in await self.asimilarity_search_with_score(query, k, **kwargs)
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
@override
|
|
419
|
+
def max_marginal_relevance_search_by_vector(
|
|
420
|
+
self,
|
|
421
|
+
embedding: list[float],
|
|
422
|
+
k: int = 4,
|
|
423
|
+
fetch_k: int = 20,
|
|
424
|
+
lambda_mult: float = 0.5,
|
|
425
|
+
*,
|
|
426
|
+
filter: Callable[[Document], bool] | None = None,
|
|
427
|
+
**kwargs: Any,
|
|
428
|
+
) -> list[Document]:
|
|
429
|
+
prefetch_hits = self._similarity_search_with_score_by_vector(
|
|
430
|
+
embedding=embedding,
|
|
431
|
+
k=fetch_k,
|
|
432
|
+
filter=filter,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if not _HAS_NUMPY:
|
|
436
|
+
msg = (
|
|
437
|
+
"numpy must be installed to use max_marginal_relevance_search "
|
|
438
|
+
"pip install numpy"
|
|
439
|
+
)
|
|
440
|
+
raise ImportError(msg)
|
|
441
|
+
|
|
442
|
+
mmr_chosen_indices = maximal_marginal_relevance(
|
|
443
|
+
np.array(embedding, dtype=np.float32),
|
|
444
|
+
[vector for _, _, vector in prefetch_hits],
|
|
445
|
+
k=k,
|
|
446
|
+
lambda_mult=lambda_mult,
|
|
447
|
+
)
|
|
448
|
+
return [prefetch_hits[idx][0] for idx in mmr_chosen_indices]
|
|
449
|
+
|
|
450
|
+
@override
|
|
451
|
+
def max_marginal_relevance_search(
|
|
452
|
+
self,
|
|
453
|
+
query: str,
|
|
454
|
+
k: int = 4,
|
|
455
|
+
fetch_k: int = 20,
|
|
456
|
+
lambda_mult: float = 0.5,
|
|
457
|
+
**kwargs: Any,
|
|
458
|
+
) -> list[Document]:
|
|
459
|
+
embedding_vector = self.embedding.embed_query(query)
|
|
460
|
+
return self.max_marginal_relevance_search_by_vector(
|
|
461
|
+
embedding_vector,
|
|
462
|
+
k,
|
|
463
|
+
fetch_k,
|
|
464
|
+
lambda_mult=lambda_mult,
|
|
465
|
+
**kwargs,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
@override
|
|
469
|
+
async def amax_marginal_relevance_search(
|
|
470
|
+
self,
|
|
471
|
+
query: str,
|
|
472
|
+
k: int = 4,
|
|
473
|
+
fetch_k: int = 20,
|
|
474
|
+
lambda_mult: float = 0.5,
|
|
475
|
+
**kwargs: Any,
|
|
476
|
+
) -> list[Document]:
|
|
477
|
+
embedding_vector = await self.embedding.aembed_query(query)
|
|
478
|
+
return self.max_marginal_relevance_search_by_vector(
|
|
479
|
+
embedding_vector,
|
|
480
|
+
k,
|
|
481
|
+
fetch_k,
|
|
482
|
+
lambda_mult=lambda_mult,
|
|
483
|
+
**kwargs,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
@classmethod
|
|
487
|
+
@override
|
|
488
|
+
def from_texts(
|
|
489
|
+
cls,
|
|
490
|
+
texts: list[str],
|
|
491
|
+
embedding: Embeddings,
|
|
492
|
+
metadatas: list[dict[str, Any]] | None = None,
|
|
493
|
+
**kwargs: Any,
|
|
494
|
+
) -> InMemoryVectorStore:
|
|
495
|
+
store = cls(
|
|
496
|
+
embedding=embedding,
|
|
497
|
+
)
|
|
498
|
+
store.add_texts(texts=texts, metadatas=metadatas, **kwargs)
|
|
499
|
+
return store
|
|
500
|
+
|
|
501
|
+
@classmethod
|
|
502
|
+
@override
|
|
503
|
+
async def afrom_texts(
|
|
504
|
+
cls,
|
|
505
|
+
texts: list[str],
|
|
506
|
+
embedding: Embeddings,
|
|
507
|
+
metadatas: list[dict[str, Any]] | None = None,
|
|
508
|
+
**kwargs: Any,
|
|
509
|
+
) -> InMemoryVectorStore:
|
|
510
|
+
store = cls(
|
|
511
|
+
embedding=embedding,
|
|
512
|
+
)
|
|
513
|
+
await store.aadd_texts(texts=texts, metadatas=metadatas, **kwargs)
|
|
514
|
+
return store
|
|
515
|
+
|
|
516
|
+
@classmethod
|
|
517
|
+
def load(
|
|
518
|
+
cls, path: str, embedding: Embeddings, **kwargs: Any
|
|
519
|
+
) -> InMemoryVectorStore:
|
|
520
|
+
"""Load a vector store from a file.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
path: The path to load the vector store from.
|
|
524
|
+
embedding: The embedding to use.
|
|
525
|
+
**kwargs: Additional arguments to pass to the constructor.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
A `VectorStore` object.
|
|
529
|
+
"""
|
|
530
|
+
path_: Path = Path(path)
|
|
531
|
+
with path_.open("r", encoding="utf-8") as f:
|
|
532
|
+
store = load(json.load(f), allowed_objects=[Document])
|
|
533
|
+
vectorstore = cls(embedding=embedding, **kwargs)
|
|
534
|
+
vectorstore.store = store
|
|
535
|
+
return vectorstore
|
|
536
|
+
|
|
537
|
+
def dump(self, path: str) -> None:
|
|
538
|
+
"""Dump the vector store to a file.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
path: The path to dump the vector store to.
|
|
542
|
+
"""
|
|
543
|
+
path_: Path = Path(path)
|
|
544
|
+
path_.parent.mkdir(exist_ok=True, parents=True)
|
|
545
|
+
with path_.open("w", encoding="utf-8") as f:
|
|
546
|
+
json.dump(dumpd(self.store), f, indent=2)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Internal utilities for the in memory implementation of `VectorStore`.
|
|
2
|
+
|
|
3
|
+
!!! warning
|
|
4
|
+
|
|
5
|
+
These are part of a private API, and users should not use them directly as they can
|
|
6
|
+
change without notice.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import warnings
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
_HAS_NUMPY = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
_HAS_NUMPY = False
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import simsimd as simd # type: ignore[import-not-found]
|
|
24
|
+
|
|
25
|
+
_HAS_SIMSIMD = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
_HAS_SIMSIMD = False
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
import numpy.typing as npt
|
|
31
|
+
|
|
32
|
+
Matrix = (
|
|
33
|
+
list[list[float]] | list[npt.NDArray[np.floating]] | npt.NDArray[np.floating]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _cosine_similarity(x: Matrix, y: Matrix) -> npt.NDArray[np.floating]:
|
|
40
|
+
"""Row-wise cosine similarity between two equal-width matrices.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
x: A matrix of shape `(n, m)`.
|
|
44
|
+
y: A matrix of shape `(k, m)`.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
A matrix of shape `(n, k)` where each element `(i, j)` is the cosine similarity
|
|
48
|
+
between the `i`th row of `x` and the `j`th row of `y`.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If the number of columns in `x` and `y` are not the same.
|
|
52
|
+
ImportError: If numpy is not installed.
|
|
53
|
+
"""
|
|
54
|
+
if not _HAS_NUMPY:
|
|
55
|
+
msg = (
|
|
56
|
+
"cosine_similarity requires numpy to be installed. "
|
|
57
|
+
"Please install numpy with `pip install numpy`."
|
|
58
|
+
)
|
|
59
|
+
raise ImportError(msg)
|
|
60
|
+
|
|
61
|
+
if len(x) == 0 or len(y) == 0:
|
|
62
|
+
return np.array([[]])
|
|
63
|
+
|
|
64
|
+
x = np.array(x)
|
|
65
|
+
y = np.array(y)
|
|
66
|
+
|
|
67
|
+
# Check for NaN
|
|
68
|
+
if np.any(np.isnan(x)) or np.any(np.isnan(y)):
|
|
69
|
+
warnings.warn(
|
|
70
|
+
"NaN found in input arrays, unexpected return might follow",
|
|
71
|
+
category=RuntimeWarning,
|
|
72
|
+
stacklevel=2,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Check for Inf
|
|
76
|
+
if np.any(np.isinf(x)) or np.any(np.isinf(y)):
|
|
77
|
+
warnings.warn(
|
|
78
|
+
"Inf found in input arrays, unexpected return might follow",
|
|
79
|
+
category=RuntimeWarning,
|
|
80
|
+
stacklevel=2,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if x.shape[1] != y.shape[1]:
|
|
84
|
+
msg = (
|
|
85
|
+
f"Number of columns in X and Y must be the same. X has shape {x.shape} "
|
|
86
|
+
f"and Y has shape {y.shape}."
|
|
87
|
+
)
|
|
88
|
+
raise ValueError(msg)
|
|
89
|
+
if not _HAS_SIMSIMD:
|
|
90
|
+
logger.debug(
|
|
91
|
+
"Unable to import simsimd, defaulting to NumPy implementation. If you want "
|
|
92
|
+
"to use simsimd please install with `pip install simsimd`."
|
|
93
|
+
)
|
|
94
|
+
x_norm = np.linalg.norm(x, axis=1)
|
|
95
|
+
y_norm = np.linalg.norm(y, axis=1)
|
|
96
|
+
# Ignore divide by zero errors run time warnings as those are handled below.
|
|
97
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
98
|
+
similarity: npt.NDArray[np.floating] = np.dot(x, y.T) / np.outer(
|
|
99
|
+
x_norm, y_norm
|
|
100
|
+
)
|
|
101
|
+
if np.isnan(similarity).all():
|
|
102
|
+
msg = "NaN values found, please remove the NaN values and try again"
|
|
103
|
+
raise ValueError(msg) from None
|
|
104
|
+
similarity[np.isnan(similarity) | np.isinf(similarity)] = 0.0
|
|
105
|
+
return similarity
|
|
106
|
+
|
|
107
|
+
x = np.array(x, dtype=np.float32)
|
|
108
|
+
y = np.array(y, dtype=np.float32)
|
|
109
|
+
return 1 - np.array(simd.cdist(x, y, metric="cosine"))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def maximal_marginal_relevance(
|
|
113
|
+
query_embedding: npt.NDArray[np.floating],
|
|
114
|
+
embedding_list: list[list[float]],
|
|
115
|
+
lambda_mult: float = 0.5,
|
|
116
|
+
k: int = 4,
|
|
117
|
+
) -> list[int]:
|
|
118
|
+
"""Calculate maximal marginal relevance.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
query_embedding: The query embedding.
|
|
122
|
+
embedding_list: A list of embeddings.
|
|
123
|
+
lambda_mult: The lambda parameter for MMR.
|
|
124
|
+
k: The number of embeddings to return.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
A list of indices of the embeddings to return.
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
ImportError: If numpy is not installed.
|
|
131
|
+
"""
|
|
132
|
+
if not _HAS_NUMPY:
|
|
133
|
+
msg = (
|
|
134
|
+
"maximal_marginal_relevance requires numpy to be installed. "
|
|
135
|
+
"Please install numpy with `pip install numpy`."
|
|
136
|
+
)
|
|
137
|
+
raise ImportError(msg)
|
|
138
|
+
|
|
139
|
+
if min(k, len(embedding_list)) <= 0:
|
|
140
|
+
return []
|
|
141
|
+
if query_embedding.ndim == 1:
|
|
142
|
+
query_embedding = np.expand_dims(query_embedding, axis=0)
|
|
143
|
+
similarity_to_query = _cosine_similarity(query_embedding, embedding_list)[0]
|
|
144
|
+
most_similar = int(np.argmax(similarity_to_query))
|
|
145
|
+
idxs = [most_similar]
|
|
146
|
+
selected = np.array([embedding_list[most_similar]])
|
|
147
|
+
while len(idxs) < min(k, len(embedding_list)):
|
|
148
|
+
best_score = -np.inf
|
|
149
|
+
idx_to_add = -1
|
|
150
|
+
similarity_to_selected = _cosine_similarity(embedding_list, selected)
|
|
151
|
+
for i, query_score in enumerate(similarity_to_query):
|
|
152
|
+
if i in idxs:
|
|
153
|
+
continue
|
|
154
|
+
redundant_score = max(similarity_to_selected[i])
|
|
155
|
+
equation_score = (
|
|
156
|
+
lambda_mult * query_score - (1 - lambda_mult) * redundant_score
|
|
157
|
+
)
|
|
158
|
+
if equation_score > best_score:
|
|
159
|
+
best_score = equation_score
|
|
160
|
+
idx_to_add = i
|
|
161
|
+
idxs.append(idx_to_add)
|
|
162
|
+
selected = np.append(selected, [embedding_list[idx_to_add]], axis=0)
|
|
163
|
+
return idxs
|
coco_core/version.py
ADDED