A LangGraph chat over your documents where follow-ups just work. It condenses each follow-up into a standalone question, keeps per-thread memory through a checkpointer, and streams a grounded, cited answer. The pattern behind “chat with my docs — and remember what we just said.”
Pro — $29/user/mo billed annually ($35 monthly). Includes unlimited projects…
Priority support and SSO are available on Pro and Enterprise tiers…
The Chat view — the conversation sidebar (memory threads), the amber standalone-question chip, the condense → retrieve → generate trajectory, and a streamed, cited answer.
Plain RAG retrieves on whatever you just typed — so “and its pricing?” matches on the word “pricing” and loses the thread. This app first rewrites the follow-up into a standalone question using the conversation, retrieves on that, and answers grounded in the sources with [n] citations — while a checkpointer keeps each conversation’s memory separate and durable.
The condense node rewrites each follow-up into a self-contained query, resolving “it / that / the second one” against the history — so retrieval sees intent, not pronouns. Turn one is passed through untouched.
The graph compiles with a LangGraph SqliteSaver. Every run is keyed by thread_id, so history accumulates per conversation and survives restarts — no history re-sent from the browser.
Generation is instructed to use only the retrieved sources and end each factual sentence with a [n] marker — every claim traces back to a passage, and “not in the sources” is said plainly.
Local sentence-transformers embeddings with cosine similarity over a normalized matrix — fast, offline, and persisted to disk so the index survives restarts.
Nodes emit events through get_stream_writer() — stage, condensed, sources, text_delta, usage. One stream_mode="custom" feed drives both the browser and the CLI.
Start, switch and delete independent memory threads from a sidebar. Reopen any thread and its full history reloads from the checkpointer — no client-side transcript needed.
Upload .pdf, .txt or .md, or paste text. A from-scratch recursive splitter chunks each document with tunable size & overlap.
chat.py drives the exact same graph with streaming in the shell. Because it shares threads.db, a conversation you start in the CLI shows up in the web sidebar, and vice versa.
Model and Bedrock connection live server-side in SQLite behind a PBKDF2 password — never in the browser. Config seeds from .env once, then the DB is the source of truth.
Real questions come in threads: an answer, then “why?”, then “what about the other one?”. Anywhere people explore documents by conversation rather than one-shot search, condense-then-retrieve plus durable memory is the shape of the work — and every answer stays auditable back to a source.
Answer from help docs and policies, then handle the inevitable follow-ups without losing what “it” refers to.
Let a team interrogate runbooks, wikis and specs conversationally — with citations so answers can be trusted and checked.
Dig into a paper set turn by turn — “summarize this”, “how does it compare?”, “what were the limitations?” — with context carried forward.
New hires ask their way through policies and playbooks; each person’s thread remembers where their conversation left off.
Ask about terms and clauses in long PDFs and keep drilling in — the standalone rewrite pulls the right passage each turn.
A small, dependency-light Python backend and a vanilla-JS frontend with no build step. Retrieval is a local embedding model; the only remote call is the LLM on Bedrock.
The graph is condense → retrieve → generate, compiled with a checkpointer that threads memory through every turn.
┌─────────────── thread memory (SqliteSaver, keyed by thread_id) ───────────────┐
│ │
START ──▼──▶ condense ──▶ retrieve ──▶ generate ──▶ END
rewrite follow-up dense search stream grounded
→ standalone Q (top-k) answer + [n] cites
On turn one the question is used as-is. On later turns the condense node feeds a bounded window of prior turns to the model and asks for a single standalone question — resolving pronouns and references before anything is retrieved. The rewrite is streamed to the UI as the amber “standalone question” chip.
The standalone question is embedded with the same local sentence-transformers model the index was built with, and scored by cosine similarity over the normalized embedding matrix. The top-k passages become the numbered SOURCES, streamed to the browser as source cards.
The generate node sends the recent history plus a SOURCES + QUESTION prompt and streams tokens back. The system prompt forbids outside knowledge and requires a [n] citation on every factual sentence; if the sources don’t cover it, it says so.
The messages channel uses the add_messages reducer, so the new human turn and the model’s reply append to the thread automatically. Because the graph runs with a SqliteSaver checkpointer keyed by thread_id, the next turn reloads the full history with no transcript sent from the client — and it all survives a restart.
Every node calls get_stream_writer() to emit stage, condensed, sources, text_delta and usage events. The FastAPI server forwards them to the browser as NDJSON; chat.py renders the same events in the terminal — one graph, two front-ends.
thread_id. Memory lives in the checkpointer, so a page reload (or a server restart) loses nothing.Ships with a pre-seeded app_config.db (copied from the sibling RAG Pipeline), so the Bedrock region, key and model carry over — it runs out of the box.
# install & launch pip install -r requirements.txt python server.py # → http://127.0.0.1:8100 # then, in the browser: 1. Knowledge Base → upload .pdf / .txt / .md (or paste), Build Index 2. Chat → ask a question, then a follow-up — memory + condense kick in 3. Settings → model & Bedrock connection (password-protected) # …or the same graph in the terminal: python chat.py # streaming CLI; shares threads.db with the web app
/overview — e.g. http://127.0.0.1:8100/overview.