Configuration Reference — Complete Environment Variables & Settings
1. Configuration hierarchy
Settings are read in order of precedence (highest wins):
1. Environment variables (PLR_*) ← highest priority
2. Per-project settings (workspace/history.json)
3. File-based config (api_config.json)
4. Code defaults (config.py) ← lowest priority
Example:
python# Default in config.py
embedding_batch_size = 32
# Override via environment variable
export PLR_EMBEDDING_BATCH_SIZE=16
# → batch size is now 16
2. Model configuration
Embedding model (BGE-M3)
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_EMBEDDING_MODEL_DIR | models/bge-m3 | Path | Local model directory |
PLR_EMBEDDING_DIM | 1024 | Int | Embedding output dimension (BGE-M3 fixed) |
PLR_EMBEDDING_MAX_LENGTH | 512 | Int | Max tokens per text (512 is optimal for 480-token chunks) |
PLR_EMBEDDING_BATCH_SIZE | 32 | Int | CAP batch size (GPU manager may tune down from free VRAM) |
PLR_EMBEDDING_USE_FP16 | True | Bool | FP16 faster on GPU, auto-disabled on CPU |
PLR_EMBEDDING_NORMALIZE | True | Bool | L2-normalize embeddings (required for cosine similarity) |
Example:
bash# Lower batch size for 4GB GPU
export PLR_EMBEDDING_BATCH_SIZE=16
# Force CPU embedding
export PLR_DEVICE=cpu
export PLR_EMBEDDING_USE_FP16=False
Reranker model (BGE-Reranker-v2-M3)
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_RERANKER_MODEL_DIR | models/bge-reranker-v2-m3 | Path | Local model directory |
PLR_RERANKER_MAX_LENGTH | 512 | Int | Max tokens per query+doc pair |
PLR_RERANKER_BATCH_SIZE | 32 | Int | CAP batch size (GPU manager may tune down) |
PLR_RERANKER_USE_FP16 | True | Bool | FP16 faster on GPU, auto-disabled on CPU |
Example:
bash# Smaller batch for low-VRAM GPUs
export PLR_RERANKER_BATCH_SIZE=16
LLM (Ollama)
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_LLM_NUM_GPU | 0 | Int | GPU layers (0 = CPU-only, -1 = use GPU fully) |
PLR_LLM_KEEP_ALIVE | 5m | Str | How long LLM stays loaded after request |
PLR_LLM_NUM_THREAD | 0 | Int | CPU threads (0 = auto) |
PLR_LLM_VRAM_ESTIMATE_MB | 5000 | Int | Fallback VRAM estimate for 8B-class LLM |
PLR_OLLAMA_BASE_URL | http://localhost:11434 | URL | Ollama HTTP API endpoint |
Why PLR_LLM_NUM_GPU=0 by default:
- Embeddings + reranking are latency-critical for search
- LLM generation is least time-critical
- Keeping LLM on CPU frees entire VRAM budget for embeddings/reranking
- Set to
-1if you prefer faster generation over faster search
Example:
bash# Use LLM on GPU (faster generation, slower search)
export PLR_LLM_NUM_GPU=-1
# Unload LLM immediately after each call (free RAM)
export PLR_LLM_KEEP_ALIVE=0
Device selection
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_DEVICE | auto | Str | auto \ |
Auto resolution:
- Try CUDA (NVIDIA GPU)
- Try MPS (Apple Silicon)
- Fall back to CPU
Example:
bash# Force CPU (no GPU needed)
export PLR_DEVICE=cpu
3. GPU manager configuration
See GPU_MANAGEMENT.md for GPU manager design.
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_GPU_MANAGER_ENABLED | 1 | Bool | Master switch (0 = disabled, transparent no-op) |
PLR_GPU_HEADROOM_MB | 256 | Int | VRAM kept free for driver/display/other apps |
PLR_GPU_RESIDENCY_MODE | adaptive | Str | eager \ |
PLR_GPU_KEEP_RESIDENT_FREE_MB | 1024 | Int | (Adaptive) Min free VRAM to keep hot model resident |
PLR_GPU_HOT_MIN_USES | 2 | Int | (Adaptive) Uses to become "hot" |
PLR_GPU_HOT_WINDOW_SECONDS | 120 | Float | (Adaptive) Sliding window for hotness tracking |
PLR_GPU_MAX_RESIDENT | 2 | Int | (Adaptive) Max models resident simultaneously |
PLR_GPU_AUTO_BATCH | 1 | Bool | Auto-tune batch sizes from free VRAM |
PLR_GPU_BATCH_SAFETY_MB | 256 | Int | VRAM reserve when auto-tuning batch |
PLR_GPU_EVICT_OLLAMA | 1 | Bool | Allow ollama stop under VRAM pressure |
PLR_GPU_DEMOTE_AFTER_USE | — | Bool | Legacy: 1→eager, 0→persistent (still honored) |
Residency modes:
eager— demote always (idle models never occupy VRAM)adaptive— keep hot models resident, demote cold (best of both worlds)persistent— never demote (all used models stay resident)
Example:
bash# Aggressive GPU freeing (tiny GPU)
export PLR_GPU_RESIDENCY_MODE=eager
export PLR_GPU_MAX_RESIDENT=1
# Persistent (capable GPU)
export PLR_GPU_RESIDENCY_MODE=persistent
export PLR_GPU_MAX_RESIDENT=3
4. Chunking configuration
| Config Field | Default | Type | Meaning |
|---|---|---|---|
chunk_size | 480 | Int | Target tokens per chunk |
overlap | 0 | Int | Overlap tokens between adjacent chunks |
respect_sections | True | Bool | Never split across section boundaries |
min_chunk_size | 32 | Int | Discard chunks smaller than this |
unit | token | Str | token \ |
No env var overrides — chunking config is code-only (edit config.py).
Example (code override):
pythonfrom legal_retrieval.config import get_config, Config, ChunkingConfig
from dataclasses import replace
cfg = get_config()
new_chunking = replace(cfg.chunking, chunk_size=600, overlap=50)
new_cfg = replace(cfg, chunking=new_chunking)
5. Storage configuration
ChromaDB
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_CHROMA_DIR | data/chroma_db | Path | ChromaDB persistent directory (workspace overrides this) |
Other settings (code-only):
collection_name:legal_chunksdistance_metric:cosine- HNSW parameters:
m=16,construction_ef=100,search_ef=128
Workspace override:
- When a workspace is active →
<workspace>/workspace/chroma_db - No workspace →
data/chroma_db
Paths
All path defaults are in config.py → PathConfig:
| Path | Default | Env Var |
|---|---|---|
data_dir | data/ | — |
raw_dir | data/raw/ | — |
processed_dir | data/processed/ | — |
models_dir | models/ | — |
logs_dir | logs/ | — |
Workspace overrides:
raw_dir→<workspace>/(uploads land directly in project folder)processed_dir→<workspace>/workspace/processedchroma_dir→<workspace>/workspace/chroma_dbbm25_dir→<workspace>/workspace/bm25_index
6. Search & retrieval configuration
| Config Field | Default | Type | Meaning |
|---|---|---|---|
top_k_retrieval | 50 | Int | Initial retrieval from ChromaDB + BM25 |
top_k_final | 5 | Int | Final result count after reranking |
min_score_threshold | 0.0 | Float | Drop chunks below this reranker score |
rerank | True | Bool | Enable reranking |
search_mode | hybrid | Str | dense \ |
rrf_k | 60 | Int | RRF constant (higher = less weight to rank) |
hybrid_merge_strategy | combine_and_dedup | Str | combine_and_dedup \ |
No env var overrides — search config is:
- Code defaults (
config.py) - Runtime pipeline config (
POST /api/pipeline/config) - Per-request overrides (
ChatRequest.skip_rerank,top_k)
Example (runtime config):
bash# Apply pipeline layout → reconfigures search
curl -X POST http://localhost:8765/api/pipeline/config \
-H "Content-Type: application/json" \
-d @pipeline-layout.json
Example (per-request override):
bash# Single query with top_k=10, skip rerank
curl -X POST http://localhost:8765/api/chat \
-H "Content-Type: application/json" \
-d '{"query":"What is rescission?","top_k":10,"skip_rerank":true}'
7. Ingestion configuration
| Config Field | Default | Type | Meaning |
|---|---|---|---|
do_ocr | True | Bool | OCR for scanned PDFs |
do_table_structure | True | Bool | Extract table structure |
ocr_lang | ["en"] | List | OCR languages |
generate_page_images | True | Bool | Generate page images (improves OCR) |
image_dpi | 150 | Int | Image DPI for OCR fallback |
max_pages | None | Int | Max pages per document (None = unlimited) |
No env var overrides — ingestion config is code-only.
Example (disable OCR for faster ingestion):
pythonfrom legal_retrieval.config import get_config, Config, IngestionConfig
from dataclasses import replace
cfg = get_config()
new_ingestion = replace(cfg.ingestion, do_ocr=False, generate_page_images=False)
new_cfg = replace(cfg, ingestion=new_ingestion)
8. Logging configuration
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_LOG_LEVEL | INFO | Str | DEBUG \ |
Log files:
logs/legal_retrieval.log— rotating file (10 MB, 5 backups)- Console (stderr) — same level as file
Example:
bash# Debug logging
export PLR_LOG_LEVEL=DEBUG
python backend/main.py
Programmatic setup:
pythonfrom legal_retrieval.logging_setup import setup_logging
setup_logging(level="DEBUG", force=True)
9. Agent configuration
| Env Var | Default | Type | Meaning |
|---|---|---|---|
PLR_AGENT_POST_TOOL_REASONING_CHARS | 300 | Int | Max chars buffered as post-tool CoT |
PLR_AGENT_POST_TOOL_REASONING_SEGMENTS | 2 | Int | Max short segments counted as reasoning |
PLR_AGENT_POST_TOOL_REASONING_SHORT_CHARS | 80 | Int | Max chars for a segment to be "short" |
What it controls:
- Untagged text after a tool result is treated as chain-of-thought (CoT panel)
- Adaptive window: only the first N short segments count as reasoning
- Longer segments mean the answer body has begun → stop buffering CoT
Example:
bash# More aggressive CoT capture
export PLR_AGENT_POST_TOOL_REASONING_SEGMENTS=5
export PLR_AGENT_POST_TOOL_REASONING_CHARS=500
10. API provider configuration
See API_PROVIDER.md for cloud mode setup.
Configuration file
File: backend/api_config.json (git-ignored)
json{
"enabled": true,
"provider": "openai",
"base_url": "https://api.openai.com/v1",
"api_key": "sk-...",
"llm_model": "gpt-4o-mini",
"embed_model": "text-embedding-3-small",
"embed_dim": 0,
"rerank_mode": "auto",
"temperature": 0.3,
"max_tokens": 4096,
"timeout": 120,
"thinking_budget": 0
}
Environment variable overrides
| Env Var | JSON Key | Type | Meaning |
|---|---|---|---|
LAWYER_API_ENABLED | enabled | Bool | Master switch |
LAWYER_PROVIDER | provider | Str | openai \ |
LAWYER_API_BASE_URL | base_url | URL | Main endpoint |
LAWYER_API_KEY | api_key | Str | Main API key |
LAWYER_LLM_MODEL | llm_model | Str | LLM model name |
LAWYER_LLM_BASE_URL | (override) | URL | LLM-specific endpoint |
LAWYER_LLM_API_KEY | (override) | Str | LLM-specific key |
LAWYER_EMBED_MODEL | embed_model | Str | Embedding model name |
LAWYER_EMBED_BASE_URL | (override) | URL | Embeddings-specific endpoint |
LAWYER_EMBED_API_KEY | (override) | Str | Embeddings-specific key |
LAWYER_EMBED_DIM | embed_dim | Int | Embedding dimension (0 = auto-detect) |
LAWYER_RERANK_MODE | rerank_mode | Str | auto \ |
LAWYER_RERANK_BASE_URL | (override) | URL | Rerank-specific endpoint |
LAWYER_RERANK_API_KEY | (override) | Str | Rerank-specific key |
LAWYER_RERANK_MODEL | (override) | Str | Rerank model name |
LAWYER_TEMPERATURE | temperature | Float | LLM temperature |
LAWYER_MAX_TOKENS | max_tokens | Int | LLM max tokens |
LAWYER_TIMEOUT | timeout | Int | API timeout (seconds) |
LAWYER_THINKING_BUDGET | thinking_budget | Int | Anthropic extended thinking budget (tokens) |
Precedence: env vars > JSON file > code defaults
Example:
bash# Quick test with OpenAI
export LAWYER_API_ENABLED=1
export LAWYER_PROVIDER=openai
export LAWYER_API_KEY=sk-...
export LAWYER_LLM_MODEL=gpt-4o-mini
python backend/main.py
11. Workspace configuration
Active workspace
Set via API:
bashcurl -X POST http://localhost:8765/api/workspace \
-H "Content-Type: application/json" \
-d '{"path":"/absolute/path/to/project"}'
Or via frontend "Work in a folder" button (native directory picker).
Workspace storage
When a workspace is active:
- Raw files →
<workspace>/ - ChromaDB →
<workspace>/workspace/chroma_db/ - BM25 →
<workspace>/workspace/bm25_index/ - Processed →
<workspace>/workspace/processed/ - History →
<workspace>/workspace/history.json
Runtime-only (resets on restart or DELETE /api/workspace).
Per-project GPU profile
Stored in <workspace>/workspace/history.json under "gpu_profile":
json{
"gpu_profile": {
"residency_mode": "adaptive",
"keep_resident_free_mb": 1024,
"hot_min_uses": 2,
"hot_window_seconds": 120,
"max_resident": 2
},
"conversations": [ ... ],
"working_set": [ ... ]
}
Applied automatically when workspace is selected.
12. Frontend configuration
Build-time configuration
File: frontend/.env.local (git-ignored)
VITE_API_BASE_URL=http://localhost:8765
Usage:
typescriptconst API_BASE = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8765';
Runtime configuration (Electron)
File: frontend/electron/src/main/config.ts
typescriptexport const CONFIG = {
backend: {
port: 8765,
host: '127.0.0.1',
healthCheckInterval: 500,
healthCheckTimeout: 30000,
},
vite: {
port: 5173,
},
};
13. Launcher configuration
File: backend/api_config.json (written by launcher)
See §10 API provider configuration.
Launcher-specific env vars:
| Env Var | Default | Meaning |
|---|---|---|
LAWYER_API_CONFIG | backend/api_config.json | Path to API config file |
LAWYER_PYTHON | (auto-detected) | Python interpreter for backend |
LAWYER_OLLAMA_MODEL | lfm2.5:8b | Default Ollama model to pull |
14. Configuration files reference
| File | Purpose | Git-ignored? |
|---|---|---|
backend/api_config.json | API provider settings | ✅ Yes |
<workspace>/workspace/history.json | Per-project chat history + GPU profile | ❌ No (user data) |
data/conversations.db | LangGraph thread state (SQLite) | ❌ No (user data) |
data/flags.db | Compliance flags (SQLite) | ❌ No (user data) |
logs/legal_retrieval.log | Rotating log file | ✅ Yes |
frontend/.env.local | Frontend build-time config | ✅ Yes |
15. Configuration validation
Check current config:
bashcurl http://localhost:8765/api/health
Returns:
json{
"status": "ok",
"service": "lawyer-assistant",
"version": "1.0.0",
"config": {
"device": "cuda",
"embedding_dim": 1024,
"search_mode": "hybrid",
"rerank_enabled": true,
"gpu_manager_enabled": true,
"workspace_active": true
}
}
Check GPU config:
bashcurl http://localhost:8765/api/gpu/status
Check API provider:
bashcurl http://localhost:8765/api/providers/status
16. Common configuration scenarios
Scenario 1: Low-VRAM GPU (4GB)
bashexport PLR_GPU_RESIDENCY_MODE=eager
export PLR_GPU_MAX_RESIDENT=1
export PLR_EMBEDDING_BATCH_SIZE=16
export PLR_RERANKER_BATCH_SIZE=16
export PLR_GPU_HEADROOM_MB=512
Scenario 2: High-VRAM GPU (24GB+)
bashexport PLR_GPU_RESIDENCY_MODE=persistent
export PLR_GPU_MAX_RESIDENT=3
export PLR_EMBEDDING_BATCH_SIZE=64
export PLR_RERANKER_BATCH_SIZE=64
export PLR_LLM_NUM_GPU=-1 # use GPU for LLM too
Scenario 3: CPU-only (no GPU)
bashexport PLR_DEVICE=cpu
export PLR_EMBEDDING_USE_FP16=False
export PLR_RERANKER_USE_FP16=False
export PLR_GPU_MANAGER_ENABLED=0
export PLR_EMBEDDING_BATCH_SIZE=4 # smaller for RAM
Scenario 4: Cloud API mode (no local models)
bashexport LAWYER_API_ENABLED=1
export LAWYER_PROVIDER=openai
export LAWYER_API_KEY=sk-...
export LAWYER_LLM_MODEL=gpt-4o-mini
export LAWYER_EMBED_MODEL=text-embedding-3-small
Scenario 5: Debugging
bashexport PLR_LOG_LEVEL=DEBUG
export PLR_GPU_MANAGER_ENABLED=1 # watch GPU logs
python backend/main.py 2>&1 | tee debug.log
17. Configuration troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| "Config not loaded" | Typo in env var name | Check PLR_ prefix |
| API key rejected | Wrong key or expired | Check api_config.json |
| GPU not used | Device set to CPU | Check PLR_DEVICE |
| Slow ingestion | Batch size too small | Increase PLR_EMBEDDING_BATCH_SIZE |
| OOM during search | Batch size too large | Decrease batch sizes |
| Logs not created | Logs dir missing | mkdir logs/ |
| Workspace not active | No folder selected | POST /api/workspace |
18. Related documentation
| Doc | Coverage |
|---|---|
GPU_MANAGEMENT.md | GPU configuration deep dive |
API_PROVIDER.md | Cloud mode configuration |
WORKSPACE.md | Workspace configuration |
BACKEND.md | Runtime pipeline config |
TROUBLESHOOTING.md | Config-related failures |
DEVELOPMENT.md | Dev environment config |