fix: fix embed paths (#1278)

* fix: fix embed paths

* tests: add local inference test for complex prefetch
This commit is contained in:
George
2026-08-04 21:15:23 +07:00
committed by George Panchuk
parent f28634c053
commit 2257c55b99
5 changed files with 62 additions and 1 deletions
+5 -1
View File
@@ -235,7 +235,11 @@ class ModelEmbedder:
current_model = getattr(item, path.current, None)
if current_model is None:
continue
if path.tail:
# a node with a tail can still hold an inference object itself,
# e.g. `prefetch.query` can be a Document, while `prefetch.query.nearest`
# can be a Document as well. If an inference object is found at the current
# node, treat it as a leaf, otherwise continue traversing the tail.
if path.tail and not isinstance(current_model, get_args(INFERENCE_OBJECT_TYPES)):
self._process_model(
current_model,
path.tail,
+8
View File
@@ -7,11 +7,16 @@ from pydantic import BaseModel, Field
class FieldPath(BaseModel):
current: str
tail: list["FieldPath"] | None = Field(default=None)
# marks the node as a valid endpoint, e.g. in ["a.b", "a.b.c"] both "b" and "c" are endpoints.
# Nodes without a tail are always endpoints, regardless of the flag value.
leaf: bool = Field(default=False)
def as_str_list(self) -> list[str]:
"""
>>> FieldPath(current='a', tail=[FieldPath(current='b', tail=[FieldPath(current='c'), FieldPath(current='d')])]).as_str_list()
['a.b.c', 'a.b.d']
>>> FieldPath(current='a', tail=[FieldPath(current='b', leaf=True, tail=[FieldPath(current='c')])]).as_str_list()
['a.b', 'a.b.c']
"""
# Recursive function to collect all paths
@@ -21,6 +26,8 @@ class FieldPath(BaseModel):
return [current_path]
else:
paths = []
if path.leaf:
paths.append(current_path)
for sub_path in path.tail:
paths.extend(collect_paths(sub_path, current_path + "."))
return paths
@@ -64,6 +71,7 @@ def convert_paths(paths: list[str]) -> list[FieldPath]:
assert current.tail is not None
current.tail.append(new_tail)
current = new_tail
current.leaf = True # the last node of each path is a valid endpoint
return converted_paths
+16
View File
@@ -391,6 +391,22 @@ def test_inspect_query_requests():
paths = inspector_embed.inspect([document_only_prefetch_request])
assert len(paths) == 1 and paths[0].as_str_list() == ["prefetch.query"]
# mixed direct Document and nested NearestQuery — both prefix paths must be kept
mixed_prefix_prefetch_request = models.QueryRequest(
query=doc,
prefetch=[
models.Prefetch(query=doc),
models.Prefetch(query=models.NearestQuery(nearest=doc)),
],
)
assert inspector.inspect(mixed_prefix_prefetch_request)
paths = inspector_embed.inspect(mixed_prefix_prefetch_request)
assert {path for field_path in paths for path in field_path.as_str_list()} == {
"query",
"prefetch.query",
"prefetch.query.nearest",
}
assert inspector.inspect([query_request_vector, document_only_query_request])
paths = inspector_embed.inspect([query_request_vector, document_only_query_request])
assert len(paths) == 1 and paths[0].as_str_list() == ["query"]
+28
View File
@@ -857,6 +857,34 @@ def test_query_batch_points(cached_embeddings):
current_requests[0].query.nearest.values,
atol=1e-3,
)
# Mix a Document directly in `prefetch.query` with one nested under NearestQuery
# (`prefetch.query.nearest`). Both must be embedded when processed in one batch.
mixed_prefix_requests = [
models.QueryRequest(
query=sparse_doc_1,
prefetch=[models.Prefetch(query=sparse_doc_2, limit=3, using="sparse-text")],
using="sparse-text",
),
models.QueryRequest(
query=models.NearestQuery(nearest=sparse_doc_3),
prefetch=[
models.Prefetch(
query=models.NearestQuery(nearest=sparse_doc_4),
limit=3,
using="sparse-text",
)
],
using="sparse-text",
),
]
local_client.query_batch_points(COLLECTION_NAME, mixed_prefix_requests)
current_requests = local_kwargs["requests"]
assert isinstance(current_requests[0].query.nearest, models.SparseVector)
assert isinstance(current_requests[0].prefetch[0].query, models.SparseVector)
assert isinstance(current_requests[1].query.nearest, models.SparseVector)
assert isinstance(current_requests[1].prefetch[0].query.nearest, models.SparseVector)
local_client.delete_collection(COLLECTION_NAME)
+5
View File
@@ -51,3 +51,8 @@ def test_parser(model):
count += check_path_recursive(plain_path.split("."), paths)
assert count == len(plain_paths)
# convert_paths must keep every plain path, including ones that are
# prefixes of longer paths (e.g. "query" and "query.nearest")
flattened = {path for field_path in paths for path in field_path.as_str_list()}
assert set(plain_paths) == flattened