""" Direct unit tests for `lambdagent.rag` — Paper III's headline RAG layer. audit/AUDIT_2026-06-05.md #35 baseline: 438 LOC of retrieval + RAG-tool + agentic-RAG code with ZERO direct test. The integration test paths in tests/test_extensions.py exercise Memory but not retrieval — a regression in TF-IDF scoring, in score-then-format glue, or in RAGTool's `Term` apply contract could ship to production with green CI. This file covers the dependency-free SimpleVectorStore + RAGTool + create_rag paths. ChromaDB-backed paths are excluded — they need an external service and that lifts test infra cost out of proportion; add an opt-in `tests/test_rag_chroma.py` if/when needed. """ from __future__ import annotations import pytest from lambdagent.rag import ( Document, SearchResult, SimpleVectorStore, RAGTool, create_rag, ) # ───────────────────────────────────────────────────────────────────────────── # SimpleVectorStore — pure data layer (no LLM) # ───────────────────────────────────────────────────────────────────────────── def test_simple_vector_store_add_returns_doc_id(): store = SimpleVectorStore() doc_id = store.add("Python is a programming language") assert isinstance(doc_id, str) and len(doc_id) > 0 assert len(store.documents) == 1 assert store.documents[0].content == "Python is a programming language" def test_simple_vector_store_search_ranks_relevant_first(): """TF-IDF cosine should rank a topically-on-target document above off-target ones. We use distinctive words so the result is deterministic regardless of tokenizer details.""" store = SimpleVectorStore() store.add("Lambda calculus is the foundation of computation theory") store.add("Python is a popular programming language for data science") store.add("The Eiffel Tower is in Paris and was built in 1889") results = store.search("what is lambda calculus", top_k=3) assert len(results) > 0, "no results returned at all" # The top-ranked result must be the lambda-calculus doc. assert "lambda" in results[0].document.content.lower(), ( f"top result was not the lambda doc; got: " f"{results[0].document.content[:80]}" ) # And it must outrank the unrelated Eiffel-Tower doc. scores_by_topic = { ("lambda" in r.document.content.lower()): r.score for r in results } if True in scores_by_topic and False in scores_by_topic: assert scores_by_topic[True] > scores_by_topic[False] def test_simple_vector_store_empty_query_returns_empty(): """Defense: an empty query shouldn't blow up — it should just return nothing useful. This guards a fragile Counter() path that used to ZeroDivisionError when the query tokenized to [].""" store = SimpleVectorStore() store.add("anything") results = store.search("", top_k=3) # Either an empty list or all-zero-score results are acceptable; # the contract is "no crash + nothing claimed relevant". if results: for r in results: assert r.score <= 0.0 + 1e-9 def test_simple_vector_store_no_documents_returns_empty(): store = SimpleVectorStore() results = store.search("anything", top_k=3) assert results == [] # ───────────────────────────────────────────────────────────────────────────── # RAGTool — wraps store as a lambdagent Term # ───────────────────────────────────────────────────────────────────────────── def test_ragtool_apply_returns_formatted_string(): """RAGTool must satisfy the Term apply contract: - input = query string - output = string suitable for injection into a downstream prompt The 'numbered' format produces `[Source N]` markers.""" store = SimpleVectorStore() store.add("Lambda calculus underlies functional programming") store.add("Lambda functions in Python are anonymous functions") rag = RAGTool(store, top_k=2, format="numbered") out = rag.apply("lambda") assert isinstance(out, str) # `numbered` format emits `[Source 1]` or `[Source 1, score=...]` # depending on rag.py's format flag. Both forms start with the same # prefix. assert "[Source 1" in out, f"output missing [Source 1 marker: {out[:120]}" def test_ragtool_min_score_filters_low_matches(): """A query that matches NOTHING in the store should yield an empty or near-empty output, not a hallucinated reference.""" store = SimpleVectorStore() store.add("Cats are friendly mammals") store.add("Dogs need daily walks") # min_score forces the filter even on the top-scored irrelevant doc. rag = RAGTool(store, top_k=3, min_score=0.5) out = rag.apply("quantum field theory renormalization") # Accept either an empty string or "no results found" message — # the contract is "don't lie about relevance". if out.strip(): assert "[Source 1]" not in out or "no results" in out.lower() # ───────────────────────────────────────────────────────────────────────────── # create_rag — public one-liner # ───────────────────────────────────────────────────────────────────────────── def test_create_rag_simple_backend_smoke(): """The README's headline 4-line RAG usage must keep working.""" rag = create_rag([ "Python is a programming language", "Lambda calculus is the foundation of computation", "AI agents are autonomous task executors", ]) assert isinstance(rag, RAGTool) out = rag.apply("what is lambda calculus") assert "[Source" in out, f"create_rag output missing source markers: {out[:120]}" def test_create_rag_unknown_backend_raises(): """Defensive: typo in backend name should not silently fall back.""" from lambdagent.core import LambdagentError with pytest.raises(LambdagentError): create_rag(["doc"], backend="not-a-real-backend")