A production-style, local-first Adaptive Research Copilot that routes questions to local vector search, live web search, or a hybrid of both — automatically.
graph TD
User["User / Streamlit UI"]
API["FastAPI + Uvicorn<br>/chat /ingest /eval/run"]
Classify["classify_query<br>(LLM routing decision)"]
Retrieve["retrieve_local_context<br>(Qdrant / FAISS)"]
Confidence["confidence_gate<br>(score 0-1)"]
WebSearch["maybe_web_search<br>(Tavily)"]
Synthesize["synthesize_answer<br>(OpenAI GPT-4o-mini)"]
Persist["persist_chat<br>(MongoDB / Motor)"]
LangSmith["LangSmith<br>(traces + eval)"]
Mongo[("MongoDB")]
Qdrant[("Qdrant / FAISS")]
n8n["n8n Workflows<br>(ingest · eval · alerts)"]
User -->|POST /chat| API
API --> Classify
Classify -->|LOCAL_RAG / HYBRID| Retrieve
Classify -->|WEB_SEARCH| WebSearch
Retrieve --> Confidence
Confidence -->|high confidence + LOCAL_RAG| Synthesize
Confidence -->|low confidence / HYBRID| WebSearch
WebSearch --> Synthesize
Synthesize --> Persist
Persist --> Mongo
Retrieve --> Qdrant
API -.->|traces| LangSmith
n8n -->|POST /ingest| API
n8n -->|POST /eval/run| API
| Component | Technology |
|---|---|
| LLM Framework | LangChain |
| Workflow Orchestration | LangGraph |
| Web Framework | FastAPI |
| ASGI Server | Uvicorn |
| UI | Streamlit |
| Vector DB | Qdrant (default) / FAISS (fallback) |
| Chat DB | MongoDB / Motor (async) |
| Document Processing | LangChain Community (loaders + splitters) |
| LLM / Embeddings | OpenAI |
| Web Search | Tavily |
| Data Validation | Pydantic v2 |
| Workflow Automation | n8n |
| Observability | LangSmith |
| Tool Type | Description | Example Tools | Main Features |
|---|---|---|---|
| LLM Application Framework | Libraries to build LLM apps (RAG, tools, prompts). Provide abstractions for models, prompts, memory, and retrieval. | LangChain, LlamaIndex, Haystack, DSPy | Prompt templates, chains, tool calling, memory, vector DB integration, optimizable prompting |
| Agent Workflow Orchestration | Frameworks to design complex agent workflows with control flow, state, and multi-agent coordination. | LangGraph, AutoGen, Semantic Kernel | Graph/state machines, multi-agent coordination, tool routing, loops, human-in-the-loop |
| Agent Collaboration Frameworks | Tools focused on role-based teams of AI agents collaborating on tasks. | CrewAI, AutoGen, AgentScope | Role-based agents, collaborative reasoning, shared context, task delegation |
| Visual / Low-Code AI Workflow Builders | GUI tools for building AI workflows or automation pipelines using drag-and-drop nodes. | n8n, Langflow, Flowise, Dify | Drag-and-drop pipelines, connectors to APIs, triggers, scheduling, simple AI agents |
| No-Code Business Automation | No-code platforms for automating business workflows with optional AI steps. | Make (Integromat), Zapier | Visual workflow builder, 1000s of app integrations, triggers, scheduled runs |
| LLM Observability & Evaluation | Platforms for tracing, debugging, evaluating, and monitoring LLM applications in production. | Langfuse, LangSmith, Arize Phoenix, Braintrust, Weave | Tracing LLM calls, prompt versioning, experiment tracking, evaluation datasets, cost & latency analytics |
| Durable Workflow / Execution Engines | Infrastructure tools for long-running workflows and reliable agent execution with retries and state persistence. | Temporal, Apache Airflow, Prefect | Durable workflows, retries, state persistence, distributed scheduling, fault tolerance |
| Managed Agent Platforms | Fully managed cloud platforms for deploying and running AI agents without managing infrastructure. | OpenAI Assistants API, Vertex AI Agent Builder, Amazon Bedrock Agents | Built-in memory, tool use, file retrieval, hosted execution, managed scaling |
| Tool | Category | Role |
|---|---|---|
| LangChain | LLM framework | Build RAG pipelines, chains, and tool-using apps |
| LlamaIndex | LLM framework | Data ingestion, indexing, and RAG |
| Haystack | LLM framework | Production RAG and NLP pipelines |
| DSPy | LLM framework | Optimize prompts and LLM pipelines programmatically |
| LangGraph | Agent orchestration | Build stateful, graph-based agent workflows |
| AutoGen | Agent orchestration + collab | Multi-agent conversations and task execution |
| Semantic Kernel | Agent orchestration | Enterprise agent workflows with .NET/Python SDK |
| CrewAI | Agent collaboration | Role-based multi-agent teams |
| AgentScope | Agent collaboration | Distributed multi-agent framework |
| n8n | Low-code automation | Integrate APIs + AI steps with visual flows |
| Langflow | Low-code AI builder | Visual builder for LangChain-based pipelines |
| Flowise | Low-code AI builder | Drag-and-drop LLM app builder |
| Dify | Low-code AI builder | LLM app platform with prompt IDE and agent UI |
| Make (Integromat) | No-code automation | Business automation workflows |
| Zapier | No-code automation | Trigger-based app integrations |
| Langfuse | Observability | Open-source LLM tracing and evaluation |
| LangSmith | Observability | LangChain-native tracing, evaluation, datasets |
| Arize Phoenix | Observability | LLM tracing, evaluation, and drift detection |
| Braintrust | Observability | Experiment tracking and evals for LLM apps |
| Weave | Observability | Lightweight tracing and eval (by Weights & Biases) |
| Temporal | Execution engine | Durable, fault-tolerant long-running workflows |
| Apache Airflow | Execution engine | Scheduled DAG-based workflow orchestration |
| Prefect | Execution engine | Modern dataflow and agent task orchestration |
| OpenAI Assistants API | Managed agent platform | Hosted agents with memory, tools, and file access |
| Vertex AI Agent Builder | Managed agent platform | Google Cloud managed agent deployment |
| Amazon Bedrock Agents | Managed agent platform | AWS managed agents with RAG and tool use |
adaptive-rag/
├── backend/
│ ├── api/
│ │ ├── main.py # FastAPI app factory + LangSmith setup
│ │ ├── routes.py # /health /ingest /chat /eval/run
│ │ ├── schemas.py # Pydantic request/response models
│ │ └── dependencies.py # DI: settings, vector store, chat repo
│ ├── orchestrator/
│ │ ├── graph.py # LangGraph StateGraph + conditional routing
│ │ ├── nodes.py # All graph node implementations
│ │ ├── state.py # GraphState + RouteDecision
│ │ ├── prompts.py # LangChain prompt templates
│ │ └── eval.py # LangSmith dataset + eval pipeline
│ ├── data/
│ │ ├── config.py # Pydantic Settings (env-based)
│ │ ├── vector_store.py # Qdrant + FAISS adapters
│ │ ├── chat_repo.py # Motor MongoDB + in-memory fallback
│ │ └── ingestion.py # LangChain Community loaders/splitters
│ └── tests/
│ ├── conftest.py
│ ├── test_graph_routing.py # LOCAL_RAG / WEB_SEARCH / HYBRID routes
│ ├── test_api.py # API contract tests
│ ├── test_data_layer.py # Vector + chat repo tests
│ ├── test_eval_pipeline.py # Eval report artifact tests
│ └── test_streamlit_smoke.py # Streamlit smoke test
├── frontend/
│ └── streamlit_app.py # Chat UI with route badges + source citations
├── workflows/
│ └── n8n/
│ ├── doc_ingestion_workflow.json
│ ├── nightly_eval_workflow.json
│ └── regression_alert_workflow.json
├── infra/
│ └── docker-compose.yml # MongoDB + Qdrant + n8n (optional)
├── scripts/
│ ├── start_backend.sh
│ ├── start_frontend.sh
│ └── run_tests.sh
├── .env.example
└── pyproject.toml
- Python 3.12+
- uv (
pip install uv) - Docker + Docker Compose (for local infra)
If you manage multiple Python versions, install pyenv and then run:
pyenv install 3.12.11
pyenv local 3.12.11This ensures the repo uses the exact interpreter version that uv sync and the tooling expect.
git clone <repo-url>
cd adaptive-rag
uv synccp .env.example .env
# Edit .env — at minimum set OPENAI_API_KEY and TAVILY_API_KEYFor FAISS fallback (no Qdrant needed), set:
FEATURE_VECTOR_BACKEND=faiss
MONGODB_URI=memory://
Skip this step if you have remote MongoDB Atlas, Qdrant Cloud, and LangSmith configured in
.env— no Docker needed.
# Local MongoDB + Qdrant only
docker compose -f infra/docker-compose.yml up -d mongodb qdrant
# With n8n workflow automation (see n8n section below)
docker compose -f infra/docker-compose.yml --profile n8n up -d# Via script (supports HOST / PORT / WORKERS / LOG_LEVEL env overrides):
bash scripts/start_backend.sh
# Or directly:
uvicorn backend.api.main:app --host 0.0.0.0 --port 8000 --reload# Via script (supports API_BASE_URL / PORT env overrides):
API_BASE_URL=http://localhost:8000 bash scripts/start_frontend.sh
# Or directly:
streamlit run frontend/streamlit_app.pyNote: The Streamlit UI is a simple chat interface that allows you to ask questions and get answers from the RAG system. It is not a full-featured chatbot, but it is a good way to test the RAG system.
bash scripts/run_tests.sh
# or:
uv run pytest -vn8n is not required to run the application. It is an optional automation layer that wraps existing API endpoints with scheduling, triggers, and Slack alerting. Everything it does can be done manually.
| n8n workflow | What it automates | Without n8n |
|---|---|---|
doc_ingestion_workflow.json |
Webhook trigger → POST /ingest |
Call POST http://localhost:8000/ingest directly |
nightly_eval_workflow.json |
Nightly cron → POST /eval/run |
Run uv run pytest or hit /eval/run manually |
regression_alert_workflow.json |
Eval result → Slack alert via webhook | ALERT_WEBHOOK_URL in .env — backend calls it directly when eval score drops below EVAL_THRESHOLD |
To use n8n locally:
# Start n8n via Docker
docker compose -f infra/docker-compose.yml --profile n8n up -d
# Open n8n UI
open http://localhost:5678Then import the workflow JSON files from workflows/n8n/ via the n8n UI (Settings → Import workflow).
To use n8n Cloud instead of self-hosting, create a free account at n8n.io, import the same JSON files, and update the webhook/API base URLs to point to your deployed backend.
Every /chat request flows through a LangGraph state machine:
-
classify_query— An LLM analyzes the question and returns one of:LOCAL_RAG— answer likely in local docsWEB_SEARCH— needs fresh/external dataHYBRID— use both
-
retrieve_local_context— Top-k documents from Qdrant/FAISS (skipped forWEB_SEARCH) -
confidence_gate— LLM scores local context 0–1. If score <CONFIDENCE_THRESHOLD(default 0.7) or route isHYBRID, falls through to web search -
maybe_web_search— Tavily API fetches top-5 live results -
synthesize_answer— GPT-4o-mini generates a cited answer from all context -
persist_chat— User + assistant messages saved to MongoDB
The Streamlit UI shows a route badge (🟢 LOCAL_RAG, 🌐 WEB_SEARCH, 🔀 HYBRID) plus confidence score, latency, and token usage for every response.
Every request is traced automatically when LANGSMITH_TRACING=true and LANGSMITH_API_KEY is set.
View traces at smith.langchain.com under project adaptive-rag-ops-lab.
curl -X POST http://localhost:8000/eval/runThis will:
- Create (or reuse) a dataset named
adaptive-rag-eval-v1in LangSmith with 3 seed Q&A examples - Run the pipeline against each example
- Score keyword relevance
- Write
eval_reports/eval_report_<timestamp>.jsonand.md
curl http://localhost:8000/healthcurl -X POST http://localhost:8000/ingest \
-H "Content-Type: application/json" \
-d '{"file_path": "/path/to/paper.pdf"}'curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"question": "What is retrieval-augmented generation?"}'curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"question": "How does it differ from fine-tuning?", "session_id": "<session_id>"}'curl http://localhost:8000/chat/<session_id>Import any of the three JSON files from workflows/n8n/ into your n8n instance:
| Workflow | Trigger | Action |
|---|---|---|
doc_ingestion_workflow.json |
Webhook POST /ingest-docs |
Calls /ingest, logs result |
nightly_eval_workflow.json |
Cron 02:00 daily |
Calls /eval/run, writes summary |
regression_alert_workflow.json |
Cron 02:30 daily |
Calls /eval/run, sends Slack alert if score < EVAL_THRESHOLD |
Set API_BASE_URL in n8n environment variables to point at your backend.
pydantic_settings not found
uv add pydantic-settingsQdrant connection refused
docker compose -f infra/docker-compose.yml up -d qdrant
# or switch to FAISS: FEATURE_VECTOR_BACKEND=faissMongoDB connection refused
docker compose -f infra/docker-compose.yml up -d mongodb
# or use in-memory: MONGODB_URI=memory://LangSmith traces not appearing — ensure LANGSMITH_API_KEY and LANGSMITH_PROJECT are set in .env and LANGSMITH_TRACING=true.
faiss-cpu not installed — uncomment faiss-cpu in pyproject.toml and run uv sync.
- Hybrid retrieval: combine dense (OpenAI embeddings) + sparse (BM25 via
langchain-community) with RRF fusion - Reranker: add
FlashrankRerankor Cohere Rerank between retrieval and synthesis - Response caching: Redis-based semantic cache using
langchain-community.cache.RedisSemanticCache - Multi-tenant namespacing: Qdrant collection-per-tenant via request header routing in FastAPI
- CI pipeline: GitHub Actions with pytest + ruff + eval regression gate (fail if
avg_keyword_relevancedrops below threshold)
| Technology | Evidence |
|---|---|
| LangChain | orchestrator/prompts.py — ChatPromptTemplate, PromptTemplate; chains used in every node |
| LangGraph | orchestrator/graph.py — StateGraph with 6 nodes + 2 conditional routing edges |
| FastAPI | api/routes.py — async handlers for all 5 endpoints |
| Uvicorn | scripts/start_backend.sh + direct uvicorn command |
| Streamlit | frontend/streamlit_app.py — full chat UI |
| Qdrant | data/vector_store.py QdrantAdapter — default path |
| FAISS | data/vector_store.py FAISSAdapter — enabled via FEATURE_VECTOR_BACKEND=faiss |
| MongoDB | data/chat_repo.py ChatRepository using Motor |
| Motor | AsyncIOMotorClient in chat_repo.py |
| LangChain Community | data/ingestion.py — PyPDFLoader, UnstructuredMarkdownLoader, BSHTMLLoader, RecursiveCharacterTextSplitter |
| OpenAI | langchain_openai.ChatOpenAI + OpenAIEmbeddings throughout orchestrator |
| Tavily | orchestrator/nodes.py maybe_web_search using TavilyClient |
| Pydantic | api/schemas.py, data/config.py — strict v2 models and Settings |
| n8n | workflows/n8n/ — 3 importable workflow JSONs |
| LangSmith | api/main.py tracing setup; orchestrator/eval.py dataset + eval pipeline; @traceable decorators on nodes |
