diff --git a/services/docs/service.py b/services/docs/service.py index 5242aa5ce..d41e3a773 100644 --- a/services/docs/service.py +++ b/services/docs/service.py @@ -50,16 +50,46 @@ class DocsService: List of DocChunk objects """ results = self.rag.search(query, k=top_k) - return [ - DocChunk( - text=r.get("text", r.get("content", "")), - source=r.get("source", r.get("metadata", {}).get("source", "unknown")), - score=r.get("score", 0.0), - metadata=r.get("metadata"), + chunks = [] + + for result in results: + if not isinstance(result, dict): + continue + + metadata = result.get("metadata") + if not isinstance(metadata, dict): + metadata = {} + + text = result.get("document") + if text is None: + text = result.get("text") + if text is None: + text = result.get("content") + if text is None: + text = "" + + source = result.get("source") + if source is None: + source = metadata.get("source") + if source is None: + source = "unknown" + + score = result.get("similarity") + if score is None: + score = result.get("score") + if score is None: + score = 0.0 + + chunks.append( + DocChunk( + text=text, + source=source, + score=score, + metadata=metadata, + ) ) - for r in results - if isinstance(r, dict) - ] + + return chunks async def index(self, directory: str) -> IndexResult: """ @@ -73,8 +103,8 @@ class DocsService: """ result = self.rag.index_personal_documents(directory) return IndexResult( - indexed=result.get("indexed", 0), - failed=result.get("failed", 0), + indexed=result.get("indexed_count", result.get("indexed", 0)), + failed=result.get("failed_count", result.get("failed", 0)), errors=result.get("errors", []), ) diff --git a/tests/test_docs_query_nondict_rows.py b/tests/test_docs_query_nondict_rows.py index 91871f14b..2e2db98b1 100644 --- a/tests/test_docs_query_nondict_rows.py +++ b/tests/test_docs_query_nondict_rows.py @@ -9,11 +9,18 @@ class _FakeRag: def search(self, query, k=5): return [ - {"text": "alpha", "source": "a.txt", "score": 0.9}, + { + "document": "alpha", + "metadata": {"source": "a.txt"}, + "similarity": 0.9, + }, "corrupt-row", None, ] + def index_personal_documents(self, directory): + return {"indexed_count": 7, "failed_count": 2, "errors": ["bad.pdf"]} + def test_query_skips_non_dict_rag_rows(): # Bypass __init__ (it builds a real RAGManager / Chroma client) and inject @@ -24,3 +31,65 @@ def test_query_skips_non_dict_rag_rows(): # old code called r.get(...) on the str/None rows and raised AttributeError. assert [c.text for c in out] == ["alpha"] assert out[0].source == "a.txt" + assert out[0].score == 0.9 + + +def test_index_maps_live_vectorrag_result_shape(): + svc = DocsService.__new__(DocsService) + svc.rag = _FakeRag() + + out = asyncio.run(svc.index("/documents")) + + assert out.indexed == 7 + assert out.failed == 2 + assert out.errors == ["bad.pdf"] + + +def test_query_normalizes_null_canonical_fields_and_malformed_metadata(): + class _MalformedRag: + def search(self, query, k=5): + return [ + { + "document": None, + "text": "legacy text", + "source": None, + "similarity": None, + "score": 0.4, + "metadata": "not-a-dict", + }, + { + "document": "canonical zero", + "similarity": 0.0, + "metadata": {"source": "nested.txt"}, + }, + { + "document": None, + "text": None, + "content": "content fallback", + "similarity": 0.2, + "metadata": ["unexpected"], + }, + ] + + svc = DocsService.__new__(DocsService) + svc.rag = _MalformedRag() + + out = asyncio.run(svc.query("query")) + + assert [chunk.text for chunk in out] == [ + "legacy text", + "canonical zero", + "content fallback", + ] + + assert out[0].source == "unknown" + assert out[0].score == 0.4 + assert out[0].metadata == {} + + assert out[1].source == "nested.txt" + assert out[1].score == 0.0 + assert out[1].metadata == {"source": "nested.txt"} + + assert out[2].source == "unknown" + assert out[2].score == 0.2 + assert out[2].metadata == {}