1. Architecture Overview
PrivateGPT is a privacy-focused RAG (Retrieval-Augmented Generation) platform that wraps LlamaIndex behind an OpenAI-compatible FastAPI server and an optional Gradio web UI. It enables document ingestion, embedding, vector search, and LLM-powered Q&A entirely within a private infrastructure — no data leaves the host environment.
The system follows a dependency-injection architecture using the injector library, with all component wiring centralized in the application bootstrapper. Settings are loaded from mergeable YAML profiles with environment variable substitution. The server exposes a stateless REST API that delegates to service classes for chat, ingestion, chunk retrieval, summarization, and embeddings.
Architecture Diagram
┌─────────────────────────┐
│ Gradio Web UI │
│ (optional, /path) │
└────────────┬────────────┘
│ mount_in_app()
┌────────────────────────────────┼────────────────────────────────┐
│ FastAPI App │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ /v1/chat │ │/v1/ingest│ │/v1/chunks│ │ /health │ ... │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────────┘ │
│ │ │ │ │
│ ┌────┴─────┐ ┌────┴─────┐ ┌───┴──────┐ │
│ │ChatService│ │IngestSvc │ │ChunksSvc │ (DI via Injector) │
│ └────┬─────┘ └────┬─────┘ └───┬──────┘ │
└───────┼─────────────┼───────────┼──────────────────────────────┘
│ │ │
┌───────┴─────────────┴───────────┴──────────────────────────────┐
│ LlamaIndex Core │
│ ┌──────────┐ ┌──────────────┐ ┌────────────┐ │
│ │LLM Component│ │Embedding │ │VectorStore │ │
│ │(pluggable) │ │Component │ │Component │ │
│ └────────────┘ └──────────────┘ └────────────┘ │
│ llamacpp huggingface qdrant │
│ ollama ollama chroma │
│ openai openai postgres │
│ azopenai azopenai clickhouse │
│ gemini gemini milvus │
│ sagemaker sagemaker │
│ mock mock │
└─────────────────────────────────────────────────────────────────┘
Component Inventory
| Component | Path | Role |
|---|---|---|
| App entry point | private_gpt/__main__.py |
Starts uvicorn with the FastAPI app |
| App factory | private_gpt/launcher.py |
Creates FastAPI app, registers routers, mounts UI |
| App singleton | private_gpt/main.py |
Exports the app object for uvicorn |
| DI container | private_gpt/di.py |
Creates and configures the Injector instance |
| Settings | private_gpt/settings/ |
YAML-based config with profile merging |
| Server routers | private_gpt/server/ |
FastAPI route handlers |
| Components | private_gpt/components/ |
LLM, embedding, ingest, vector/node store wrappers |
| OpenAI compat | private_gpt/open_ai/ |
OpenAI response format models |
| Gradio UI | private_gpt/ui/ui.py |
Web interface with 4 interaction modes |
| Utilities | private_gpt/utils/ |
ETA, Ollama helpers, retry decorator |
| Paths | private_gpt/paths.py |
Derived paths for models, docs, local_data |
| Constants | private_gpt/constants.py |
PROJECT_ROOT_PATH |
Technology Stack
| Layer | Technology |
|---|---|
| Language | Python 3.11+ |
| Web framework | FastAPI + Uvicorn |
| AI framework | LlamaIndex |
| DI framework | Injector |
| Settings | Pydantic v2 models + YAML (PyYAML SafeLoader) |
| Web UI | Gradio |
| LLM backends | llama.cpp, Ollama, OpenAI, Azure OpenAI, Gemini, SageMaker |
| Embedding backends | HuggingFace, Ollama, OpenAI, Azure OpenAI, Gemini, SageMaker, Mistral AI |
| Vector stores | Qdrant, Chroma, PostgreSQL, ClickHouse, Milvus |
| Node stores | Simple (file-based), PostgreSQL |
| Package management | Poetry |
| Code quality | Black, Ruff, mypy, pre-commit |
| Testing | pytest |
| API docs | Fern (OpenAPI-driven) |
Data Flow: RAG Query
User query → FastAPI chat_router
→ ChatService.stream_chat(messages, use_context=True)
→ LlamaIndex query engine
→ EmbeddingComponent → vector similarity search
→ VectorStoreComponent → retrieve top-k chunks
→ (optional) RerankSettings → cross-encoder rerank
→ LLMComponent → generate response with context
← streaming ChatResponse + source Chunks
← SSE stream (OpenAI format) or JSON response
Data Flow: Document Ingestion
File upload → FastAPI ingest_router
→ IngestService.ingest(file)
→ LlamaIndex document parser
→ Split into chunks/nodes
→ EmbeddingComponent → generate embeddings
→ VectorStoreComponent → store vectors
→ NodeStoreComponent → store document nodes
← IngestResponse with doc_ids + metadata
2. System Requirements
| Resource | Development | Production |
|---|---|---|
| Python | 3.11+ | 3.11+ |
| RAM | 8 GB | 16 GB+ (32 GB for large models) |
| CPU | 4 cores | 8+ cores (for parallel ingestion) |
| Disk | 10 GB | 50 GB+ (models + vector data) |
| GPU | Optional (llama.cpp) | Recommended for local LLMs |
| Network | Required for model downloads | Optional (fully offline after setup) |
Note
When using cloud LLM backends (OpenAI, Azure, Gemini), the RAM and GPU requirements drop significantly since inference runs remotely. Local llama.cpp and HuggingFace embeddings are the primary consumers of local resources.
Dependencies (Key Libraries)
| Library | Role |
|---|---|
fastapi |
REST API framework |
uvicorn |
ASGI server |
llama-index-core |
RAG orchestration |
injector |
Dependency injection |
pydantic |
Settings validation |
gradio |
Web UI (optional extra) |
ollama |
Ollama client (optional extra) |
qdrant-client |
Qdrant vector store (optional extra) |
chromadb |
Chroma vector store (optional extra) |
tqdm |
Progress bars for model downloads |
retry-async |
Retry decorator with jitter |
httpx |
HTTP client (Ollama connectivity) |
tiktoken |
Tokenization (bundled cache for offline use) |
3. Configuration Reference
Configuration Architecture
Settings are loaded through a profile-merging system in private_gpt/settings/settings_loader.py:
- The
defaultprofile (settings.yaml) is always loaded first - Profiles from
PGPT_PROFILESenv var are loaded in order and deep-merged - If running tests (
tests.fixturesinsys.modules), thetestprofile is appended - The merged dict is validated against the
SettingsPydantic model
Important
Profile merging uses deep update — nested keys are merged, not replaced wholesale. A profile only needs to specify the keys it wants to override.
Environment Variables
| Variable | Default | Description |
|---|---|---|
PGPT_PROFILES |
(empty) | Comma-separated list of profiles to activate |
PGPT_SETTINGS_FOLDER |
project root | Directory containing settings YAML files |
APP_ENV |
prod |
Environment name (used in ${APP_ENV:default}) |
PORT |
8001 |
Server port |
LOCAL_INGESTION_ENABLED |
false |
Enable local folder ingestion |
OPENAI_API_KEY |
(empty) | OpenAI API key |
AZ_OPENAI_API_KEY |
(empty) | Azure OpenAI API key |
AZ_OPENAI_ENDPOINT |
(empty) | Azure OpenAI endpoint URL |
AZ_OPENAI_EMBEDDING_DEPLOYMENT_NAME |
(empty) | Azure embedding deployment name |
AZ_OPENAI_LLM_DEPLOYMENT_NAME |
(empty) | Azure LLM deployment name |
GOOGLE_API_KEY |
(empty) | Google Gemini API key |
HF_TOKEN |
(empty) | HuggingFace access token |
PGPT_MODE |
mock |
LLM mode for Docker profile |
PGPT_EMBED_MODE |
mock |
Embedding mode for Docker profile |
PGPT_HF_REPO_ID |
lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF |
llama.cpp HuggingFace repo |
PGPT_HF_MODEL_FILE |
Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf |
llama.cpp model file |
PGPT_EMBEDDING_HF_MODEL_NAME |
nomic-ai/nomic-embed-text-v1.5 |
HuggingFace embedding model |
PGPT_OLLAMA_API_BASE |
http://ollama:11434 |
Ollama API base for Docker |
PGPT_OLLAMA_LLM_MODEL |
llama3.1 |
Ollama LLM model for Docker |
PGPT_OLLAMA_EMBEDDING_MODEL |
nomic-embed-text |
Ollama embedding model for Docker |
PGPT_OLLAMA_AUTOPULL_MODELS |
true |
Auto-pull Ollama models in Docker |
Environment Variable Substitution Syntax
The YAML loader (private_gpt/settings/yaml.py) supports ${VAR} and ${VAR:default}:
api_key: ${OPENAI_API_KEY} # Required — fails if not set
api_key: ${OPENAI_API_KEY:} # Optional — empty string default
model: ${MODEL:gpt-3.5-turbo} # Optional — with specific default
The substitution is implemented as a custom PyYAML implicit resolver that matches the pattern \$\{(\w|_)+:?.*\}.
Complete Settings Schema
All settings are defined as Pydantic BaseModel classes in private_gpt/settings/settings.py.
Server Settings (server)
| Key | Type | Default | Description |
|---|---|---|---|
server.env_name |
str |
— (required) | Environment name |
server.port |
int |
— (required) | Server port |
server.cors.enabled |
bool |
false |
Enable CORS headers |
server.cors.allow_credentials |
bool |
false |
Allow cookies in cross-origin requests |
server.cors.allow_origins |
list[str] |
[] |
Allowed origins |
server.cors.allow_origin_regex |
list[str] |
null |
Origin regex patterns |
server.cors.allow_methods |
list[str] |
["GET"] |
Allowed HTTP methods |
server.cors.allow_headers |
list[str] |
[] |
Allowed request headers |
server.auth.enabled |
bool |
false |
Enable API authentication |
server.auth.secret |
str |
— (required when enabled) | Full Authorization header value |
Warning
The default
settings.yamlships withserver.cors.allow_origins: ["*"]andserver.cors.allow_methods: ["*"]. Restrict these in production deployments.
Data Settings (data)
| Key | Type | Default | Description |
|---|---|---|---|
data.local_data_folder |
str |
— (required) | Path to local storage (absolute or relative to project root) |
data.local_ingestion.enabled |
bool |
false |
Enable local folder ingestion |
data.local_ingestion.allow_ingest_from |
list[str] |
[] |
Allowed folders for local ingestion |
LLM Settings (llm)
| Key | Type | Default | Description |
|---|---|---|---|
llm.mode |
str |
— (required) | One of: llamacpp, openai, openailike, azopenai, sagemaker, mock, ollama, gemini |
llm.max_new_tokens |
int |
256 |
Max tokens per completion |
llm.context_window |
int |
3900 |
Max context tokens |
llm.tokenizer |
str |
null |
HuggingFace tokenizer model ID |
llm.temperature |
float |
0.1 |
Generation temperature |
llm.prompt_style |
str |
llama2 |
One of: default, llama2, llama3, tag, mistral, chatml |
Embedding Settings (embedding)
| Key | Type | Default | Description |
|---|---|---|---|
embedding.mode |
str |
— (required) | One of: huggingface, openai, azopenai, sagemaker, ollama, mock, gemini, mistralai |
embedding.ingest_mode |
str |
simple |
One of: simple, batch, parallel, pipeline |
embedding.count_workers |
int |
2 |
Worker count for non-simple modes |
embedding.embed_dim |
int |
384 |
Embedding vector dimensions |
RAG Settings (rag)
| Key | Type | Default | Description |
|---|---|---|---|
rag.similarity_top_k |
int |
2 |
Number of chunks for RAG / reranker input |
rag.similarity_value |
float |
null |
Minimum similarity score threshold (0–1) |
rag.rerank.enabled |
bool |
false |
Enable cross-encoder reranking |
rag.rerank.model |
str |
cross-encoder/ms-marco-MiniLM-L-2-v2 |
Reranker model |
rag.rerank.top_n |
int |
2 |
Number of documents after reranking |
UI Settings (ui)
| Key | Type | Default | Description |
|---|---|---|---|
ui.enabled |
bool |
— (required) | Enable Gradio web UI |
ui.path |
str |
— (required) | Mount path for UI |
ui.default_mode |
str |
RAG |
One of: RAG, Search, Basic, Summarize |
ui.default_chat_system_prompt |
str |
null |
Default system prompt for Basic mode |
ui.default_query_system_prompt |
str |
null |
Default system prompt for RAG mode |
ui.default_summarization_system_prompt |
str |
null |
Default system prompt for Summarize mode |
ui.delete_file_button_enabled |
bool |
true |
Show delete-file button |
ui.delete_all_files_button_enabled |
bool |
false |
Show delete-all button |
Summarize Settings (summarize)
| Key | Type | Default | Description |
|---|---|---|---|
summarize.use_async |
bool |
true |
Async summarization |
Vector Store Settings (vectorstore)
| Key | Type | Default | Description |
|---|---|---|---|
vectorstore.database |
str |
— (required) | One of: chroma, qdrant, postgres, clickhouse, milvus |
Node Store Settings (nodestore)
| Key | Type | Default | Description |
|---|---|---|---|
nodestore.database |
str |
— (required) | One of: simple, postgres |
llama.cpp Settings (llamacpp)
| Key | Type | Default | Description |
|---|---|---|---|
llamacpp.llm_hf_repo_id |
str |
— (required) | HuggingFace repo ID for model |
llamacpp.llm_hf_model_file |
str |
— (required) | GGUF model filename |
llamacpp.tfs_z |
float |
1.0 |
Tail free sampling (1.0 = disabled) |
llamacpp.top_k |
int |
40 |
Top-K sampling |
llamacpp.top_p |
float |
0.9 |
Nucleus sampling |
llamacpp.repeat_penalty |
float |
1.1 |
Repetition penalty |
HuggingFace Settings (huggingface)
| Key | Type | Default | Description |
|---|---|---|---|
huggingface.embedding_hf_model_name |
str |
— (required) | HuggingFace embedding model name |
huggingface.access_token |
str |
null |
HuggingFace access token |
huggingface.trust_remote_code |
bool |
false |
Trust and execute remote model code |
Caution
Setting
trust_remote_code: trueallows arbitrary code execution from the model repository. The defaultsettings.yamlenables this fornomic-ai/nomic-embed-text-v1.5which requires it. Verify the model source before enabling.
Ollama Settings (ollama)
| Key | Type | Default | Description |
|---|---|---|---|
ollama.api_base |
str |
http://localhost:11434 |
Ollama API endpoint |
ollama.embedding_api_base |
str |
http://localhost:11434 |
Ollama embedding endpoint |
ollama.llm_model |
str |
null |
LLM model name |
ollama.embedding_model |
str |
null |
Embedding model name |
ollama.keep_alive |
str |
5m |
Time model stays loaded after request |
ollama.tfs_z |
float |
1.0 |
Tail free sampling |
ollama.num_predict |
int |
null |
Max tokens (-1 = infinite, -2 = fill context) |
ollama.top_k |
int |
40 |
Top-K sampling |
ollama.top_p |
float |
0.9 |
Nucleus sampling |
ollama.repeat_last_n |
int |
64 |
Repetition lookback window |
ollama.repeat_penalty |
float |
1.1 |
Repetition penalty |
ollama.request_timeout |
float |
120.0 |
Request timeout in seconds |
ollama.autopull_models |
bool |
false |
Auto-pull missing models |
OpenAI Settings (openai)
| Key | Type | Default | Description |
|---|---|---|---|
openai.api_base |
str |
null |
Custom API base URL |
openai.api_key |
str |
— (required) | API key |
openai.model |
str |
gpt-3.5-turbo |
LLM model |
openai.request_timeout |
float |
120.0 |
Request timeout |
openai.embedding_api_base |
str |
null |
Custom embedding API base |
openai.embedding_api_key |
str |
— (required) | Embedding API key |
openai.embedding_model |
str |
text-embedding-ada-002 |
Embedding model |
Azure OpenAI Settings (azopenai)
| Key | Type | Default | Description |
|---|---|---|---|
azopenai.api_key |
str |
— (required) | Azure API key |
azopenai.azure_endpoint |
str |
— (required) | Azure endpoint URL |
azopenai.api_version |
str |
2023_05_15 |
API version (YYYY-MM-DD format) |
azopenai.embedding_deployment_name |
str |
— (required) | Embedding deployment name |
azopenai.embedding_model |
str |
text-embedding-ada-002 |
Embedding model |
azopenai.llm_deployment_name |
str |
— (required) | LLM deployment name |
azopenai.llm_model |
str |
gpt-35-turbo |
LLM model |
Gemini Settings (gemini)
| Key | Type | Default | Description |
|---|---|---|---|
gemini.api_key |
str |
— (required) | Google API key |
gemini.model |
str |
models/gemini-pro |
LLM model |
gemini.embedding_model |
str |
models/embedding-001 |
Embedding model |
SageMaker Settings (sagemaker)
| Key | Type | Default | Description |
|---|---|---|---|
sagemaker.llm_endpoint_name |
str |
— (required) | SageMaker LLM endpoint |
sagemaker.embedding_endpoint_name |
str |
— (required) | SageMaker embedding endpoint |
Qdrant Settings (qdrant)
| Key | Type | Default | Description |
|---|---|---|---|
qdrant.location |
str |
null |
:memory: for in-memory, or URL |
qdrant.url |
str |
null |
Qdrant server URL |
qdrant.port |
int |
6333 |
REST API port |
qdrant.grpc_port |
int |
6334 |
gRPC port |
qdrant.prefer_grpc |
bool |
false |
Prefer gRPC over REST |
qdrant.https |
bool |
null |
Use HTTPS |
qdrant.api_key |
str |
null |
API key for Qdrant Cloud |
qdrant.prefix |
str |
null |
REST URL path prefix |
qdrant.timeout |
float |
null |
Request timeout |
qdrant.host |
str |
null |
Hostname (defaults to localhost) |
qdrant.path |
str |
null |
Local persistence path |
qdrant.force_disable_check_same_thread |
bool |
true |
Disable SQLite thread check for local Qdrant |
PostgreSQL Settings (postgres)
| Key | Type | Default | Description |
|---|---|---|---|
postgres.host |
str |
localhost |
Database host |
postgres.port |
int |
5432 |
Database port |
postgres.user |
str |
postgres |
Database user |
postgres.password |
str |
postgres |
Database password |
postgres.database |
str |
postgres |
Database name |
postgres.schema_name |
str |
public |
Schema name |
ClickHouse Settings (clickhouse)
| Key | Type | Default | Description |
|---|---|---|---|
clickhouse.host |
str |
localhost |
Server host |
clickhouse.port |
int |
8443 |
Server port |
clickhouse.username |
str |
default |
Username |
clickhouse.password |
str |
(empty) | Password |
clickhouse.database |
str |
__default__ |
Database name |
clickhouse.secure |
bool|str |
false |
Use HTTPS/TLS |
clickhouse.interface |
str |
null |
http or https |
clickhouse.settings |
dict |
null |
ClickHouse session settings |
clickhouse.connect_timeout |
int |
null |
Connection timeout |
clickhouse.send_receive_timeout |
int |
null |
Read timeout |
clickhouse.verify |
bool |
null |
Verify server certificate |
clickhouse.ca_cert |
str |
null |
CA root certificate path |
clickhouse.client_cert |
str |
null |
Client certificate path |
clickhouse.client_cert_key |
str |
null |
Client certificate key path |
clickhouse.http_proxy |
str |
null |
HTTP proxy |
clickhouse.https_proxy |
str |
null |
HTTPS proxy |
clickhouse.server_host_name |
str |
null |
TLS server hostname |
Milvus Settings (milvus)
| Key | Type | Default | Description |
|---|---|---|---|
milvus.uri |
str |
local_data/private_gpt/milvus/milvus_local.db |
Milvus URI |
milvus.token |
str |
(empty) | Access token |
milvus.collection_name |
str |
make_this_parameterizable_per_api_call |
Collection name |
milvus.overwrite |
bool |
true |
Overwrite existing collection schema |
Configuration Files
| File | Format | Location | Purpose |
|---|---|---|---|
settings.yaml |
YAML | Project root | Base/default configuration |
settings-{profile}.yaml |
YAML | Project root | Profile overrides |
pyproject.toml |
TOML | Project root | Poetry dependencies and extras |
.pre-commit-config.yaml |
YAML | Project root | Pre-commit hooks |
docker-compose.yaml |
YAML | Project root | Docker Compose stack |
Dockerfile.llamacpp-cpu |
Dockerfile | Project root | llama.cpp CPU Docker image |
Dockerfile.ollama |
Dockerfile | Project root | Ollama Docker image |
4. Deployment & Scaling
Deployment Options
Direct Python
PGPT_PROFILES=ollama python -m private_gpt
The entry point (private_gpt/__main__.py) starts uvicorn bound to 0.0.0.0 on the configured port with custom log configuration.
Note
Uvicorn's default logging is disabled (
log_config=None) in favor of PrivateGPT's own logging format:HH:MM:SS.mmm [LEVEL ] module_name - message.
Docker Compose
The docker-compose.yaml and Docker profile (settings-docker.yaml) configure:
- Port:
8080(via${PORT:8080}) - LLM/embedding modes: configurable via env vars (
PGPT_MODE,PGPT_EMBED_MODE) - Ollama auto-pull: enabled by default
docker compose up -d
Docker with Ollama
docker build -t private-gpt -f Dockerfile.ollama .
docker run -p 8001:8080 \
-e PGPT_OLLAMA_API_BASE=http://host.docker.internal:11434 \
private-gpt
Docker with llama.cpp (CPU)
docker build -t private-gpt-cpu -f Dockerfile.llamacpp-cpu .
docker run -p 8001:8080 private-gpt-cpu
Scaling Considerations
Tip
For production deployments handling multiple concurrent users, consider:
- Using a client-server vector store (Qdrant server, PostgreSQL) instead of local file-based storage
- Running the Ollama service on a separate host with GPU acceleration
- Setting
embedding.ingest_mode: parallelwith appropriatecount_workersfor bulk ingestion- Placing a reverse proxy (nginx, Traefik) in front of PrivateGPT for TLS termination
The FastAPI application is single-process by default. For horizontal scaling:
- Use an external vector store that supports concurrent connections
- Run multiple uvicorn workers or deploy behind a load balancer
- Ensure the Ollama/LLM backend can handle concurrent requests
5. Operations
Health Check
curl http://localhost:8001/health
{"status": "ok"}
Tip
Use the
/healthendpoint for container orchestration liveness probes. It returns HTTP 200 when the FastAPI application is running and accepting connections.
Monitoring
PrivateGPT uses Python's standard logging module at INFO level by default. The log format is:
HH:MM:SS.mmm [LEVEL ] module_name - message
Set ROOT_LOG_LEVEL = "DEBUG" in private_gpt/__init__.py for verbose output including library logs.
LlamaIndex observability is enabled via the simple global handler in launcher.py, which provides callback-based tracing of LLM calls, embedding operations, and retrieval steps.
Ollama Model Management
When ollama.autopull_models is true, PrivateGPT automatically pulls missing models at startup. The pull process:
- Checks installed models via
client.list() - If the configured model is not found, pulls with streaming progress bars
- Retries up to 5 times with jitter (3–10 seconds) on connection errors
Note
The Ollama connection check uses a retry decorator with 5 attempts and randomized jitter between 3.0 and 10.0 seconds to handle slow Ollama startups.
Local Data Management
Default local data paths (derived from data.local_data_folder):
local_data/private_gpt/— main data directorylocal_data/private_gpt/qdrant/— Qdrant local persistence (when using local Qdrant)local_data/private_gpt/milvus/— Milvus local persistencemodels/— downloaded model filesmodels/cache/— model cache
Backup and Recovery
For local Qdrant deployments, back up the local_data/ directory. For PostgreSQL deployments, use standard pg_dump. Vector store data and node store data must be backed up together to maintain consistency.
6. Performance
Ingestion Mode Comparison
| Mode | Behavior | Best For |
|---|---|---|
simple |
Sequential, one file at a time | Small datasets, debugging |
batch |
Parse in parallel, embed in batch | Mixed file sizes |
parallel |
Parse and embed in parallel across workers | Local setups with multi-core CPU |
pipeline |
Continuous embedding pipeline | High-throughput ingestion |
The count_workers setting (default: 2) controls parallelism for non-simple modes. Optimal values depend on CPU threads and available RAM.
Warning
Setting
count_workerstoo high inparallelmode can cause memory exhaustion, especially with large embedding models. Stay at or below your CPU thread count.
LLM Tuning Parameters
| Parameter | Effect | Default |
|---|---|---|
temperature |
Higher = more creative, lower = more factual | 0.1 |
max_new_tokens |
Maximum response length | 256 (base), 512 (ollama/local profiles) |
context_window |
Maximum input context | 3900 |
top_k |
Vocabulary diversity filter | 40 |
top_p |
Nucleus sampling threshold | 0.9 |
repeat_penalty |
Repetition avoidance strength | 1.1 |
tfs_z |
Tail free sampling (1.0 = disabled) | 1.0 |
RAG Tuning
rag.similarity_top_k: Number of chunks retrieved before reranking (default:2)rag.similarity_value: Minimum similarity score filter — disabled by default; set to0.45–0.7to filter low-relevance results- Reranking: Enable for better precision at the cost of additional compute (loads a cross-encoder model)
7. Security
Authentication
Authentication is implemented in private_gpt/server/utils/auth.py as a FastAPI dependency:
- When
server.auth.enabledisfalse(default): a dummy dependency always returnsTrue - When enabled: validates the
Authorizationheader againstserver.auth.secretusingsecrets.compare_digest()(constant-time comparison)
Failed authentication returns HTTP 401 with a WWW-Authenticate: Basic realm="All the API" header.
Caution
The default
settings.yamlcontains a hardcoded auth secret:Basic c2VjcmV0OmtleQ==(which decodes tosecret:key). This is a known security issue (#2204). Always change this value and enable authentication before any network exposure.
CORS Configuration
CORS is handled via FastAPI's CORSMiddleware, configured under server.cors. The default settings.yaml enables CORS with permissive settings:
cors:
enabled: true
allow_origins: ["*"]
allow_methods: ["*"]
allow_headers: ["*"]
Warning
Wildcard CORS origins allow any website to make API requests to your PrivateGPT instance. Restrict
allow_originsto your specific frontend domains in production.
Data Privacy
- No telemetry: Gradio analytics is explicitly disabled (
GRADIO_ANALYTICS_ENABLED=False) - No ChromaDB telemetry: Anonymized telemetry is disabled
- Offline tokenization: The
tiktoken_cache/directory is bundled for offline token counting - Local-only processing: When using local LLM and embedding backends, no data leaves the host
- Local ingestion control: The
data.local_ingestion.allow_ingest_fromsetting restricts which filesystem paths can be ingested
Security Recommendations
- [ ] Change the default auth secret before deployment
- [ ] Enable authentication (
server.auth.enabled: true) - [ ] Restrict CORS origins to known frontends
- [ ] Use TLS termination via a reverse proxy
- [ ] Restrict
local_ingestion.allow_ingest_fromto specific directories - [ ] Review
huggingface.trust_remote_code— disable if not required by your model - [ ] Consider integrity and malware checks for local embeddings (#2201)
8. Developer Internals
Repository Structure
private-gpt/
├── private_gpt/ # Main application package
│ ├── __init__.py # Logging setup, telemetry disabling
│ ├── __main__.py # Uvicorn entry point
│ ├── main.py # App singleton
│ ├── launcher.py # FastAPI app factory
│ ├── di.py # Dependency injection container
│ ├── constants.py # PROJECT_ROOT_PATH
│ ├── paths.py # Derived filesystem paths
│ ├── components/ # Pluggable backend components
│ │ ├── embedding/ # Embedding backends
│ │ │ └── custom/ # SageMaker custom embedding
│ │ ├── ingest/ # Document ingestion
│ │ ├── llm/ # LLM backends
│ │ │ ├── prompt_helper.py # Prompt formatting
│ │ │ └── custom/ # SageMaker custom LLM
│ │ ├── node_store/ # Node storage backends
│ │ └── vector_store/ # Vector storage backends
│ │ └── batched_chroma.py # Batched Chroma operations
│ ├── open_ai/ # OpenAI-compatible models
│ │ ├── openai_models.py # Request/response Pydantic models
│ │ └── extensions/
│ │ └── context_filter.py # Document ID filtering
│ ├── server/ # FastAPI routers and services
│ │ ├── chat/ # Chat completions
│ │ ├── chunks/ # Chunk retrieval
│ │ ├── completions/ # Text completions
│ │ ├── embeddings/ # Embedding generation
│ │ ├── health/ # Health check
│ │ ├── ingest/ # Document ingestion + watcher
│ │ ├── recipes/summarize/ # Summarization recipe
│ │ └── utils/auth.py # Authentication
│ ├── settings/ # Configuration system
│ │ ├── settings.py # Pydantic settings models
│ │ ├── settings_loader.py # Profile loading and merging
│ │ └── yaml.py # YAML loader with env var substitution
│ ├── ui/ # Gradio web interface
│ │ ├── ui.py # UI logic (4 modes, file management)
│ │ ├── images.py # Logo SVG data
│ │ └── avatar-bot.ico # Bot avatar
│ └── utils/ # Utilities
│ ├── eta.py # ETA calculator with Pearson regression
│ ├── ollama.py # Ollama connection, model pulling
│ ├── retry.py # Retry decorator wrapper
│ └── typing.py # TypeVar definitions
├── fern/ # API documentation (Fern)
│ ├── docs.yml # Fern docs configuration
│ ├── fern.config.json # Fern project config
│ ├── generators.yml # SDK generator config
│ ├── openapi/openapi.json # OpenAPI 3.1 spec
│ └── docs/ # Documentation pages
├── scripts/ # Utility scripts
│ ├── extract_openapi.py # Export OpenAPI spec
│ ├── ingest_folder.py # Bulk folder ingestion
│ └── setup # Setup script
├── tests/ # Test suite
│ ├── conftest.py # Shared fixtures
│ ├── fixtures/ # Test fixtures (mock injector, etc.)
│ ├── server/ # Router tests
│ ├── settings/ # Settings tests
│ └── ui/ # UI tests
├── settings.yaml # Default settings
├── settings-*.yaml # Profile settings
├── docker-compose.yaml # Docker stack
├── Dockerfile.llamacpp-cpu # llama.cpp CPU image
├── Dockerfile.ollama # Ollama image
├── Makefile # Build/test targets
└── pyproject.toml # Poetry project definition
Dependency Injection Architecture
PrivateGPT uses the injector library for dependency injection. The DI container is created in private_gpt/di.py:
def create_application_injector() -> Injector:
_injector = Injector(auto_bind=True)
_injector.binder.bind(Settings, to=unsafe_typed_settings)
return _injector
Key behaviors:
auto_bind=Trueallows automatic resolution of dependencies not explicitly boundSettingsis bound as a singleton to the loaded settings object- The global injector is created at module import time
- Each FastAPI request gets the injector via
request.state.injector - Service classes (
ChatService,IngestService, etc.) use@injectdecorators
Note
The
global_injectoris a module-level singleton. For testing, useMockInjectorto override bindings. The test fixtures intests/fixtures/mock_injector.pydemonstrate this pattern.
Application Startup Sequence
private_gpt/__init__.py— configures logging (INFOlevel), disables Gradio analytics and ChromaDB telemetry, sets tiktoken cache pathprivate_gpt/settings/settings_loader.py— loads active profiles, merges settingsprivate_gpt/settings/settings.py— validates merged settings against Pydantic modelsprivate_gpt/di.py— creates the globalInjectorand bindsSettingsprivate_gpt/launcher.py—create_app()creates the FastAPI app:- Registers
bind_injector_to_requestdependency - Includes all routers (completions, chat, chunks, ingest, summarize, embeddings, health)
- Sets up LlamaIndex
simpleobservability handler - Configures CORS middleware if enabled
- Mounts Gradio UI if
ui.enabled
- Registers
private_gpt/__main__.py— starts uvicorn on0.0.0.0:{port}
API Endpoints (Complete Reference)
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/v1/completions |
Yes | Text completion with optional context |
POST |
/v1/chat/completions |
Yes | Chat completion with optional context |
POST |
/v1/chunks |
Yes | Retrieve relevant document chunks |
POST |
/v1/ingest |
Yes | Ingest file (deprecated — use /v1/ingest/file) |
POST |
/v1/ingest/file |
Yes | Ingest and process a file |
POST |
/v1/ingest/text |
Yes | Ingest raw text |
GET |
/v1/ingest/list |
Yes | List all ingested documents |
DELETE |
/v1/ingest/{doc_id} |
Yes | Delete an ingested document |
POST |
/v1/summarize |
Yes | Summarize text or documents |
POST |
/v1/embeddings |
Yes | Generate embeddings for input text |
GET |
/health |
No | System health check |
"Auth: Yes" means the endpoint respects the authenticated dependency — when server.auth.enabled is false, authentication is effectively a no-op.
OpenAI Compatibility Layer
The private_gpt/open_ai/openai_models.py module defines Pydantic models that mirror the OpenAI API response format:
| Class | Purpose |
|---|---|
OpenAIDelta |
Streaming chunk content |
OpenAIMessage |
Message with role (assistant/system/user) and content |
OpenAIChoice |
Response choice (either delta for streaming or message for non-streaming) |
OpenAICompletion |
Top-level response with id, object, created, model, choices |
Helper functions:
to_openai_response()— converts a LlamaIndex response toOpenAICompletionto_openai_sse_stream()— converts a LlamaIndex streaming generator to SSE format withdata:prefixed JSON chunks, terminated bydata: [DONE]
Context Filtering
The ContextFilter model (private_gpt/open_ai/extensions/context_filter.py) allows filtering RAG queries to specific documents by passing their doc_ids. This is used by both the API and the Gradio UI when a specific file is selected.
Gradio UI Internals
PrivateGptUi (private_gpt/ui/ui.py) is a @singleton class that:
- Receives services via
@inject:IngestService,ChatService,ChunksService,SummarizeService - Builds a Gradio
Blocksinterface with a mode radio, file management sidebar, system prompt input, and chat interface - Dispatches to different service methods based on the selected
Modesenum (RAG,Search,Basic,Summarize) - Limits chat history to 20 messages to prevent context overflow
- Strips source separators from history to keep context clean
- Mounts into FastAPI via
gr.mount_gradio_app()
The UI tab title is hardcoded as "My Private GPT".
ETA Calculator
private_gpt/utils/eta.py provides an ETA class that predicts completion time using Pearson product-moment correlation coefficient on timing data. It maintains a sliding window of 100 samples and reports progress in human-readable format (e.g., "2m 30s @ 15/min").
Retry Mechanism
private_gpt/utils/retry.py wraps the retry-async library's retry decorator with typed parameters. Used for Ollama connection retries with configurable:
tries: max attempts (5 for Ollama)jitter: random delay range (3.0–10.0 seconds for Ollama)exceptions: exception types to catchbackoff: multiplicative backoff factor
Module Reference
private_gpt/settings/settings.py
The central settings module defines 27 Pydantic BaseModel classes representing every configurable aspect of the system. The top-level Settings model aggregates all sub-models. Settings are loaded once at import time via unsafe_typed_settings and made available through DI or the settings() function.
private_gpt/launcher.py
The create_app() factory function is the single place where all routers are registered, middleware is configured, and the UI is mounted. It takes an Injector and returns a fully configured FastAPI application.
private_gpt/ui/ui.py
At 598 lines, this is the largest module. It implements the complete Gradio interface including file upload/delete, 4 chat modes, streaming response rendering with source attribution, system prompt management, and model label display.
private_gpt/open_ai/openai_models.py
Implements the OpenAI-compatible response format. The OpenAICompletion class provides from_text() for single responses and json_from_delta() for streaming chunks.
private_gpt/server/utils/auth.py
Implements a switchable authentication pattern: at module load time, it checks settings().server.auth.enabled and defines either a no-op authenticated() function or one that validates the Authorization header via secrets.compare_digest().
Code Patterns and Conventions
- Dependency injection everywhere: Services are
@inject-decorated, DI container resolves all dependencies - Pydantic for all data: Settings, API request/response bodies, and internal models use Pydantic
BaseModel - Profile-based configuration: No hardcoded values — everything flows through the YAML profile system
- Module-level initialization: Settings are loaded and validated at import time, before the server starts
- Match statements: Python 3.10+
match/caseused for mode dispatching in the UI - Type hints throughout: Full type annotations with
mypychecking - Pre-commit hooks: Black (formatting), Ruff (linting), mypy (types), pytest (tests on push)
Testing
Tests use pytest with custom fixtures:
| Fixture | Purpose |
|---|---|
test_client |
FastAPI TestClient (from tests/fixtures/fast_api_test_client.py) |
MockInjector |
DI container with overridable bindings |
auto_close_qdrant |
Qdrant cleanup fixture |
ingest_helper |
Test file ingestion |
The test profile (settings-test.yaml) configures:
- Auth disabled with dummy secret
"foo bar; dummy secret" - Mock LLM and embedding modes
- Local data stored in
local_data/tests - UI disabled
Test coverage includes:
- Authentication (default no-auth and enabled basic auth)
- Settings loading, merging, and env var substitution
- Summarize routes (streaming, single, with/without context)
- UI endpoint mounting
Potential Improvements
- Per-user document isolation (#1796) — currently all ingested documents are global
- Integrity and malware checks for local embeddings (#2201)
- Additional LLM backend support (Claude requested in #2199)
- OceanBase vector storage support (#2214)
- Session-based ingestion with stop-and-start capability (#2190, #1914)
- Bulk ingestion from Dockerfile with
--watchsupport (#1914)
Contributing
The project uses:
- Poetry for dependency management
- Black for code formatting
- Ruff for linting
- mypy for type checking
- pytest for testing
- pre-commit for automated hooks (format on commit, test on push)
Setup the development environment:
git clone https://github.com/zylon-ai/private-gpt
cd private-gpt
poetry install --all-extras
pre-commit install
Run checks:
make test # Run pytest
make mypy # Run type checking
The API documentation is generated with Fern from the OpenAPI spec at fern/openapi/openapi.json. The Python SDK is generated via fernapi/fern-python-sdk (version 0.6.2).
9. Integration Points
OpenAI SDK Compatibility
PrivateGPT's API is compatible with OpenAI's client libraries. Point the OpenAI SDK at your PrivateGPT instance:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8001/v1", api_key="not-needed")
response = client.chat.completions.create(
model="private-gpt",
messages=[{"role": "user", "content": "Hello"}]
)
Fern SDK
An official Python SDK is generated from the OpenAPI spec using Fern. The SDK output path is ../../pgpt-sdk/python relative to the fern/ directory. The Fern documentation is hosted at docs.privategpt.dev.
Ingestion Watcher
The private_gpt/server/ingest/ingest_watcher.py module provides filesystem watching capability for automatic ingestion of new files placed in monitored directories. This requires data.local_ingestion.enabled: true.
LlamaIndex Ecosystem
PrivateGPT builds on LlamaIndex for:
- Document parsing and chunking
- Embedding model abstraction
- Vector store abstraction
- Query engine and chat engine
- Callback-based observability
All LlamaIndex components are accessed through PrivateGPT's component wrappers in private_gpt/components/, which handle configuration injection and backend selection.