This repository is a small experimental implementation of an "Adaptive RAG" (Retrieval-Augmented Generation) graph built with LangGraph / LangChain-style runnables. The system routes user questions to the best strategy (local vector retrieval, web search, or direct LLM generation), grades retrieved documents and generations for relevance and hallucination, and re-routes when needed.
The project is intended as a demo / research playground rather than a production-ready system.
Features
- Router that chooses between vectorstore RAG, web search, and direct generation
- Document-level grading to filter irrelevant retrievals
- Hallucination grader to check whether an LLM answer is supported by retrieved facts
- Simple orchestrated graph (StateGraph) with conditional edges and nodes
Table of contents
- Quick start
- Dependencies
- Files and structure
- How it works (high level)
- Running the demo (CLI)
- Extending the project
- Tests
- Contributing
Quick start
- Create a virtual environment and install requirements
python -m venv .venv
.\.venv\Scripts\activate
pip install -r requirements.txt- Create a
.envin the project root and set provider keys as needed. The code uses environment variables (via python-dotenv) to configure LLM and web search providers. Example variables you may need depending on the provider(s) you choose:
- GOOGLE_API_KEY or credentials for Google generative models (if using
langchain_google_genai) - OPENAI_API_KEY (if you switch to OpenAI)
- Any keys required by
langchain_tavilyfor web search
See model.py for the example LLM/embedding configuration.
Dependencies
Install the pinned dependencies from requirements.txt. The project expects a LangChain / LangGraph style environment and includes optional provider libraries such as langchain-google-genai, langchain-chroma, and langchain-tavily.
Files and project structure
main.py— Simple CLI chat loop that invokes the compiled graph (app.invoke) and prints formatted responses.ingestion.py— Example document ingestion / vectorstore creation (uses web loaders and Chroma in the example).model.py— LLM and Embedding model instantiation. By default the repo shows a Google Gemini example; swap in your preferred LLMs.graph/— Core orchestration and graph nodesgraph/graph.py— Builds theStateGraph, registers nodes and edges, and compiles anapprunnable. Also contains routing and decision logic.graph/consts.py— Node name constants used in the graph.graph/state.py—GraphStateTypedDict definition describing the shared state passed between nodes.graph/chains/— Reusable runnable chainsrouter.py— The LLM-based router that choosesvectorstore,websearch, ordirectgeneration.generation.py— Generation chain that formats context + question and runs the LLM.retrieval_grader.py— Grades retrieved documents for relevance.answer_grader.py— Grades whether a generation answers the question.hallucination_grader.py— Checks whether a generation is grounded in retrieved facts.
graph/nodes/— Node wrappers that adapt chain runnables to the graph interfaceretrieve.py,grade_documents.py,generate.py,web_search.py
How it works (high level)
- The user question enters the graph at a routing entry point.
- The router uses a small LLM prompt + structured output to choose a datasource:
vectorstore,websearch, ordirect. - If routed to
vectorstore, the system runs retrieval (viaingestion.retriever) and then grades documents. If any document is deemed irrelevant, the graph triggers a web search. - Generation uses retrieved documents (or web search results) as context. After generation, the hallucination grader and answer grader evaluate whether the output is grounded and whether it answers the question. Based on those signals the graph either accepts the generation or re-routes to web search / regenerates.
Running the demo (CLI)
After installing dependencies and adding credentials to .env, run:
python main.pyType queries at the prompt. Type quit, exit, or bye to exit.
Programmatic usage
You can also import the compiled graph and call it from other Python code:
from graph.graph import app
result = app.invoke({"question": "What are agent memories and how do they work?"})
print(result)Notes and configuration
model.pycurrently shows example usage with Google Gemini (langchain_google_genai). Swap the model to your preferred provider by editingmodel.py.ingestion.pycontains an example of fetching web pages and building a Chroma vectorstore. Edit the loader and persist location to match your environment.graph/nodes expectGraphStateobjects shaped like the TypedDict ingraph/state.py.graph.get_graph().draw_mermaid_png(output_file_path="graph.png")is called during graph compilation to produce a visualization (requires graphviz/png tooling).
Extending the project
- Add new nodes by creating callables that accept and return
GraphState-like dicts and register them ingraph/graph.pywithworkflow.add_node(...). - Add new routing rules by updating
graph/chains/router.pyand the prompt/examples used by the structured output parser.
Tests
There is a small pytest test under graph/chains/test/test_chains.py which you can run with:
pytest -qContributing
This repository is experimental. Contributions are welcome — please open issues or pull requests for bug fixes, clarifications, or improvements. When contributing, keep changes small and add or update tests where relevant.
License
Add your preferred license file (e.g., MIT) to make the repo open-source friendly.
Acknowledgements
This project pulls together ideas from LangChain, LangGraph, and RAG patterns for retrieval + generation.
Contact
Open an issue in this repository if you need help or want to propose changes.
-
.with_structured_output() method - This is the easiest and most reliable way to get structured outputs. - This method takes a schema as input which specifies the names, types, and descriptions of the desired output attributes. - The method returns a outputs objects corresponding to the given schema - The schema can be specified as a TypedDict class, JSON Schema or a Pydantic class. - If we want the model to return a Pydantic object, we just need to pass in the desired Pydantic class. - For more complex schemas it's very useful to add few-shot examples to the prompt. - add examples to a system message - etc
-
retrieval_grader - The retrieval grader acts as a quality control mechanism that evaluates whether retrieved documents are actually relevant to the user’s question. This component is crucial because vector similarity alone doesn’t guarantee relevance — documents might be semantically similar but contextually inappropriate.
This grading step prevents irrelevant documents from contaminating our generation process and triggers web search when local documents are insufficient.
-
hallucination_grader - The hallucination grader assesses whether the generated answer is grounded in the provided facts. This is important for ensuring the reliability and accuracy of the information being presented to the user.It compares the generated response against the retrieved documents
-
When hallucinations are detected, our system can trigger regeneration or seek additional information, ensuring that users receive accurate and trustworthy responses.
-
i got error in this line - answer_grader: RunnableSequence = answer_prompt | structured_llm_grader The expression answer_prompt | structured_llm_grader creates a RunnableSerializable, but you're trying to assign it to a variable typed as RunnableSequence