Joseph Pollack
restore docs ci
a46bf8b unverified

A newer version of the Gradio SDK is available: 6.1.0

Upgrade

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][settings-class] class extends BaseSettings from pydantic_settings and defines all application configuration:

class Settings(BaseSettings):
    """Strongly-typed application settings."""

    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,
        extra="ignore",
    )

View source

Singleton Instance

A global settings instance is available for import:

# Singleton for easy import
settings = get_settings()

View source

Usage Pattern

Access configuration throughout the codebase:

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

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:

    openai_model: str = Field(default="gpt-5.1", description="OpenAI model name")

Anthropic Configuration

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:

    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:

# 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:

    hf_token: str | None = Field(
        default=None, alias="HF_TOKEN", description="HuggingFace API token"
    )
    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:

# 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:

    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:

# 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:

    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:

# 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:

    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:

# 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:

    # 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:

# 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:

    # 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:

# 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:

    # 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:

# 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:

    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:

# 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:

    # 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:

# Log Level: "DEBUG", "INFO", "WARNING", or "ERROR"
LOG_LEVEL=INFO

The logging configuration:

    # Logging
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"

Logging is configured via the configure_logging() function:

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:

    @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:

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:

    @property
    def modal_available(self) -> bool:
        """Check if Modal credentials are configured."""
        return bool(self.modal_token_id and self.modal_token_secret)
    @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:

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:

    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):

    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:

    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:

    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:

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:

    max_iterations: int = Field(default=10, ge=1, le=50)

The llm_provider field has literal validation:

    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:

class ConfigurationError(DeepCriticalError):
    """Raised when configuration is invalid."""

    pass

Error Handling Example

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