# nilsjennissen/langgraph architecture

> Architecture diagram of the GitHub repository nilsjennissen/langgraph, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-23). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/nilsjennissen/langgraph
- Repository: https://github.com/nilsjennissen/langgraph

## Overview

1. **Project shape**
   - This repository is a local RAG chatbot application centered on two runtime entrypoints: `app.py` and `app_streamlit.py`.
   - The rest of the repo is supporting material: a PDF document corpus in `docs/`, exploratory work in `notebooks/`, and tests in `tests/`.

2. **Primary purpose**
   - The system answers user questions by combining **retrieval over local PDFs** with **LLM generation**.
   - It is not a generic app scaffold; the architecture is specifically optimized for adaptive question answering with routing, retrieval, rewriting, and answer grading.

3. **Main orchestration layer**
   - The core logic is described as a **LangGraph state machine**.
   - The graph holds a small shared state: the user question, retrieved documents, the generated answer, and retry count.
   - This graph is the central control plane that decides whether to answer directly or invoke retrieval, and whether to rewrite and retry.

4. **Question routing boundary**
   - The first architectural decision is a router that classifies each query into one of two paths:
     - **direct LLM response** for general questions,
     - **retrieval-augmented path** for questions that should be answered from the document corpus.
   - This boundary is important because it separates “knowledge in the model” from “knowledge in the PDFs.”

5. **Retrieval subsystem**
   - The retrieval path is multi-stage:
     - document retrieval,
     - document relevance grading,
     - query rewriting if retrieval quality is poor.
   - The README describes an **ensemble retriever** combining keyword search (**BM25**) and semantic search via a vectorstore-backed approach.
   - The document source is the `docs/` directory, which currently contains many domain PDFs and acts as the local knowledge base.

6. **Generation and self-evaluation loop**
   - Once relevant documents are selected, the graph generates an answer using retrieved context.
   - The system then grades the generation for quality/grounding.
   - If the output is weak, the workflow can loop back through query transformation and retrieval again, making the system iterative rather than single-pass.

7. **LLM provider abstraction**
   - The chatbot is intentionally provider-agnostic and can be wired to OpenAI, Ollama, Groq, or Hugging Face integrations through LangChain.
   - This means the model layer is an interchangeable dependency rather than a fixed implementation.
   - Architecturally, the LLM backend is a pluggable service boundary beneath the graph nodes.

8. **Presentation layer**
   - `app.py` appears to be the main script for running the chatbot logic from Python.
   - `app_streamlit.py` is a separate UI entrypoint for an interactive Streamlit front end.
   - These are thin presentation surfaces over the same underlying retrieval/generation workflow.

9. **Data flow from user to answer**
   - User question enters the app entrypoint.
   - The graph routes the question.
   - If retrieval is needed, the system loads/uses the PDF corpus, searches it, filters candidate passages, and may rewrite the query.
   - The selected context is passed to the LLM, the answer is graded, and the final response is returned to the UI.

10. **Corpus and local assets**
   - The repository stores its knowledge source locally as PDFs under `docs/`.
   - This is the main durable data boundary in the repo: there is no separate database or remote knowledge store shown.
   - The document set is domain-specific and determines the chatbot’s factual coverage.

11. **Development and experimentation**
   - `notebooks/pdf-chat.ipynb` is for step-by-step experimentation and likely mirrors or probes the graph pipeline interactively.
   - This notebook layer is outside the production path and is best viewed as a development sandbox for retrieval and prompting behavior.

12. **Testing and quality boundaries**
   - `tests/tests.py` provides a minimal automated validation layer.
   - Given the architecture, tests likely focus on graph behavior, document loading, routing decisions, or utility functions rather than end-to-end model determinism.

13. **What is not present**
   - There is no evidence of a separate server, database, queue, or microservice decomposition.
   - The architecture is therefore a **single-process application** with local document storage and outbound API calls to LLM providers.

