Preserving Context, Revealing Insight
Amber answers questions over a document collection by combining vector search with a knowledge graph built from those same documents. It ingests your files, extracts entities and relationships into Neo4j, embeds the chunks into Milvus, and picks a retrieval strategy per query. Every answer comes back with citations to the chunks it was built from.
- Overview
- Getting Started
- Configuration
- Usage
- Key Features
- System Architecture
- How It Works
- API Reference
- Application Structure
- Development
- Testing
- Performance & Scaling
- Troubleshooting
- Implementation Details
- Contributing
- License
A plain RAG system retrieves chunks by vector similarity alone. That works until the answer depends on how things in the corpus relate to each other, and then it quietly returns the wrong five chunks. Amber builds that structure explicitly instead of hoping the embeddings encode it.
At ingestion, each document is chunked, embedded into Milvus, passed to an LLM that extracts entities and relationships into Neo4j, and the resulting entity graph is clustered into communities with the hierarchical Leiden algorithm. At query time, any of those layers can be searched, and usually more than one is.
Five retrieval modes:
- Basic: vector-only search, for simple queries
- Local: entity-focused graph traversal, for precise lookups
- Global: map-reduce over community summaries, for broad questions
- Drift: iterative reasoning with generated follow-up questions, for complex queries
- Structured: direct Cypher execution, for list and count queries
- Docker & Docker Compose v2.20+ - Recommended for easiest setup
- LLM Provider - At least one required:
- System Resources - Minimum:
- 8 GB RAM (16 GB recommended)
- 20 GB disk space
- 2 CPU cores (4+ recommended)
-
Clone the Repository
git clone https://github.com/yuzushi-dev/Amber.git cd Amber -
Configure Environment
cp .env.example .env
Edit
.envand set your API keys:# LLM Provider (required - choose at least one) OPENAI_API_KEY=sk-proj-... ANTHROPIC_API_KEY=sk-ant-... # Security (important!) SECRET_KEY=your-secret-key-here # Generate with: openssl rand -hex 32 NEO4J_PASSWORD=strong_neo4j_password # Optional: Customize ports API_PORT=8000
-
Launch Services
# Standard launch (CPU mode) ./start.sh # With NVIDIA GPU support (for local embeddings/models) ./start.sh --gpu # Or manually: # docker compose up -d # docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d # GPU
This starts 11 services:
nginx- Edge proxy, owns all external traffic (host:8000for the API, host:80for the frontend)api- FastAPI backend (internal; exposed through nginx)frontend- React frontend (internal; served through nginx)worker- Celery workerscelery_beat- Scheduler for periodic taskspostgres- Metadata database (host port 5433 → container 5432)neo4j- Graph database (ports 7474, 7687)milvus- Vector database (ports 19530, 9091)etcd- Milvus metadata store (internal)redis- Cache & broker (port 6379)minio- S3-compatible object storage; console on port 9001, S3 API on 9000 inside the network only
Note: nginx owns host ports
:8000and:80. The API and frontend containers do not bind host ports directly. For a zero-downtime deploy,deploy/docker-compose.canary.ymlbrings up anapi-canarycontainer on port 8001, which you can smoke-test without nginx in the path;deploy/cutover.sh --to canarythen repoints nginx at it,--dry-runshows the change first, and--to liverolls back. -
Run Database Migrations (Critical!)
make migrate # or: docker compose exec api alembic upgrade head -
Access the Application
- Frontend: http://localhost (served by nginx on port 80; the frontend container runs
npm run devautomatically) - API Docs: http://localhost:8000/docs
- Neo4j Browser: http://localhost:7474
- Username:
neo4j - Password: (from
.envNEO4J_PASSWORD)
- Username:
- MinIO Console: http://localhost:9001
- Credentials: (from
.envOBJECT_STORAGE_ACCESS_KEY/OBJECT_STORAGE_SECRET_KEY)
- Credentials: (from
- Frontend: http://localhost (served by nginx on port 80; the frontend container runs
-
Verify Health
curl http://localhost:8000/health # Should return: {"status": "healthy"} -
Generate an API Key
make generate-key # or: docker compose exec api python -c "from src.shared.security import generate_api_key; print(generate_api_key())"Save the generated key - you'll need it for API requests.
-
Upload Your First Document (via API)
curl -X POST "http://localhost:8000/v1/documents" \ -H "X-API-Key: your-api-key-here" \ -F "file=@path/to/document.pdf"
-
Check Processing Status
curl "http://localhost:8000/v1/documents/{document_id}/status" \ -H "X-API-Key: your-api-key-here"
-
Query the Knowledge Base
curl -X POST "http://localhost:8000/v1/query" \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "query": "What are the main topics in my documents?", "options": { "search_mode": "basic", "include_sources": true } }'
Key configuration options in .env:
# Application
API_HOST=0.0.0.0
API_PORT=8000
DEBUG=false
LOG_LEVEL=INFO
# Security
SECRET_KEY=your-secret-key-here
# Set to false once every API key has explicit tenant links, so that an
# unlinked key is rejected instead of falling back to the default tenant
ALLOW_LINKLESS_KEY_DEFAULT_TENANT=true# PostgreSQL
DATABASE_URL=postgresql+asyncpg://graphrag:graphrag@postgres:5432/graphrag
POSTGRES_USER=graphrag
POSTGRES_PASSWORD=graphrag
POSTGRES_DB=graphrag
# Neo4j
NEO4J_URI=bolt://neo4j:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=changeme
# Milvus
MILVUS_HOST=milvus
MILVUS_PORT=19530
# MinIO (S3-compatible object storage)
OBJECT_STORAGE_HOST=minio
OBJECT_STORAGE_PORT=9000
OBJECT_STORAGE_ACCESS_KEY=minioadmin
OBJECT_STORAGE_SECRET_KEY=minioadmin
OBJECT_STORAGE_BUCKET_NAME=documents
# Redis
REDIS_URL=redis://redis:6379/0
CELERY_BROKER_URL=redis://redis:6379/1
CELERY_RESULT_BACKEND=redis://redis:6379/2OPENAI_API_KEY=sk-proj-...
ANTHROPIC_API_KEY=sk-ant-...
# The reference deployment runs Ollama Cloud on gemma4:31b-cloud.
# With neither variable set, config/settings.yaml applies: openai / gpt-4.1-mini.
DEFAULT_LLM_PROVIDER=ollama_cloud # openai | anthropic | ollama | ollama_cloud
DEFAULT_LLM_MODEL=gemma4:31b-cloud
OLLAMA_CLOUD_API_KEYS=key_account1,key_account2
# Embeddings default to ollama / nomic-embed-text / 768 in config/settings.yaml.
# Set these to override; changing the dimension count requires a new collection.
DEFAULT_EMBEDDING_PROVIDER=ollama # openai | ollama | local
DEFAULT_EMBEDDING_MODEL=nomic-embed-text
EMBEDDING_DIMENSIONS=768OLLAMA_BASE_URL=http://localhost:11434/v1
OLLAMA_MODEL=llama3.2
# Context window — increase for large RAG prompts
OLLAMA_NUM_CTX=32768
# Concurrent-request capacity guard (prevents GPU OOM under load)
OLLAMA_CAPACITY_ENABLED=true
OLLAMA_CAPACITY_TOTAL=6
OLLAMA_CAPACITY_RESERVED_CHAT=2
OLLAMA_CAPACITY_RESERVED_INGESTION=2RATE_LIMIT_REQUESTS_PER_MINUTE=60
RATE_LIMIT_REQUESTS_PER_HOUR=1000
RATE_LIMIT_QUERIES_PER_MINUTE=20
RATE_LIMIT_UPLOADS_PER_HOUR=50# Application DB role — enforces Row-Level Security (non-superuser)
# Docker network name required (not localhost); omit to fall back to superuser connection
APP_DATABASE_URL=postgresql+asyncpg://graphrag_app:<password>@postgres:5432/graphrag
# Secret key rotation — set OLD value during rotation window, then remove after
SECRET_KEY_OLD=previous-secret-key-here
# LLM capacity guard behaviour (default: fail-closed; set true to allow requests through when Redis is absent)
LLM_CAPACITY_FAIL_OPEN=falseimport requests
url = "http://localhost:8000/v1/documents"
headers = {"X-API-Key": "your-api-key"}
files = {"file": open("report.pdf", "rb")}
response = requests.post(url, headers=headers, files=files)
print(response.json())payload = {
"query": "What are the key findings?",
"options": {
"search_mode": "basic",
"include_sources": true
}
}
response = requests.post(
"http://localhost:8000/v1/query",
headers={"X-API-Key": "your-api-key"},
json=payload
)# Local search - entity-focused
payload = {"query": "...", "options": {"search_mode": "local"}}
# Global search - community summaries
payload = {"query": "...", "options": {"search_mode": "global"}}
# Drift search - iterative reasoning
payload = {"query": "...", "options": {"search_mode": "drift"}}curl -N "http://localhost:8000/v1/query/stream?query=Explain..." \
-H "X-API-Key: your-api-key"Every mode below is reachable per query, either by naming it explicitly or by letting the router pick one.
- Hybrid Retrieval: Combines dense (semantic) and sparse (SPLADE) vectors, so keyword matches and paraphrases both land
- Dense:
nomic-embed-textembeddings (768 dimensions) by default; any provider model works, the collection is built from the configured dimension count - Sparse (New): Learned keyword expansion using SPLADE (cocondenser-ensembledistil)
- Native Fusion: Uses Reciprocal Rank Fusion (RRFRanker) in Milvus
- Result caching with Redis for performance
- Local Search: Entity-focused traversal for precise information
- Global Search: Hierarchical community summaries for broad questions
- Drift Search: Agentic, iterative exploration with dynamic follow-up questions
- Graph Traversal: Multi-hop relationship exploration
- Query Rewriting: Improves ambiguous or poorly-formed queries
- Query Decomposition: Breaks complex questions into sub-queries
- HyDE (Hypothetical Document Embeddings): Generates hypothetical answers to improve retrieval
- Query Routing: Automatically selects the best search strategy
- Structured Query Detection: Bypasses RAG for simple list/count queries
- Automatic Classification: Documents stamped with a taxonomy label (
AdminGuide,CEGuide,UserGuide,ZendeskKB) at ingestion time based on folder name and keyword heuristics - Product Context Resolution: Deterministic
ProductContextResolvermaps CE/admin/user terminology in the query to edition and audience, with no LLM call - Taxonomy-Aware Retrieval: Pre-filters the candidate pool by taxonomy before vector/graph search, with a 4-stage broadening fallback (strict → edition-only → audience-only → unfiltered) to guarantee recall
- Observability: Taxonomy routing decisions are recorded in the execution trace and surfaced through the observability admin endpoint
- Backfill Script:
scripts/backfill_document_taxonomy.pyre-classifies existing documents without re-ingestion
Entities and relations are extracted per chunk, deduplicated, then clustered into communities that can be summarized and searched on their own.
- LLM-powered extraction from document chunks
- Gleaning: Iterative extraction to maximize recall
- Supports multiple entity types and relationship patterns
- Automatic entity deduplication and linking
- Hierarchical Leiden Algorithm for multi-level clustering
- Configurable resolution for cluster granularity
- Automatic community summarization using LLMs
- Community embedding for similarity search
- Incremental updates without full rebuilds
- Maintenance operations: deduplication, enrichment, summarization
- Graph statistics and health monitoring
- Tenant isolation for multi-tenant deployments
- Shared Corpus:
defaulttenant acts as the enterprise knowledge base; other tenants consume shared content without duplication via explicit ACL grants - ACL-Enforced Retrieval: Vector search, graph traversal, and global search all filter by document-share visibility; non-shared
defaultdocuments are invisible to other tenants - Document Shares API: Share documents to specific tenants at upload time or via the document library; bulk workflows available in the admin UI
- Short-Lived Cache: Visible shared document ID set is cached in Redis with explicit invalidation on share mutations for low-latency ACL checks
- Runtime Kill Switches: Operators can independently disable share management, vector ACL, or graph ACL without a code deployment
Everything between an uploaded file and a queryable chunk runs in Celery, with the document's state tracked in Postgres so a crashed worker does not lose the job.
- PDF: configured local extraction via Docling, Kreuzberg, or PyMuPDF4LLM; Unstructured remains the general parser route when no specialist is selected
- Markdown: Native parsing with structure preservation
- Text: Direct ingestion
- External Sources: Connectors for Confluence, Zendesk
- Semantic Chunking: Respects document structure (headers, paragraphs, code blocks)
- Configurable Parameters: Chunk size, overlap, and strategy
- Token-aware: Uses tiktoken for accurate token counting
- Preserves document hierarchy and context
- Celery Workers: Async task processing with Redis broker
- State Machine: Tracks document status through pipeline stages
- Automatic Retries: Exponential backoff with jitter
- Stale Job Recovery: Detects and recovers hung or abandoned tasks
- Progress Tracking: Real-time status updates
- Content-based hashing (SHA-256)
- Automatic detection of duplicate uploads
- Idempotent ingestion API
Generation is provider-agnostic, streams over SSE when you call the stream endpoint, and attaches per-chunk citations plus a quality score to every answer.
- OpenAI: GPT-4o, GPT-4o-mini, GPT-4.1-mini, GPT-4.1-nano, GPT-4-turbo, o1
- Anthropic: Claude Sonnet 4, Claude 3.5 Sonnet, Claude 3.5 Haiku, Claude 3 Opus
- Ollama: Local LLM support (Llama 3, Mistral, DeepSeek, Phi-3, Qwen, etc.)
- Ollama Cloud:
https://ollama.com/v1directly, with sequential failover across several API keys; this is what the reference deployment runs, ongemma4:31b-cloud - Tiered Providers: Economy (extraction), Standard (RAG), Premium (evaluation)
- Streaming: Server-Sent Events for real-time token streaming
- Cost Tracking: Token usage and cost estimation per query
- Deterministic Scorer: Rates each query plus its retrieved context across 9 dimensions with no LLM call, then assigns a complexity tier (
simple,standard,complex,reasoning) that selects the Ollama model. This axis is unrelated to the provider tiers above, despite both using the wordstandard - Per-Tenant Opt-In: Off unless enabled for the tenant
- Observable: The chosen tier is emitted as a
complexity_tierSSE event; Ollama thinking mode is available at the reasoning tier
- Ollama: External service (via API). Best for existing Ollama users, GPU offloading, and model flexibility (e.g.,
nomic-embed-text,mxbai-embed-large). - Local: Internal native library (
sentence-transformers). Best for zero-setup, self-contained usage. Runs models likeBAAI/bge-m3directly within the application (requires ~1-2GB RAM).
- Chunk-level citations with relevance scores
- Document attribution with titles and metadata
- Source deduplication across retrieval results
- Preview snippets for context
- Interactive Citation Explorer: Click-through source navigation with highlighting
- Faithfulness checks: Ensures answers are grounded in sources
- Relevance scoring: Filters irrelevant results
- Follow-up suggestions: Generates contextual next questions
- Ragas Integration: Automated evaluation with standard metrics
- Quality Badge: Visual score indicator for response confidence
- Routing Badge: Shows which retrieval mode was used (Basic/Local/Global/Drift)
- Persisted Metrics: Badges saved with conversation history
- Thumbs Up/Down: Direct feedback on AI responses
- Pending Review Queue: Admin review of user feedback
- Q&A Library: Verified responses for training/fine-tuning
- Golden Dataset Export: Export approved Q&A pairs for evaluation
The admin UI covers the operational surface: documents, connectors, jobs, backups, evaluation, and live tuning of retrieval parameters.
- Upload Wizard: Batch upload with drag-and-drop
- Live Status Tracking: Real-time ingestion progress
- Document Details: View chunks, entities, relationships, communities
- Database Overview: Graph statistics and health metrics
- Vector Store Inspection: Collection stats and memory usage
- PDF Viewer: In-browser PDF viewing with page navigation
- Conversation Export: Export chat history as PDF or Markdown
- Confluence: Sync wiki pages from Atlassian Confluence Cloud
- Zendesk: Ingest Help Center articles from Zendesk
- Content Browser: Browse and selectively ingest items from connected services
- Incremental Sync: Efficient updates using
sincetimestamps
- Job Dashboard: Monitor active, pending, and completed tasks
- Job Controls: Cancel, retry, or view logs for any job
- Queue Monitoring: Real-time inspection of Celery queues
- Worker Health: Track worker status and task concurrency
- Stop All Jobs: Emergency termination of all running tasks
- Full System Backup: Complete archive of PostgreSQL (metadata), Neo4j (graph), Milvus (vectors), and MinIO (files)
- User Data Backup: Lightweight portability scope (Vectors, Graph, Chunks) sans system configs
- Point-in-Time Recovery: Restore capability with "Merge" or "Replace" strategies
- Scheduled Backups: Automated daily/weekly snapshots with retention policies
- Scripted Backup:
scripts/backup.shcovers postgres, neo4j, redis, milvus, etcd, minio, uploads and config, selectable with--include=, and supports--dry-run
- Community Detection: Trigger full or incremental updates
- Graph Enrichment: Entity resolution and relationship strengthening
- Index Optimization: Rebuild vector indices
- Cache Management: Clear semantic and result caches
- System Health: Health checks across all services
- Ragas Integration: Faithfulness, relevance, precision, recall
- Golden Dataset Management: Upload and manage test sets
- Benchmark Execution: Batch evaluation with progress tracking
- Results Dashboard: Visualize scores and trends over time
- Tuning Dashboard: Adjust retrieval parameters without restarts
- Chunking Strategy: Modify chunk size and overlap
- Search Settings: Configure top-k, reranking, and fusion weights
- Provider Selection: Switch LLM and embedding providers
- Global Domain Rules: Define rules that apply to all queries via Admin UI
Multi-tenancy is enforced at the database layer, not only in application code, and the guards fail closed when Redis is missing.
- API Key Management: SHA-256 hashed keys stored in PostgreSQL; tiered scopes (
user,tenant_admin,super_admin) - Tenant Isolation: The tenant comes from the API key's links, optionally narrowed by an
X-Tenant-IDheader; asking for a tenant the key is not linked to returns 403. A key with no tenant links at all still falls back to thedefaulttenant and logs a warning on every request, a legacy bootstrap path you close withALLOW_LINKLESS_KEY_DEFAULT_TENANT=false - DB-Layer RLS:
FORCE ROW LEVEL SECURITYon 8 tenant tables via a dedicatedgraphrag_appPostgres role (NOBYPASSRLS); workers use a super-admin session flag to bypass legitimately - Rate Limiting: Per-tenant request and upload limits, fail-closed (HTTP 503) when Redis is unavailable
- Upload Size Limits: Configurable max file sizes
- Agent Filesystem Access: The Maintainer Agent's
read_file,list_directory, andgrep_searchtools are off unless a request explicitly setsagent_role; the default Knowledge Agent has graph and vector tools only
- Dual-Secret Keyring: Zero-downtime
SECRET_KEYrotation viaSECRET_KEY_OLDfallback; tokens signed under the old key remain valid during the rotation window - Connector Credential Encryption: OAuth tokens, passwords, and subdomain strings are encrypted at rest with Fernet (AES-128-CBC, key derived from
SECRET_KEY). They are never stored in plain JSONB - SSE Ticket One-Time Use: Auth tickets consumed atomically via Redis
GETDEL; replay within the TTL window is no longer possible - Exception Sanitization: HTTP 500 responses no longer include DB hostnames, connection strings, or raw exception text
- Circuit Breakers: Prevent cascade failures
- Graceful Degradation: Fallback to simpler modes on errors
- Retry Logic: Automatic retries with exponential backoff
- Structured Logging: JSON logs with request IDs
- Health Checks: Liveness and readiness probes
- Fail-Closed Capacity Guard: LLM capacity limiter fails closed when Redis is absent (
LLM_CAPACITY_FAIL_OPEN=falsedefault)
- Request Tracing: Request IDs for end-to-end tracking
- Timing Metrics: Per-request timing for every pipeline stage
- Cache Hit Rates: Monitor cache effectiveness
- Query Metrics: Track input/output tokens, costs, latency breakdowns (retrieval vs generation), and success/error rates per query
- Taxonomy Routing Metrics: Per-query taxonomy resolution stage and document pre-filter stats
- Event Stream: Real-time processing events via WebSockets
- Audit Trail: Config changes and curation actions record the actual caller identity in the audit log
Amber runs as a set of services behind an nginx edge proxy: a React frontend, a FastAPI API, Celery workers for everything slow, and six data stores that each hold one kind of state (Postgres, Neo4j, Milvus, Redis, MinIO, etcd).
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ Consumer Interface │ │ Admin Dashboard │ │
│ │ (/amber/chat) │ │ (/admin/*) │ │
│ │ - Clean chat UI │ │ - Document Management │ │
│ │ - SSE Streaming │ │ - Job Monitoring │ │
│ │ - Citation Display │ │ - System Operations │ │
│ └──────────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ API GATEWAY │
│ FastAPI (Python 3.11+) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Middleware: Auth, Rate Limit, CORS, Timing, Request ID │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Routes: /query, /documents, /admin/*, /health │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▼ ▼
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ COMPUTE LAYER │ │ WORKER LAYER │
│ │ │ │
│ ┌─────────────────────┐ │ │ ┌────────────────────────┐ │
│ │ Retrieval Service │ │ │ │ Celery Workers │ │
│ │ - Vector Search │ │ │ │ - Document Processing │ │
│ │ - Graph Traversal │ │ │ │ - Entity Extraction │ │
│ │ - Fusion & Rerank │ │ │ │ - Graph Construction │ │
│ └─────────────────────┘ │ │ │ - Community Detection │ │
│ │ │ └────────────────────────┘ │
│ ┌─────────────────────┐ │ │ │
│ │ Generation Service │ │ │ ┌────────────────────────┐ │
│ │ - LLM Orchestration │ │ │ │ Background Tasks │ │
│ │ - Streaming Support │ │ │ │ - Async Processing │ │
│ │ - Citation Building │ │ │ │ - State Management │ │
│ └─────────────────────┘ │ │ │ - Retry Logic │ │
│ │ │ └────────────────────────┘ │
└─────────────────────────────┘ └──────────────────────────────┘
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ DATA LAYER │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PostgreSQL │ │ Neo4j │ │ Milvus │ │
│ │ (Metadata) │ │ (Graph) │ │ (Vectors) │ │
│ │ │ │ │ │ │ │
│ │ - Documents │ │ - Entities │ │ - Embeddings │ │
│ │ - Chunks │ │ - Relations │ │ - Collections│ │
│ │ - Users/Keys │ │ - Communities│ │ - Indices │ │
│ │ - Jobs │ │ - Summaries │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Redis │ │ MinIO │ │ etcd (Milvus│ │
│ │ (Cache & │ │ (Object │ │ metadata) │ │
│ │ Broker) │ │ Storage) │ │ │ │
│ │ │ │ │ │ │ │
│ │ - Embeddings │ │ - Raw Files │ │ - Config │ │
│ │ - Results │ │ - Documents │ │ - State │ │
│ │ - Task Queue │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ OpenAI API │ │ Anthropic API │ │
│ │ - Embeddings │ │ - Claude Models │ │
│ │ - GPT Models │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Layer | Component | Technology | Purpose |
|---|---|---|---|
| Frontend | UI Framework | React 19 + Vite | Modern reactive UI with fast HMR |
| Router | TanStack Router v1 | Type-safe routing | |
| State | Zustand + TanStack Query | Global state & server state management | |
| Styling | Tailwind CSS + shadcn/ui | Utility-first CSS with components | |
| UI Components | Radix UI + Framer Motion | Accessible components with animations | |
| Graph Viz | React Force Graph 2D/3D | Interactive knowledge graph visualization | |
| API | Framework | FastAPI 0.109+ | High-performance async API |
| Runtime | Python 3.11+ | Modern Python with type hints | |
| Server | Uvicorn | ASGI server with hot reload | |
| Validation | Pydantic v2 | Data validation and serialization | |
| Databases | Metadata | PostgreSQL 16 | ACID-compliant relational data |
| Graph | Neo4j 5 Community | Property graph with Cypher queries | |
| Vector | Milvus 2.5+ | Hybrid search (Dense + Sparse) | |
| Cache | Redis 7 | In-memory cache & message broker | |
| Object Storage | MinIO | S3-compatible file storage | |
| Coordination | etcd | Milvus metadata store (internal) | |
| Processing | Task Queue | Celery 5.3+ | Distributed async task processing |
| Broker | Redis | Task queue backend | |
| Migrations | Alembic | Database schema versioning | |
| External | LLM Providers | OpenAI, Anthropic | Text generation & embeddings |
| Extraction | Unstructured, PyMuPDF4LLM | Multi-format document parsing | |
| Reranking | FlashRank | Fast semantic reranking | |
| Graph Clustering | igraph + leidenalg | Community detection | |
| Evaluation | Ragas | RAG metrics evaluation | |
| Infra | Orchestration | Docker Compose | Service orchestration |
Ingestion uses structure-aware semantic chunking rather than fixed-size splitting.
- Hierarchy-First Splitting: The
SemanticChunker(src/core/ingestion/application/chunking/semantic.py) respects document anatomy. It protects code blocks first, then splits by:- Markdown Headers (
#,##, ...) to preserve topological context. - Paragraphs (
\n\n) to maintain flow. - Sentences (via regex) as a last resort for dense text.
- Markdown Headers (
- Domain-Adaptive Sizing: Chunk sizes and overlaps are automatically optimized based on document type (defined in
src/core/generation/application/intelligence/strategies.py):- General (Default): 600 tokens / 50 overlap
- Technical (Code/Manuals): 800 tokens / 50 overlap
- Financial (Reports/Tables): 800 tokens / 50 overlap
- Scientific (Research Papers): 1000 tokens / 100 overlap
- Legal (Contracts/Clauses): 1000 tokens / 100 overlap
- Conversational: 500 tokens / 100 overlap
- Token-Aware Overlap: Rather than character-based overlap, tokens from the end of the previous chunk are prepended to the next to ensure semantic continuity.
- Chunk Quality Filtering: Implements a helper "Quality Coloring" system (
ChunkQualityScorer) that grades every chunk (0-1) based on text density, fragmentation, and OCR artifacts.- Noise Reduction: Low-quality chunks (< 0.3) that also yield zero graph entities are automatically discarded during extraction, preventing "garbage-in" from polluting the vector store.
- Resilient Embedding: The
EmbeddingServiceuses exponential backoff retries for rate limits and uses token-aware batching (max 8000 tokens/batch) to optimize API throughput.
Graph construction runs in two stages: iterative entity extraction, then community detection over the extracted entities.
- Entity Definition: Entities are defined via flexible Pydantic models, supporting over 30+ domain-specific types alongside standard named entities.
- Core Types:
PERSON,ORGANIZATION,LOCATION,EVENT,CONCEPT,DOCUMENT,TECHNOLOGY,PRODUCT,DATE,MONEY,ARTICLE. - Infrastructure Types:
COMPONENT,SERVICE,NODE,DOMAIN,CLASS_OF_SERVICE,RESOURCE,QUOTA_OBJECT,STORAGE_OBJECT,BACKUP_OBJECT,ITEM. - Operational Types:
ACCOUNT,ACCOUNT_TYPE,ROLE,TASK,PROCEDURE,MIGRATION_PROCEDURE,CLI_COMMAND,API_OBJECT,CONFIG_OPTION,CERTIFICATE,SECURITY_FEATURE. - Schema: Every extracted entity includes a
name(capitalized),type,description(self-contained summary). - Relationships:
source,target,type(e.g.,DEPENDS_ON,PROTECTS,RUNS_ON), andweight(1-10 strength score).
- Core Types:
- Generation Mechanism (Dynamic Ontology Injection):
- The 30+ types are dynamically injected into the LLM system prompt as a canonical ontology (
{entity_types_str}). - The LLM is strictly instructed to classify entities only into these allowed types.
- Output Format: The system uses a strict Tuple-Delimited Format (e.g.,
("entity"<|>NAME<|>TYPE...)) to prevent parsing errors common with standard JSON, ensuring high-fidelity extraction even from messy text.
- The 30+ types are dynamically injected into the LLM system prompt as a canonical ontology (
- Gleaning (Iterative Extraction): Implemented in
GraphExtractor, this technique prevents "extraction amnesia."- Pass 1: Zero-shot extraction of entities and relationships (Temperature 0.1).
- Pass 2 (Gleaning): The LLM is fed the text and the entities found in Pass 1, and asked "What did you miss?". This raises recall on dense documents.
- Leiden Community Detection: We use the hierarchical Leiden algorithm to cluster entities into communities.
- Summarization: Each community is summarized by an LLM to create a "Community Node," enabling Global Search (answering "What is the main theme?" by reading summaries rather than thousands of raw chunks).
- Quality Assurance (Hybrid Scoring): To prevent hallucinations and low-quality extractions, a strict scoring system is applied:
- Intrinsic Confidence: Entities with an LLM-generated
importance_score < 0.5are automatically discarded. - Extrinsic Validation: A
QualityScorermodule evaluates generated answers and critical extractions on 4 dimensions: Context Relevance, Completeness, Factual Grounding, and Coherence, using a mix of LLM evaluation and heuristic checks.
- Intrinsic Confidence: Entities with an LLM-generated
Retrieval is handled by an orchestration layer that mixes deterministic and agentic strategies.
- Fusion (Hybrid Search): We employ Reciprocal Rank Fusion (RRF) to combine results from Milvus (Vector) and Neo4j (Keyword/Graph).
- Milvus Hybrid: Within Milvus itself, we combine Dense Vectors (Semantic) and Sparse Vectors (SPLADE/Keyword) to find the most relevant chunks.
- Graph Fusion: These results are then fused with graph traversals.
- Formula:
score = Σ(1 / (k + rank + 1)) - This ensures that a document appearing in both top-lists is ranked significantly higher than one appearing in only one.
- Drift Search (Agentic): Defined in
DriftSearchService(src/core/retrieval/application/search/drift_search.py), this is the heaviest retrieval mode:- Primer: Performs an initial standard retrieval (Top-5) to get a baseline context.
- Expansion Loop: The LLM analyzes the Primer results and generates Follow-Up Questions. These sub-queries are executed to "drift" to related graph neighborhoods.
- Synthesis: All accumulated context (Primer + Expansion) is deduplicated and fed to the LLM for a final, citation-backed answer.
For complex queries requiring multi-step reasoning, Amber employs a full Agentic RAG architecture using a ReAct (Reason+Act) loop.
- Agent Orchestrator: The
AgentOrchestrator(src/core/generation/application/agent/orchestrator.py) manages the loop:- Receive query → LLM decides: call a tool OR give final answer.
- If tool: execute, append result to context, repeat.
- Max 10 steps to prevent infinite loops.
- Available Tools:
Tool Description Mode search_codebaseVector search over documents Knowledge (default) query_graphExecute Cypher queries on Neo4j Knowledge read_file,list_directory,grep_searchFilesystem access Maintainer (opt-in) - Agent Modes: Two security levels controlled via
agent_roleparameter:- Knowledge Agent (default): Vector + Graph tools only. Safe for production.
- Maintainer Agent: Adds filesystem tools. Requires explicit opt-in.
- Resilient Content Fallback: If Milvus returns empty content, the system automatically fetches from PostgreSQL, with full observability (OTel event + log metric).
- Implementation:
src/core/generation/application/agent/,src/core/tools/.
Full OpenAPI specification at /docs. Key endpoints:
| Method | Endpoint | Description |
|---|---|---|
POST |
/v1/query |
Submit a RAG query |
GET/POST |
/v1/query/stream |
Stream query response via SSE |
POST |
/v1/documents |
Upload a document |
GET |
/v1/documents/{id} |
Get document details |
GET |
/v1/documents/{id}/status |
Check processing status |
| Method | Endpoint | Description |
|---|---|---|
GET |
/v1/admin/jobs |
List background jobs |
POST |
/v1/admin/jobs/{id}/cancel |
Cancel a job |
POST |
/v1/admin/maintenance/communities/detect |
Trigger community detection |
POST |
/v1/admin/ragas/benchmark/run |
Run evaluation |
| Method | Endpoint | Description |
|---|---|---|
GET |
/v1/connectors |
List available connector types |
GET |
/v1/connectors/{type}/status |
Get connector status |
POST |
/v1/connectors/{type}/connect |
Authenticate with credentials |
POST |
/v1/connectors/{type}/sync |
Trigger sync (full or incremental) |
GET |
/v1/connectors/{type}/items |
Browse content from connector |
POST |
/v1/connectors/{type}/ingest |
Ingest selected items by ID |
- Clean, focused chat interface
- Real-time streaming responses
- Inline citations with sources
- Follow-up question suggestions
- Documents: Upload, manage, view details
- Database Overview: Graph statistics
- Query Log: Granular inspection of recent RAG queries for debugging
- Vector Store: Milvus collection inspection
- Jobs: Monitor and control background tasks
- Queues: Real-time queue inspection
- Tuning: Dynamic parameter adjustment
- Ragas: Evaluation and benchmarking
-
Start Infrastructure
docker compose up -d postgres neo4j milvus redis minio etcd
-
Backend
python3.11 -m venv .venv source .venv/bin/activate pip install -r requirements.txt alembic upgrade head uvicorn src.api.main:app --reload -
Worker
source .venv/bin/activate celery -A src.workers.celery_app worker --loglevel=info -
Frontend
cd frontend npm install npm run dev # Runs on http://localhost:5173
The frontend ships a Dockerfile.prod and a Compose override that serve the built assets through Nginx instead of the Vite dev server.
make format # Format code
make lint # Run linter
make typecheck # Type checkingmake migrate-new # Create migration
make migrate # Run migrationsRun the repository test targets for unit, integration, and coverage checks:
make test # Run all tests
make test-unit # Unit tests only
make test-int # Integration tests
make coverage # With coverage reportIndicative figures carried over from an earlier development deployment, not a benchmark: the corpus size, model, and hardware they came from were never recorded. Treat them as an order of magnitude and measure your own.
| Search Mode | Cold | Warm |
|---|---|---|
| Basic | 800ms | 250ms |
| Local | 1200ms | 400ms |
| Global | 2500ms | 800ms |
| Drift | 5000ms | 1500ms |
- Horizontal: Add more workers (
docker compose up -d --scale worker=4) - Vertical: Increase worker resources
- Caching: Tune Redis cache TTLs
- Database: Configure Neo4j/Milvus for your dataset size
Services won't start
docker compose logs api
docker compose restart apiDocument processing stuck
docker compose logs -f worker
# Check worker for errors, restart if neededQuery returns no results
- Check document processing status
- Verify vector collection exists
- Check embeddings API key
High memory usage
- Reduce worker concurrency
- Clear caches
- Adjust Redis maxmemory
Contributions are welcome.
- Fork & clone the repository
- Create a feature branch
- Make changes with tests
- Run
make testandmake lint - Submit a pull request
Follow Conventional Commits for commit messages.
Amber is released under the MIT License. See LICENSE for details. Release history is in CHANGELOG.md.