Welcome to the RAG Project Assistantβa hybrid private-cloud Retrieval-Augmented Generation (RAG) assistant built from the ground up using LangGraph v1.2+ and modern LangChain standalone integration packages.
This system represents a state-of-the-art hybrid AI architecture. It performs data parsing, text chunking, embedding generation, and vector retrieval fully locally and privately on your own computer. It only routes synthesis and evaluation queries to the cloud via the high-speed DeepSeek API, guaranteeing high security, minimal data egress, and extremely low API costs.
graph TD
User([User Input]) --> Streamlit[app.py Streamlit UI]
Streamlit --> Agent[my_agent/agent.py compiled graph]
subgraph AgentGraph [LangGraph Workflow]
Start([Start]) --> Retrieve[retrieve_node]
Retrieve --> Generate[generate_node]
Generate --> Evaluate[evaluate_node]
Evaluate -- "Fail (loop_count < 3)" --> WebSearch[web_search_node]
WebSearch --> Generate
Evaluate -- "Pass OR Max Loops" --> End([End])
end
Retrieve -.-> Chroma[(Chroma Vector DB)]
Chroma -.-> Ollama[Local Ollama: qwen3-embedding:0.6b]
WebSearch -.-> Tavily[Tavily Web Search API]
Ingest[ingest.py CLI] --> Chroma
Docs[docs/ directory] --> Ingest
The assistant leverages a directed acyclic state graph compiled with an in-memory MemorySaver checkpointer, preserving conversation thread context for the duration of a browser session. Below is a detailed breakdown of the execution flow:
- Inputs: Reads
user_queryfrom the graph state. - Mechanism: Invokes the local vector store retriever. It leverages local Ollama running the
qwen3-embedding:0.6bmodel to vectorize the query, performing a cosine-similarity search against local Chroma DB vectors stored in./chroma_db/. - Output: Retrieves the top 4 most relevant text segments and saves them to
documentsin the graph state.
- Inputs: Reads
messageschat history, retrieveddocumentscontext, and anyevaluation_feedbackfrom the state. - Mechanism:
- Formulates a rich, grounded system prompt injecting the retrieved context.
- If a previous candidate answer failed the evaluator audit, it automatically appends the grader's critical feedback to the prompt, instructing the model to refine its answer.
- Calls the fast
deepseek-v4-flashmodel with thinking disabled to ensure near-zero latency.
- Output: appends the generated AIMessage to
messagesand increments theloop_countstate variable.
- Inputs: Reads the latest generated answer, retrieved
documents, and the originaluser_query. - Mechanism:
- Invokes an objective LLM auditor running
deepseek-v4-flash. - It utilizes LangChain's
with_structured_outputbound to a strict Pydantic schema to grade the response.
- Invokes an objective LLM auditor running
- Pydantic Grader Schema (
GradeAnswer):class GradeAnswer(BaseModel): hallucination_score: str # 'yes' or 'no' (Is the answer grounded in the retrieved docs?) relevance_score: str # 'yes' or 'no' (Does the answer resolve the user's query?) feedback: str # Detailed critique if either score is 'no'
- Output: Sets
evaluation_passedtoTrue/Falseand writes the critique toevaluation_feedbackin the state.
- Inputs: Executed only if
evaluation_passedisFalseandloop_countis less than 3. - Mechanism:
- Takes the query and the grader's feedback to formulate an optimized search term.
- Invokes the modern standalone
TavilySearchtool (langchain-tavily) to retrieve live, external search results. - Automatically parses the new search result payload dictionary (
resultslist key extraction) and merges it as a supplementary source document.
- Output: Appends the search results to
documentsin the state and routes control back togenerate_nodeto perform a self-corrective rewrite loop.
.
βββ my_agent/ # Main agent package
β βββ utils/ # Utilities, tools, and schemas
β β βββ __init__.py
β β βββ state.py # Graph state schema definition (TypedDict)
β β βββ tools.py # Chroma retriever & Tavily Search configurations
β β βββ nodes.py # State processing nodes (retrieve, generate, evaluate, search)
β βββ __init__.py
β βββ agent.py # Main graph setup, conditional edges, and MemorySaver compiler
βββ docs/ # Local document storage directory (Git-ignored)
β βββ .gitkeep # Folder placeholder ensuring tracking of empty docs directory
βββ .env # API keys and secret variables (ignored by Git)
βββ .env.example # Key configuration template
βββ ingest.py # Document loader, segment splitter, and Chroma persistence CLI
βββ langgraph.json # LangGraph CLI configuration mapping
βββ pyproject.toml # Package configuration and dependencies managed by uv
βββ README.md # Highly detailed project guide (this file)
- Ollama: Download and install Ollama. Keep the service active.
- uv: Ensure the high-performance Python package installer uv is installed.
To generate vector embeddings locally without sending raw files to the cloud, pull the Qwen embedding model:
ollama pull qwen3-embedding:0.6bCreate a local .env file in the root workspace (based on .env.example):
# DeepSeek API configuration key
DEEPSEEK_API_KEY=your_deepseek_api_key_here
# Tavily Web Search API key
TAVILY_API_KEY=your_tavily_api_key_hereSynchronize your virtual environment and install all packages in milliseconds using uv:
uv sync-
Drop any local markdown files (
.md) or PDF manuals (.pdf) inside thedocs/folder. -
Run the ingestion CLI:
uv run python ingest.py
The script automatically parses all documents using TextLoader and the high-speed PyMuPDF library, splits them recursively into 1000-character segments, runs local embeddings, and PERSISTS the database in the local
./chroma_dbfolder.Tip: To ingest from a custom folder outside the workspace, use:
uv run python ingest.py --docs-dir "C:\Path\To\External\Docs"
Start the web application:
uv run streamlit run app.pyOpen http://localhost:8501 in your browser. As you chat, the Stock Streamlit Chat UI streams graph outputs directly to your terminal. It also presents live, collapsible execution logs for each node inside native st.expander components, giving you complete visibility into the retrieved text chunks, grader evaluation metrics, and search queries instantly!
- Cause: Your local Ollama server is offline or is running on a non-standard port.
- Fix: Ensure Ollama is running (check your Windows system tray). You can verify it by opening
http://localhost:11434in your browser.
Q2: Streamlit shows "pydantic_core._pydantic_core.ValidationError: If using default api base, DEEPSEEK_API_KEY must be set."
- Cause: Your
.envfile is missing orDEEPSEEK_API_KEYis empty/incorrect. - Fix: Make sure you have created the
.envfile at the root of the project containing your validsk-...API key.
- Cause: Missing or invalid
TAVILY_API_KEY. - Fix: Ensure your Tavily key is correctly configured inside
.env. If you do not have a Tavily search key, the RAG assistant will still run completely offline/locally, but the web search fallback node will print warnings and exit.
The codebase adheres to the strictest Python standards, completely verified by Ruff:
- No Unused Imports: All redundant imports have been removed.
- PEP Compliance: Delayed, import-time environment loading statements (essential for ChatDeepSeek key validation) are fully annotated using
# noqa: E402to satisfy import-ordering guidelines.
To run the linter and verify code quality:
uv run ruff check .Output:
All checks passed!