14. **Diagram guidance**
   - A good architecture diagram should show:
     - user/UI on the left,
     - an orchestration graph in the center,
     - local PDF corpus as the retrieval source,
     - optional LLM provider backends on the right,
     - and a feedback loop from answer grading back into query rewriting/retrieval.

Main entry points: [`docs`](https://github.com/nilsjennissen/langgraph/tree/main/docs) (PDF corpus), [`app.py`](https://github.com/nilsjennissen/langgraph/blob/main/app.py) (App), [`app_streamlit.py`](https://github.com/nilsjennissen/langgraph/blob/main/app_streamlit.py) (Streamlit UI), [`notebooks/pdf-chat.ipynb`](https://github.com/nilsjennissen/langgraph/blob/main/notebooks/pdf-chat.ipynb) (Notebook)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_presentation["Presentation"]
  node_app_py["App<br/>entrypoint<br/>[app.py]"]
  node_streamlit_app["Streamlit UI<br/>ui entrypoint<br/>[app_streamlit.py]"]
end

subgraph group_orchestration["Orchestration"]
  node_state_graph{{"State graph<br/>langgraph orchestrator"}}
  node_shared_state["Shared state<br/>graph state"]
  node_router(("Router<br/>query classifier"))
  node_direct_answer["Direct answer<br/>llm node"]
  node_rag_path{{"RAG path<br/>qa pipeline"}}
  node_query_rewrite["Rewrite query<br/>transform node"]
  node_grade_retrieval(("Grade docs<br/>relevance judge"))
  node_generate_answer["Generate answer<br/>llm node"]
  node_grade_answer(("Grade answer<br/>quality judge"))
end

subgraph group_retrieval["Retrieval"]
  node_retriever{{"Ensemble retriever<br/>hybrid search"}}
  node_bm25["BM25<br/>keyword search"]
  node_vectorstore[("Vector store<br/>semantic index")]
end

subgraph group_ml["Model layer"]
  node_llm_provider(("LLM provider<br/>pluggable backend"))
end

subgraph group_assets["Assets"]
  node_pdf_corpus[("PDF corpus<br/>local knowledge base")]
  node_notebook["Notebook<br/>experiment sandbox<br/>[pdf-chat.ipynb]"]
  node_tests["Tests<br/>validation<br/>[tests.py]"]
end

node_app_py -->|"starts"| node_state_graph
node_streamlit_app -->|"starts"| node_state_graph
node_state_graph -->|"reads/writes"| node_shared_state
node_state_graph -->|"routes"| node_router
node_router -->|"general query"| node_direct_answer
node_router -->|"needs corpus"| node_rag_path
node_rag_path -->|"fetch context"| node_retriever
node_retriever -->|"lexical"| node_bm25
node_retriever -->|"semantic"| node_vectorstore
node_bm25 -->|"indexes"| node_pdf_corpus
node_vectorstore -->|"built from"| node_pdf_corpus
node_retriever -->|"candidate check"| node_grade_retrieval
node_grade_retrieval -->|"if poor"| node_query_rewrite
node_query_rewrite -->|"retry search"| node_retriever
node_grade_retrieval -->|"if useful"| node_generate_answer
node_generate_answer -->|"calls"| node_llm_provider
node_generate_answer -->|"self-check"| node_grade_answer
node_grade_answer -->|"if weak"| node_query_rewrite
node_grade_answer -->|"finalize"| node_state_graph
node_notebook -.->|"experiments with"| node_retriever
node_tests -.->|"validates"| node_state_graph

click node_app_py "https://github.com/nilsjennissen/langgraph/blob/main/app.py"
click node_streamlit_app "https://github.com/nilsjennissen/langgraph/blob/main/app_streamlit.py"
click node_pdf_corpus "https://github.com/nilsjennissen/langgraph/tree/main/docs"
click node_notebook "https://github.com/nilsjennissen/langgraph/blob/main/notebooks/pdf-chat.ipynb"
click node_tests "https://github.com/nilsjennissen/langgraph/blob/main/tests/tests.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_app_py,node_streamlit_app toneBlue
class node_state_graph,node_shared_state,node_router,node_direct_answer,node_rag_path,node_query_rewrite,node_grade_retrieval,node_generate_answer,node_grade_answer toneAmber
class node_retriever,node_bm25,node_vectorstore toneMint
class node_llm_provider toneRose
class node_pdf_corpus,node_notebook,node_tests toneIndigo
```

## Components

### Presentation

User-facing entrypoints

- [App](https://github.com/nilsjennissen/langgraph/blob/main/app.py): entrypoint · Python chatbot entrypoint · `app.py`
- [Streamlit UI](https://github.com/nilsjennissen/langgraph/blob/main/app_streamlit.py): ui entrypoint · Interactive frontend for the same workflow · `app_streamlit.py`

### Orchestration

LangGraph control plane and answer loop

- **State graph**: langgraph orchestrator · Central control plane that routes and loops over question answering steps
- **Shared state**: graph state · Question, retrieved docs, generated answer, retry count
- **Router**: query classifier · Chooses direct answer vs retrieval-augmented path
- **Direct answer**: llm node · Answers general questions without corpus lookup
- **RAG path**: qa pipeline · Retrieval-grounded answer branch
- **Rewrite query**: transform node · Refines weak queries before retrying retrieval
- **Grade docs**: relevance judge · Checks whether retrieved passages are useful
- **Generate answer**: llm node · Forms grounded response from selected context
- **Grade answer**: quality judge · Self-evaluates grounding and response quality

### Retrieval

Local PDF knowledge access and ranking

- **Ensemble retriever**: hybrid search · Combines lexical BM25 and semantic vector search
- **BM25**: keyword search · Sparse lexical retrieval over the PDF corpus
- **Vector store**: semantic index · Embedding-backed semantic retrieval index

### Model layer

LLM and embedding dependencies

- **LLM provider**: pluggable backend · OpenAI, Ollama, Groq, or Hugging Face via LangChain

### Assets

Local corpus and exploratory material

- [PDF corpus](https://github.com/nilsjennissen/langgraph/tree/main/docs): local knowledge base · Domain PDFs used as the factual source · `docs`
- [Notebook](https://github.com/nilsjennissen/langgraph/blob/main/notebooks/pdf-chat.ipynb): experiment sandbox · Interactive exploration of retrieval and prompting · `notebooks/pdf-chat.ipynb`
- [Tests](https://github.com/nilsjennissen/langgraph/blob/main/tests/tests.py): validation · Minimal automated checks for core behavior · `tests/tests.py`

## Connections

- App → State graph: starts · Runs the chatbot workflow from Python
- Streamlit UI → State graph: starts · Drives the same pipeline from the UI
- State graph → Shared state: reads/writes · Maintains question, docs, answer, and retries
- State graph → Router: routes · First decision point for each query
- Router → Direct answer: general query · Bypasses retrieval for non-corpus questions
- Router → RAG path: needs corpus · Sends knowledge-seeking questions to retrieval
- RAG path → Ensemble retriever: fetch context · Gets candidate passages from local PDFs
- Ensemble retriever → BM25: lexical · Keyword-based search branch
- Ensemble retriever → Vector store: semantic · Embedding-based search branch
- BM25 → PDF corpus: indexes · Searches the local document set
- Vector store → PDF corpus: built from · Derived from the same PDF corpus
- Ensemble retriever → Grade docs: candidate check · Filters weak retrieval results
- Grade docs → Rewrite query: if poor · Rewrites the question when retrieval quality is low
- Rewrite query → Ensemble retriever: retry search · Loops back into retrieval with improved query
- Grade docs → Generate answer: if useful · Passes relevant context to generation
- Generate answer → LLM provider: calls · Uses a pluggable model backend
- Generate answer → Grade answer: self-check · Evaluates grounding and response quality
- Grade answer → Rewrite query: if weak · Can trigger another retrieval iteration
- Grade answer → State graph: finalize · Returns the finished answer when sufficient
- Notebook → Ensemble retriever: experiments with · Used to probe retrieval and prompting behavior
- Tests → State graph: validates · Checks core workflow behavior
