fix(vector): address PR review round 2 — status branching, doc_id guard, doc restore
- _ensure_keyword_payload_indexes: distinguish 400 (schema conflict, warning)
from other status codes (5xx/network, error) so a transient outage doesn't
silently leave the collection unindexed.
- build_search_result_from_point: use .get("doc_id") + return None on missing
instead of KeyError-crashing the search; reverse metadata merge order so
payload-derived chunk_index/total_chunks win over caller-supplied extras.
- docs/configuration.md: restore the OpenAI/Mistral/Bedrock/Simple provider
sections + reference-table rows that were dropped in the rebase. Reword
the "Startup migrations" bullet to describe what the code actually does
(no sampling — full scroll, zero writes when clean). Add operator note
about the SemanticSearchResult.id TypeError path.
- tests: pytest.approx for float equality (Sonar python:S1244); coverage
for non-400 → ERROR, payload={doc_id: None}, and missing doc_id key.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
6aba589a6e
commit
b5b4025bb4
@@ -11,6 +11,8 @@ from nextcloud_mcp_server.config import get_settings
|
||||
from nextcloud_mcp_server.vector.placeholder import get_placeholder_filter
|
||||
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class NextcloudClientProtocol(Protocol):
|
||||
@@ -91,7 +93,6 @@ async def get_indexed_doc_types(user_id: str) -> set[str]:
|
||||
... # Search notes
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
settings = get_settings()
|
||||
|
||||
qdrant_client = await get_qdrant_client()
|
||||
@@ -205,15 +206,19 @@ def build_search_result_from_point(
|
||||
if point.payload is None:
|
||||
return None
|
||||
|
||||
doc_id = str(point.payload["doc_id"])
|
||||
raw_doc_id = point.payload.get("doc_id")
|
||||
if raw_doc_id is None:
|
||||
logger.warning("Skipping point %s: missing doc_id in payload", point.id)
|
||||
return None
|
||||
doc_id = str(raw_doc_id)
|
||||
doc_type = point.payload.get("doc_type", "note")
|
||||
|
||||
metadata: dict[str, Any] = {
|
||||
"chunk_index": point.payload.get("chunk_index"),
|
||||
"total_chunks": point.payload.get("total_chunks"),
|
||||
}
|
||||
if metadata_extras:
|
||||
metadata.update(metadata_extras)
|
||||
# Caller-supplied metadata is merged first; payload-derived common fields
|
||||
# (chunk_index, total_chunks) win in case of key collisions so they always
|
||||
# reflect the actual point.
|
||||
metadata: dict[str, Any] = dict(metadata_extras) if metadata_extras else {}
|
||||
metadata["chunk_index"] = point.payload.get("chunk_index")
|
||||
metadata["total_chunks"] = point.payload.get("total_chunks")
|
||||
|
||||
# File-specific metadata for PDF viewer
|
||||
if doc_type == "file" and (path := point.payload.get("file_path")):
|
||||
|
||||
@@ -46,9 +46,21 @@ async def _ensure_keyword_payload_indexes(
|
||||
except UnexpectedResponse as e:
|
||||
body = getattr(e, "content", b"") or b""
|
||||
body_text = body.decode("utf-8", errors="replace")
|
||||
logger.warning(
|
||||
"Failed to create payload index on '%s': %s", field, body_text
|
||||
)
|
||||
# 400 is the expected schema-conflict path (index already exists
|
||||
# with a different type). 5xx / network-shaped errors should not
|
||||
# be silently downgraded — keep the loop going so the remaining
|
||||
# fields still get attempted, but log at error so operators see it.
|
||||
if e.status_code == 400:
|
||||
logger.warning(
|
||||
"Schema conflict on payload index '%s': %s", field, body_text
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
"Unexpected error creating payload index on '%s' (status %s): %s",
|
||||
field,
|
||||
e.status_code,
|
||||
body_text,
|
||||
)
|
||||
|
||||
|
||||
async def _backfill_doc_id_to_string(
|
||||
|
||||
Reference in New Issue
Block a user