Skip to content

Phase 8: Retrieval Layer #49

Description

@tinkermonkey

Implement retrieval/query.py with the retrieve function and get_lineage function that enable semantic search against the LanceDB vector index and full provenance lookup from the document store.

Requirements

  • Implement retrieve(query: str, embedder: Embedder, lance_db_path: Path, document_store: DocumentStore, top_k: int = 10) -> list[dict] function in retrieval/query.py:
    1. Embed the query: query_vector = embedder.embed_query(query)
    2. Open LanceDB at lance_db_path and access the chunk vectors table
    3. Perform nearest-neighbor search: results = table.search(query_vector).limit(top_k).to_list()
    4. Map each result to a dict: {"chunk_text": ..., "chunk_hash": ..., "source_id": ..., "score": ...}
    5. Return results ordered by descending relevance score
  • Implement get_lineage(chunk_hash: str, document_store: DocumentStore) -> LineageRecord | None function:
    • Calls document_store.get_lineage(chunk_hash) and returns the result
  • The retrieve function must accept top_k and return exactly top_k results (or fewer if fewer chunks exist)
  • Results must include: chunk_text (the chunk content text), chunk_hash, source_id, score (relevance score from LanceDB)
  • Create tests/retrieval/test_query.py with integration tests: index chunks, retrieve with query, verify result fields, verify ordering by score, verify lineage lookup

Design Guidance

  • retrieval/query.py already exists as a stub — implement the functions there
  • LanceDB open pattern: import lancedb; db = lancedb.connect(lance_db_path); table = db.open_table("chunk_vectors")
  • LanceDB search pattern: table.search(query_vector).limit(top_k).to_pandas() or .to_list() — the result includes a _distance column (lower = closer). Convert distance to score: score = 1.0 - result["_distance"] or use as-is and document the ordering semantics.
  • ChunkVector LanceDB model (from storage/vector_store.py) has fields: chunk_hash, content, source_id, embedding — map contentchunk_text in the result dict
  • The retrieve function is a module-level function, not a class method — keep it simple
  • get_lineage is also a module-level function
  • For integration tests: use a temp LanceDB directory, write known chunks via the pipeline or directly via VectorStore, then retrieve and verify
  • The retrieval layer has no business logic beyond embedding, searching, and mapping — keep it thin
  • Import: from context_library.storage.models import LineageRecord, from context_library.core.embedder import Embedder, from context_library.storage.document_store import DocumentStore

Acceptance Criteria

  • retrieve() returns a list of dicts with keys: chunk_text, chunk_hash, source_id, score
  • Results are ordered by descending relevance score
  • retrieve() returns at most top_k results; fewer if fewer chunks exist in the index
  • get_lineage() returns the correct LineageRecord for a given chunk_hash after ingest
  • get_lineage() returns None for an unknown chunk_hash
  • Integration test: ingest chunks → retrieve with a related query → verify top result has the expected source_id
  • tests/retrieval/test_query.py passes

Dependencies

Phase 6: Embedder, Phase 7: Ingestion Pipeline Orchestrator

Parent Issue

Part of #40

Discussion

This work is detailed in discussion 41

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions