DeepCritical / docs /configuration /CONFIGURATION.md
Joseph Pollack
restore docs ci
b4ff56e
|
raw
history blame
22.4 kB
# Configuration Guide
## Overview
DeepCritical uses **Pydantic Settings** for centralized configuration management. All settings are defined in the `Settings` class in `src/utils/config.py` and can be configured via environment variables or a `.env` file.
The configuration system provides:
- **Type Safety**: Strongly-typed fields with Pydantic validation
- **Environment File Support**: Automatically loads from `.env` file (if present)
- **Case-Insensitive**: Environment variables are case-insensitive
- **Singleton Pattern**: Global `settings` instance for easy access throughout the codebase
- **Validation**: Automatic validation on load with helpful error messages
## Quick Start
1. Create a `.env` file in the project root
2. Set at least one LLM API key (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or `HF_TOKEN`)
3. Optionally configure other services as needed
4. The application will automatically load and validate your configuration
## Configuration System Architecture
### Settings Class
The `Settings` class extends `BaseSettings` from `pydantic_settings` and defines all application configuration:
```13:21:src/utils/config.py
class Settings(BaseSettings):
"""Strongly-typed application settings."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
```
### Singleton Instance
A global `settings` instance is available for import:
```234:235:src/utils/config.py
# Singleton for easy import
settings = get_settings()
```
### Usage Pattern
Access configuration throughout the codebase:
```python
from src.utils.config import settings
# Check if API keys are available
if settings.has_openai_key:
# Use OpenAI
pass
# Access configuration values
max_iterations = settings.max_iterations
web_search_provider = settings.web_search_provider
```
## Required Configuration
### LLM Provider
You must configure at least one LLM provider. The system supports:
- **OpenAI**: Requires `OPENAI_API_KEY`
- **Anthropic**: Requires `ANTHROPIC_API_KEY`
- **HuggingFace**: Optional `HF_TOKEN` or `HUGGINGFACE_API_KEY` (can work without key for public models)
#### OpenAI Configuration
```bash
LLM_PROVIDER=openai
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-5.1
```
The default model is defined in the `Settings` class:
```29:29:src/utils/config.py
openai_model: str = Field(default="gpt-5.1", description="OpenAI model name")
```
#### Anthropic Configuration
```bash
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=your_anthropic_api_key_here
ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
```
The default model is defined in the `Settings` class:
```30:32:src/utils/config.py
anthropic_model: str = Field(
default="claude-sonnet-4-5-20250929", description="Anthropic model"
)
```
#### HuggingFace Configuration
HuggingFace can work without an API key for public models, but an API key provides higher rate limits:
```bash
# Option 1: Using HF_TOKEN (preferred)
HF_TOKEN=your_huggingface_token_here
# Option 2: Using HUGGINGFACE_API_KEY (alternative)
HUGGINGFACE_API_KEY=your_huggingface_api_key_here
# Default model
HUGGINGFACE_MODEL=meta-llama/Llama-3.1-8B-Instruct
```
The HuggingFace token can be set via either environment variable:
```33:35:src/utils/config.py
hf_token: str | None = Field(
default=None, alias="HF_TOKEN", description="HuggingFace API token"
)
```
```57:59:src/utils/config.py
huggingface_api_key: str | None = Field(
default=None, description="HuggingFace API token (HF_TOKEN or HUGGINGFACE_API_KEY)"
)
```
## Optional Configuration
### Embedding Configuration
DeepCritical supports multiple embedding providers for semantic search and RAG:
```bash
# Embedding Provider: "openai", "local", or "huggingface"
EMBEDDING_PROVIDER=local
# OpenAI Embedding Model (used by LlamaIndex RAG)
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
# Local Embedding Model (sentence-transformers, used by EmbeddingService)
LOCAL_EMBEDDING_MODEL=all-MiniLM-L6-v2
# HuggingFace Embedding Model
HUGGINGFACE_EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
```
The embedding provider configuration:
```47:50:src/utils/config.py
embedding_provider: Literal["openai", "local", "huggingface"] = Field(
default="local",
description="Embedding provider to use",
)
```
**Note**: OpenAI embeddings require `OPENAI_API_KEY`. The local provider (default) uses sentence-transformers and requires no API key.
### Web Search Configuration
DeepCritical supports multiple web search providers:
```bash
# Web Search Provider: "serper", "searchxng", "brave", "tavily", or "duckduckgo"
# Default: "duckduckgo" (no API key required)
WEB_SEARCH_PROVIDER=duckduckgo
# Serper API Key (for Google search via Serper)
SERPER_API_KEY=your_serper_api_key_here
# SearchXNG Host URL (for self-hosted search)
SEARCHXNG_HOST=http://localhost:8080
# Brave Search API Key
BRAVE_API_KEY=your_brave_api_key_here
# Tavily API Key
TAVILY_API_KEY=your_tavily_api_key_here
```
The web search provider configuration:
```71:74:src/utils/config.py
web_search_provider: Literal["serper", "searchxng", "brave", "tavily", "duckduckgo"] = Field(
default="duckduckgo",
description="Web search provider to use",
)
```
**Note**: DuckDuckGo is the default and requires no API key, making it ideal for development and testing.
### PubMed Configuration
PubMed search supports optional NCBI API key for higher rate limits:
```bash
# NCBI API Key (optional, for higher rate limits: 10 req/sec vs 3 req/sec)
NCBI_API_KEY=your_ncbi_api_key_here
```
The PubMed tool uses this configuration:
```22:29:src/tools/pubmed.py
def __init__(self, api_key: str | None = None) -> None:
self.api_key = api_key or settings.ncbi_api_key
# Ignore placeholder values from .env.example
if self.api_key == "your-ncbi-key-here":
self.api_key = None
# Use shared rate limiter
self._limiter = get_pubmed_limiter(self.api_key)
```
### Agent Configuration
Control agent behavior and research loop execution:
```bash
# Maximum iterations per research loop (1-50, default: 10)
MAX_ITERATIONS=10
# Search timeout in seconds
SEARCH_TIMEOUT=30
# Use graph-based execution for research flows
USE_GRAPH_EXECUTION=false
```
The agent configuration fields:
```80:85:src/utils/config.py
# Agent Configuration
max_iterations: int = Field(default=10, ge=1, le=50)
search_timeout: int = Field(default=30, description="Seconds to wait for search")
use_graph_execution: bool = Field(
default=False, description="Use graph-based execution for research flows"
)
```
### Budget & Rate Limiting Configuration
Control resource limits for research loops:
```bash
# Default token budget per research loop (1000-1000000, default: 100000)
DEFAULT_TOKEN_LIMIT=100000
# Default time limit per research loop in minutes (1-120, default: 10)
DEFAULT_TIME_LIMIT_MINUTES=10
# Default iterations limit per research loop (1-50, default: 10)
DEFAULT_ITERATIONS_LIMIT=10
```
The budget configuration with validation:
```87:105:src/utils/config.py
# Budget & Rate Limiting Configuration
default_token_limit: int = Field(
default=100000,
ge=1000,
le=1000000,
description="Default token budget per research loop",
)
default_time_limit_minutes: int = Field(
default=10,
ge=1,
le=120,
description="Default time limit per research loop (minutes)",
)
default_iterations_limit: int = Field(
default=10,
ge=1,
le=50,
description="Default iterations limit per research loop",
)
```
### RAG Service Configuration
Configure the Retrieval-Augmented Generation service:
```bash
# ChromaDB collection name for RAG
RAG_COLLECTION_NAME=deepcritical_evidence
# Number of top results to retrieve from RAG (1-50, default: 5)
RAG_SIMILARITY_TOP_K=5
# Automatically ingest evidence into RAG
RAG_AUTO_INGEST=true
```
The RAG configuration:
```127:141:src/utils/config.py
# RAG Service Configuration
rag_collection_name: str = Field(
default="deepcritical_evidence",
description="ChromaDB collection name for RAG",
)
rag_similarity_top_k: int = Field(
default=5,
ge=1,
le=50,
description="Number of top results to retrieve from RAG",
)
rag_auto_ingest: bool = Field(
default=True,
description="Automatically ingest evidence into RAG",
)
```
### ChromaDB Configuration
Configure the vector database for embeddings and RAG:
```bash
# ChromaDB storage path
CHROMA_DB_PATH=./chroma_db
# Whether to persist ChromaDB to disk
CHROMA_DB_PERSIST=true
# ChromaDB server host (for remote ChromaDB, optional)
CHROMA_DB_HOST=localhost
# ChromaDB server port (for remote ChromaDB, optional)
CHROMA_DB_PORT=8000
```
The ChromaDB configuration:
```113:125:src/utils/config.py
chroma_db_path: str = Field(default="./chroma_db", description="ChromaDB storage path")
chroma_db_persist: bool = Field(
default=True,
description="Whether to persist ChromaDB to disk",
)
chroma_db_host: str | None = Field(
default=None,
description="ChromaDB server host (for remote ChromaDB)",
)
chroma_db_port: int | None = Field(
default=None,
description="ChromaDB server port (for remote ChromaDB)",
)
```
### External Services
#### Modal Configuration
Modal is used for secure sandbox execution of statistical analysis:
```bash
# Modal Token ID (for Modal sandbox execution)
MODAL_TOKEN_ID=your_modal_token_id_here
# Modal Token Secret
MODAL_TOKEN_SECRET=your_modal_token_secret_here
```
The Modal configuration:
```110:112:src/utils/config.py
# External Services
modal_token_id: str | None = Field(default=None, description="Modal token ID")
modal_token_secret: str | None = Field(default=None, description="Modal token secret")
```
### Logging Configuration
Configure structured logging:
```bash
# Log Level: "DEBUG", "INFO", "WARNING", or "ERROR"
LOG_LEVEL=INFO
```
The logging configuration:
```107:108:src/utils/config.py
# Logging
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
```
Logging is configured via the `configure_logging()` function:
```212:231:src/utils/config.py
def configure_logging(settings: Settings) -> None:
"""Configure structured logging with the configured log level."""
# Set stdlib logging level from settings
logging.basicConfig(
level=getattr(logging, settings.log_level),
format="%(message)s",
)
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.stdlib.BoundLogger,
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
)
```
## Configuration Properties
The `Settings` class provides helpful properties for checking configuration state:
### API Key Availability
Check which API keys are available:
```171:189:src/utils/config.py
@property
def has_openai_key(self) -> bool:
"""Check if OpenAI API key is available."""
return bool(self.openai_api_key)
@property
def has_anthropic_key(self) -> bool:
"""Check if Anthropic API key is available."""
return bool(self.anthropic_api_key)
@property
def has_huggingface_key(self) -> bool:
"""Check if HuggingFace API key is available."""
return bool(self.huggingface_api_key or self.hf_token)
@property
def has_any_llm_key(self) -> bool:
"""Check if any LLM API key is available."""
return self.has_openai_key or self.has_anthropic_key or self.has_huggingface_key
```
**Usage:**
```python
from src.utils.config import settings
# Check API key availability
if settings.has_openai_key:
# Use OpenAI
pass
if settings.has_anthropic_key:
# Use Anthropic
pass
if settings.has_huggingface_key:
# Use HuggingFace
pass
if settings.has_any_llm_key:
# At least one LLM is available
pass
```
### Service Availability
Check if external services are configured:
```143:146:src/utils/config.py
@property
def modal_available(self) -> bool:
"""Check if Modal credentials are configured."""
return bool(self.modal_token_id and self.modal_token_secret)
```
```191:204:src/utils/config.py
@property
def web_search_available(self) -> bool:
"""Check if web search is available (either no-key provider or API key present)."""
if self.web_search_provider == "duckduckgo":
return True # No API key required
if self.web_search_provider == "serper":
return bool(self.serper_api_key)
if self.web_search_provider == "searchxng":
return bool(self.searchxng_host)
if self.web_search_provider == "brave":
return bool(self.brave_api_key)
if self.web_search_provider == "tavily":
return bool(self.tavily_api_key)
return False
```
**Usage:**
```python
from src.utils.config import settings
# Check service availability
if settings.modal_available:
# Use Modal sandbox
pass
if settings.web_search_available:
# Web search is configured
pass
```
### API Key Retrieval
Get the API key for the configured provider:
```148:160:src/utils/config.py
def get_api_key(self) -> str:
"""Get the API key for the configured provider."""
if self.llm_provider == "openai":
if not self.openai_api_key:
raise ConfigurationError("OPENAI_API_KEY not set")
return self.openai_api_key
if self.llm_provider == "anthropic":
if not self.anthropic_api_key:
raise ConfigurationError("ANTHROPIC_API_KEY not set")
return self.anthropic_api_key
raise ConfigurationError(f"Unknown LLM provider: {self.llm_provider}")
```
For OpenAI-specific operations (e.g., Magentic mode):
```162:169:src/utils/config.py
def get_openai_api_key(self) -> str:
"""Get OpenAI API key (required for Magentic function calling)."""
if not self.openai_api_key:
raise ConfigurationError(
"OPENAI_API_KEY not set. Magentic mode requires OpenAI for function calling. "
"Use mode='simple' for other providers."
)
return self.openai_api_key
```
## Configuration Usage in Codebase
The configuration system is used throughout the codebase:
### LLM Factory
The LLM factory uses settings to create appropriate models:
```129:144:src/utils/llm_factory.py
if settings.llm_provider == "huggingface":
model_name = settings.huggingface_model or "meta-llama/Llama-3.1-8B-Instruct"
hf_provider = HuggingFaceProvider(api_key=settings.hf_token)
return HuggingFaceModel(model_name, provider=hf_provider)
if settings.llm_provider == "openai":
if not settings.openai_api_key:
raise ConfigurationError("OPENAI_API_KEY not set for pydantic-ai")
provider = OpenAIProvider(api_key=settings.openai_api_key)
return OpenAIModel(settings.openai_model, provider=provider)
if settings.llm_provider == "anthropic":
if not settings.anthropic_api_key:
raise ConfigurationError("ANTHROPIC_API_KEY not set for pydantic-ai")
anthropic_provider = AnthropicProvider(api_key=settings.anthropic_api_key)
return AnthropicModel(settings.anthropic_model, provider=anthropic_provider)
```
### Embedding Service
The embedding service uses local embedding model configuration:
```29:31:src/services/embeddings.py
def __init__(self, model_name: str | None = None):
self._model_name = model_name or settings.local_embedding_model
self._model = SentenceTransformer(self._model_name)
```
### Orchestrator Factory
The orchestrator factory uses settings to determine mode:
```69:80:src/orchestrator_factory.py
def _determine_mode(explicit_mode: str | None) -> str:
"""Determine which mode to use."""
if explicit_mode:
if explicit_mode in ("magentic", "advanced"):
return "advanced"
return "simple"
# Auto-detect: advanced if paid API key available
if settings.has_openai_key:
return "advanced"
return "simple"
```
## Environment Variables Reference
### Required (at least one LLM)
- `OPENAI_API_KEY` - OpenAI API key (required for OpenAI provider)
- `ANTHROPIC_API_KEY` - Anthropic API key (required for Anthropic provider)
- `HF_TOKEN` or `HUGGINGFACE_API_KEY` - HuggingFace API token (optional, can work without for public models)
#### LLM Configuration Variables
- `LLM_PROVIDER` - Provider to use: `"openai"`, `"anthropic"`, or `"huggingface"` (default: `"huggingface"`)
- `OPENAI_MODEL` - OpenAI model name (default: `"gpt-5.1"`)
- `ANTHROPIC_MODEL` - Anthropic model name (default: `"claude-sonnet-4-5-20250929"`)
- `HUGGINGFACE_MODEL` - HuggingFace model ID (default: `"meta-llama/Llama-3.1-8B-Instruct"`)
#### Embedding Configuration Variables
- `EMBEDDING_PROVIDER` - Provider: `"openai"`, `"local"`, or `"huggingface"` (default: `"local"`)
- `OPENAI_EMBEDDING_MODEL` - OpenAI embedding model (default: `"text-embedding-3-small"`)
- `LOCAL_EMBEDDING_MODEL` - Local sentence-transformers model (default: `"all-MiniLM-L6-v2"`)
- `HUGGINGFACE_EMBEDDING_MODEL` - HuggingFace embedding model (default: `"sentence-transformers/all-MiniLM-L6-v2"`)
#### Web Search Configuration Variables
- `WEB_SEARCH_PROVIDER` - Provider: `"serper"`, `"searchxng"`, `"brave"`, `"tavily"`, or `"duckduckgo"` (default: `"duckduckgo"`)
- `SERPER_API_KEY` - Serper API key (required for Serper provider)
- `SEARCHXNG_HOST` - SearchXNG host URL (required for SearchXNG provider)
- `BRAVE_API_KEY` - Brave Search API key (required for Brave provider)
- `TAVILY_API_KEY` - Tavily API key (required for Tavily provider)
#### PubMed Configuration Variables
- `NCBI_API_KEY` - NCBI API key (optional, increases rate limit from 3 to 10 req/sec)
#### Agent Configuration Variables
- `MAX_ITERATIONS` - Maximum iterations per research loop (1-50, default: `10`)
- `SEARCH_TIMEOUT` - Search timeout in seconds (default: `30`)
- `USE_GRAPH_EXECUTION` - Use graph-based execution (default: `false`)
#### Budget Configuration Variables
- `DEFAULT_TOKEN_LIMIT` - Default token budget per research loop (1000-1000000, default: `100000`)
- `DEFAULT_TIME_LIMIT_MINUTES` - Default time limit in minutes (1-120, default: `10`)
- `DEFAULT_ITERATIONS_LIMIT` - Default iterations limit (1-50, default: `10`)
#### RAG Configuration Variables
- `RAG_COLLECTION_NAME` - ChromaDB collection name (default: `"deepcritical_evidence"`)
- `RAG_SIMILARITY_TOP_K` - Number of top results to retrieve (1-50, default: `5`)
- `RAG_AUTO_INGEST` - Automatically ingest evidence into RAG (default: `true`)
#### ChromaDB Configuration Variables
- `CHROMA_DB_PATH` - ChromaDB storage path (default: `"./chroma_db"`)
- `CHROMA_DB_PERSIST` - Whether to persist ChromaDB to disk (default: `true`)
- `CHROMA_DB_HOST` - ChromaDB server host (optional, for remote ChromaDB)
- `CHROMA_DB_PORT` - ChromaDB server port (optional, for remote ChromaDB)
#### External Services Variables
- `MODAL_TOKEN_ID` - Modal token ID (optional, for Modal sandbox execution)
- `MODAL_TOKEN_SECRET` - Modal token secret (optional, for Modal sandbox execution)
#### Logging Configuration Variables
- `LOG_LEVEL` - Log level: `"DEBUG"`, `"INFO"`, `"WARNING"`, or `"ERROR"` (default: `"INFO"`)
## Validation
Settings are validated on load using Pydantic validation:
- **Type Checking**: All fields are strongly typed
- **Range Validation**: Numeric fields have min/max constraints (e.g., `ge=1, le=50` for `max_iterations`)
- **Literal Validation**: Enum fields only accept specific values (e.g., `Literal["openai", "anthropic", "huggingface"]`)
- **Required Fields**: API keys are checked when accessed via `get_api_key()` or `get_openai_api_key()`
### Validation Examples
The `max_iterations` field has range validation:
```81:81:src/utils/config.py
max_iterations: int = Field(default=10, ge=1, le=50)
```
The `llm_provider` field has literal validation:
```26:28:src/utils/config.py
llm_provider: Literal["openai", "anthropic", "huggingface"] = Field(
default="openai", description="Which LLM provider to use"
)
```
## Error Handling
Configuration errors raise `ConfigurationError` from `src/utils/exceptions.py`:
```22:25:src/utils/exceptions.py
class ConfigurationError(DeepCriticalError):
"""Raised when configuration is invalid."""
pass
```
### Error Handling Example
```python
from src.utils.config import settings
from src.utils.exceptions import ConfigurationError
try:
api_key = settings.get_api_key()
except ConfigurationError as e:
print(f"Configuration error: {e}")
```
### Common Configuration Errors
1. **Missing API Key**: When `get_api_key()` is called but the required API key is not set
2. **Invalid Provider**: When `llm_provider` is set to an unsupported value
3. **Out of Range**: When numeric values exceed their min/max constraints
4. **Invalid Literal**: When enum fields receive unsupported values
## Configuration Best Practices
1. **Use `.env` File**: Store sensitive keys in `.env` file (add to `.gitignore`)
2. **Check Availability**: Use properties like `has_openai_key` before accessing API keys
3. **Handle Errors**: Always catch `ConfigurationError` when calling `get_api_key()`
4. **Validate Early**: Configuration is validated on import, so errors surface immediately
5. **Use Defaults**: Leverage sensible defaults for optional configuration
## Future Enhancements
The following configurations are planned for future phases:
1. **Additional LLM Providers**: DeepSeek, OpenRouter, Gemini, Perplexity, Azure OpenAI, Local models
2. **Model Selection**: Reasoning/main/fast model configuration
3. **Service Integration**: Additional service integrations and configurations