Repository navigation
Async RAG pipeline with thread-safe FAISS and improved retrieval scoring - #1
Merged
Merged
Conversation
Retrieval quality - Convert FAISS squared-L2 distance into a real cosine similarity (0-1, higher = better) instead of reporting raw distance as a "score"; the old top_score was effectively inverted. - L2-normalize embeddings so cosine math is exact. - Add configurable RETRIEVAL_K (default 5, matching the README) and RETRIEVAL_SCORE_THRESHOLD to drop irrelevant chunks. - Short-circuit to a graceful "no relevant context" answer when nothing clears the threshold, instead of asking the analyst to invent an answer from no evidence. Multi-agent correctness - Fix the broken revision loop: the analyst now receives the critic's feedback and its previous answer on a revision pass, so the loop can actually improve the result instead of regenerating the same output. - Force JSON output (Ollama format=json) for the router/analyst/critic so structured parsing stops silently falling back to error placeholders. Concurrency / performance - Make all agent nodes async and use llm.ainvoke so blocking LLM calls no longer stall the event loop during SSE streaming. - Run FAISS search and document embedding off the event loop via asyncio.to_thread; cache LLM clients per configuration. Vector store - Implement delete_by_doc_id (was a no-op, so deleted docs kept being retrieved); guard mutations/reads with a lock for thread safety. - Remove confusing dead code in initialize(). Housekeeping - Add a pytest suite for the scoring/chunking/parsing logic. - Drop the obsolete GOOGLE_API_KEY from CI (project runs on local Ollama). - Apply black formatting and clear ruff unused-import/dead-variable errors so the lint/format CI steps pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013WDZTHsDvmhUax6fydeLwx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactored the RAG pipeline to be fully async, added thread-safety to FAISS operations, implemented proper L2-to-cosine similarity conversion, and improved retrieval quality with configurable score thresholds.
Key Changes
Core Pipeline & Async Support
router,retriever,analyst,critic,formatter) async functionsasyncio.to_thread()to keep the event loop responsiveroute_after_retrieval()conditional edge to short-circuit the pipeline when no relevant chunks are found, returning a user-friendly message instead of asking the analyst to synthesize from empty contextFAISS Thread Safety
threading.Lock()toVectorStoreServiceto guard all mutations and reads of the FAISS index and docstore_save_locked()internal method for callers that already hold the lockadd_documents,similarity_search,delete_by_doc_id,save,get_stats) now acquire the lock before accessing the vectorstoreRetrieval Scoring & Quality
l2_squared_to_cosine()function to convert FAISS squared-L2 distances to cosine similarity in [0, 1]cos(a, b) = 1 - score / 2for L2-normalized vectorsRETRIEVAL_SCORE_THRESHOLD(default 0.2) to filter low-confidence chunks before passing to analystRETRIEVAL_K(default 5) for number of chunks to retrievesimilarity_search()to apply threshold filtering and return converted similarity scoresLLM & JSON Handling
json_modeparameter toget_llm()to enable Ollama's JSON output format constraintparse_json_response()utility to robustly parse model responses with optional markdown fencesjson_mode=Truefor structured outputsjson_modefor free-form markdown generation@lru_cacheto avoid rebuilding clients on every invocationDocument Deletion
delete_by_doc_id()by iterating the docstore and deleting all chunks matching the doc_idRevision Loop Improvements
Testing & Code Quality
test_retrieval.pywith unit tests for L2-to-cosine conversion, text chunking, and JSON parsingconftest.pyto ensure pytest can import theapppackageprint()with structured logger calls)timezone.utcinstead of deprecatedutcnow()Configuration
RETRIEVAL_KandRETRIEVAL_SCORE_THRESHOLDto settings and.env.exampleGOOGLE_API_KEYfrom CI workflowNotable Implementation Details
asyncio.to_thread()https://claude.ai/code/session_013WDZTHsDvmhUax6fydeLwx