mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-05 06:13:15 +00:00
An agent node can list input_documents (short refs like A1, a literal "*" for all inputs, or state variables holding refs produced by an upstream node). The engine resolves each through the run-scoped artifact gate and feeds it to the node's LLM natively when the model supports the format (PDFs included via the provider's image synthesis), otherwise extracts it to text with Docling. A file_passing policy (auto/native/extract) and per-node count + per-file byte caps bound cost; the native decision uses the same supported-types source the provider handler uses, so a document is never silently dropped. Bytes never enter run state.
419 lines
22 KiB
Python
419 lines
22 KiB
Python
import os
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from pydantic import field_validator
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
|
|
from application.core.db_uri import ( # noqa: E402
|
|
normalize_pgvector_connection_string,
|
|
normalize_postgres_uri,
|
|
)
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(extra="ignore")
|
|
|
|
AUTH_TYPE: Optional[str] = None # simple_jwt, session_jwt, oidc, or None
|
|
|
|
# OIDC SSO (AUTH_TYPE=oidc) — any OpenID Connect IdP with discovery (Authentik, Keycloak, ...)
|
|
OIDC_ISSUER: Optional[str] = None # e.g. https://auth.example.com/application/o/docsgpt/
|
|
OIDC_CLIENT_ID: Optional[str] = None
|
|
OIDC_CLIENT_SECRET: Optional[str] = None # optional; PKCE is always used
|
|
OIDC_SCOPES: str = "openid profile email"
|
|
OIDC_USER_ID_CLAIM: str = "sub" # ID-token claim mapped to the DocsGPT user id
|
|
OIDC_FRONTEND_URL: Optional[str] = None # browser-facing app origin, e.g. http://localhost:5173
|
|
OIDC_REDIRECT_URI: Optional[str] = None # override; default <request host>/api/auth/oidc/callback
|
|
OIDC_SESSION_LIFETIME_SECONDS: int = 28800 # minted session JWT lifetime (8h)
|
|
OIDC_PROVIDER_NAME: Optional[str] = None # sign-in button label, e.g. "Acme SSO"
|
|
OIDC_ALLOWED_GROUPS: Optional[str] = None # comma-separated allowlist; unset = any authenticated user
|
|
OIDC_GROUPS_CLAIM: str = "groups" # ID-token/userinfo claim carrying group membership
|
|
OIDC_ADMIN_GROUPS: Optional[str] = None # comma-separated groups granted admin; unset = no OIDC admin mapping
|
|
|
|
# RBAC (admin/user roles). Persisted admin grants live in the user_roles
|
|
# table and apply only under AUTH_TYPE=oidc. LOCAL_MODE_ADMIN is the only
|
|
# non-DB admin path and applies only to AUTH_TYPE=None (no-auth self-host).
|
|
# It MUST stay False in any networked deployment.
|
|
LOCAL_MODE_ADMIN: bool = False
|
|
|
|
# SCIM 2.0 provisioning (IdP-driven user create/deactivate at /scim/v2)
|
|
SCIM_ENABLED: bool = False
|
|
SCIM_TOKEN: Optional[str] = None # bearer token for IdP SCIM clients (required when enabled)
|
|
|
|
LLM_PROVIDER: str = "docsgpt"
|
|
LLM_NAME: Optional[str] = None # if LLM_PROVIDER is openai, LLM_NAME can be gpt-4 or gpt-3.5-turbo
|
|
EMBEDDINGS_NAME: str = "huggingface_sentence-transformers/all-mpnet-base-v2"
|
|
EMBEDDINGS_BASE_URL: Optional[str] = None # Remote embeddings API URL (OpenAI-compatible)
|
|
EMBEDDINGS_KEY: Optional[str] = None # api key for embeddings (if using openai, just copy API_KEY)
|
|
EMBEDDINGS_MAX_INPUT_TOKENS: Optional[int] = None # truncate each remote embed input to N tokens (overflow lost)
|
|
# Optional directory of operator-supplied model YAMLs, loaded after the
|
|
# built-in catalog under application/core/models/. Later wins on
|
|
# duplicate model id. See application/core/models/README.md.
|
|
MODELS_CONFIG_DIR: Optional[str] = None
|
|
|
|
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
|
|
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/1"
|
|
# Prefetch=1 caps SIGKILL loss to one task. Visibility timeout must exceed
|
|
# the longest legitimate task runtime (ingest, agent webhook) but stay
|
|
# short enough that SIGKILLed tasks redeliver promptly. 1h matches Onyx
|
|
# and Dify defaults; long ingests can override via env.
|
|
CELERY_WORKER_PREFETCH_MULTIPLIER: int = 1
|
|
CELERY_VISIBILITY_TIMEOUT: int = 3600
|
|
# Recycle the prefork worker child once its resident size crosses this many
|
|
# kilobytes — backstops native-heap growth from docling/torch parsing. 0 disables.
|
|
CELERY_WORKER_MAX_MEMORY_PER_CHILD: int = 4194304
|
|
# Recycle the child after this many tasks; 0 disables (memory cap is the primary knob).
|
|
CELERY_WORKER_MAX_TASKS_PER_CHILD: int = 0
|
|
# Only consulted when VECTOR_STORE=mongodb or when running scripts/db/backfill.py; user data lives in Postgres.
|
|
MONGO_URI: Optional[str] = None
|
|
# User-data Postgres DB.
|
|
POSTGRES_URI: Optional[str] = None
|
|
# On app startup, apply pending Alembic migrations. Default ON for dev; disable in prod if you manage schema out-of-band.
|
|
AUTO_MIGRATE: bool = True
|
|
# On app startup, create the target Postgres database if it's missing (requires CREATEDB privilege). Dev-friendly default.
|
|
AUTO_CREATE_DB: bool = True
|
|
LLM_PATH: str = os.path.join(current_dir, "models/docsgpt-7b-f16.gguf")
|
|
DEFAULT_MAX_HISTORY: int = 150
|
|
DEFAULT_LLM_TOKEN_LIMIT: int = 128000 # Fallback when model not found in registry
|
|
RESERVED_TOKENS: dict = {
|
|
"system_prompt": 500,
|
|
"current_query": 500,
|
|
"safety_buffer": 1000,
|
|
}
|
|
DEFAULT_AGENT_LIMITS: dict = {
|
|
"token_limit": 50000,
|
|
"request_limit": 500,
|
|
}
|
|
UPLOAD_FOLDER: str = "inputs"
|
|
PARSE_PDF_AS_IMAGE: bool = False
|
|
PARSE_IMAGE_REMOTE: bool = False
|
|
DOCLING_OCR_ENABLED: bool = False # Enable OCR for docling parsers (PDF, images)
|
|
DOCLING_OCR_ATTACHMENTS_ENABLED: bool = False # Enable OCR for docling when parsing attachments
|
|
# Pages docling's threaded pipeline buffers in flight; the library
|
|
# default (100) drives worker RSS to ~3 GB on a mid-size PDF.
|
|
DOCLING_PIPELINE_QUEUE_MAX_SIZE: int = 2
|
|
VECTOR_STORE: str = "faiss" # "faiss" or "elasticsearch" or "qdrant" or "milvus" or "lancedb" or "pgvector"
|
|
# Allow-list of retriever keys an agent may use. Values must match the
|
|
# ``RetrieverCreator.retrievers`` registry keys (``classic`` / ``default``),
|
|
# NOT the legacy ``classic_rag`` label which never matched the registry.
|
|
RETRIEVERS_ENABLED: list = ["classic", "default"]
|
|
# Kill-switch for per-source retrieval dispatch. When False the retrieval
|
|
# path collapses to today's single-retriever behavior (consumed by the
|
|
# Dispatcher in a later change; defined here so the flag exists up front).
|
|
PER_SOURCE_RETRIEVAL_ENABLED: bool = True
|
|
# Flagship GraphRAG flag. Reserved and unused for now; gates graph-aware
|
|
# ingestion/retrieval when that feature lands.
|
|
GRAPHRAG_ENABLED: bool = False
|
|
# Model for ingest-time graph extraction; None reuses the instance default
|
|
# model (LLM_PROVIDER/LLM_NAME). Operator-overridable (e.g. a cheaper model).
|
|
GRAPHRAG_EXTRACTION_MODEL: Optional[str] = None
|
|
# Hard cap on chunks extracted per source (cost control).
|
|
GRAPHRAG_MAX_CHUNKS_FOR_EXTRACTION: int = 2000
|
|
AGENT_NAME: str = "classic"
|
|
FALLBACK_LLM_PROVIDER: Optional[str] = None # provider for fallback llm
|
|
FALLBACK_LLM_NAME: Optional[str] = None # model name for fallback llm
|
|
FALLBACK_LLM_API_KEY: Optional[str] = None # api key for fallback llm
|
|
|
|
# Google Drive integration
|
|
GOOGLE_CLIENT_ID: Optional[str] = None # Replace with your actual Google OAuth client ID
|
|
GOOGLE_CLIENT_SECRET: Optional[str] = None # Replace with your actual Google OAuth client secret
|
|
CONNECTOR_REDIRECT_BASE_URI: Optional[str] = (
|
|
"http://127.0.0.1:7091/api/connectors/callback" ##add redirect url as it is to your provider's console(gcp)
|
|
)
|
|
|
|
# Microsoft Entra ID (Azure AD) integration
|
|
MICROSOFT_CLIENT_ID: Optional[str] = None # Azure AD Application (client) ID
|
|
MICROSOFT_CLIENT_SECRET: Optional[str] = None # Azure AD Application client secret
|
|
MICROSOFT_TENANT_ID: Optional[str] = "common" # Azure AD Tenant ID (or 'common' for multi-tenant)
|
|
MICROSOFT_AUTHORITY: Optional[str] = None # e.g., "https://login.microsoftonline.com/{tenant_id}"
|
|
|
|
# Confluence Cloud integration
|
|
CONFLUENCE_CLIENT_ID: Optional[str] = None
|
|
CONFLUENCE_CLIENT_SECRET: Optional[str] = None
|
|
|
|
# GitHub source
|
|
GITHUB_ACCESS_TOKEN: Optional[str] = None # PAT token with read repo access
|
|
|
|
# LLM Cache
|
|
CACHE_REDIS_URL: str = "redis://localhost:6379/2"
|
|
|
|
API_URL: str = "http://localhost:7091" # backend url for celery worker
|
|
MCP_OAUTH_REDIRECT_URI: Optional[str] = None # public callback URL for MCP OAuth
|
|
INTERNAL_KEY: Optional[str] = None # internal api key for worker-to-backend auth
|
|
|
|
API_KEY: Optional[str] = None # LLM api key (used by LLM_PROVIDER)
|
|
|
|
# Provider-specific API keys (for multi-model support)
|
|
OPENAI_API_KEY: Optional[str] = None
|
|
ANTHROPIC_API_KEY: Optional[str] = None
|
|
GOOGLE_API_KEY: Optional[str] = None
|
|
GROQ_API_KEY: Optional[str] = None
|
|
HUGGINGFACE_API_KEY: Optional[str] = None
|
|
OPEN_ROUTER_API_KEY: Optional[str] = None
|
|
NOVITA_API_KEY: Optional[str] = None
|
|
|
|
OPENAI_API_BASE: Optional[str] = None # azure openai api base url
|
|
OPENAI_API_VERSION: Optional[str] = None # azure openai api version
|
|
AZURE_DEPLOYMENT_NAME: Optional[str] = None # azure deployment name for answering
|
|
AZURE_EMBEDDINGS_DEPLOYMENT_NAME: Optional[str] = None # azure deployment name for embeddings
|
|
OPENAI_BASE_URL: Optional[str] = None # openai base url for open ai compatable models
|
|
|
|
# elasticsearch
|
|
ELASTIC_CLOUD_ID: Optional[str] = None # cloud id for elasticsearch
|
|
ELASTIC_USERNAME: Optional[str] = None # username for elasticsearch
|
|
ELASTIC_PASSWORD: Optional[str] = None # password for elasticsearch
|
|
ELASTIC_URL: Optional[str] = None # url for elasticsearch
|
|
ELASTIC_INDEX: Optional[str] = "docsgpt" # index name for elasticsearch
|
|
|
|
# SageMaker config
|
|
SAGEMAKER_ENDPOINT: Optional[str] = None # SageMaker endpoint name
|
|
SAGEMAKER_REGION: Optional[str] = None # SageMaker region name
|
|
SAGEMAKER_ACCESS_KEY: Optional[str] = None # SageMaker access key
|
|
SAGEMAKER_SECRET_KEY: Optional[str] = None # SageMaker secret key
|
|
|
|
# prem ai project id
|
|
PREMAI_PROJECT_ID: Optional[str] = None
|
|
|
|
# Qdrant vectorstore config
|
|
QDRANT_COLLECTION_NAME: Optional[str] = "docsgpt"
|
|
QDRANT_LOCATION: Optional[str] = None
|
|
QDRANT_URL: Optional[str] = None
|
|
QDRANT_PORT: Optional[int] = 6333
|
|
QDRANT_GRPC_PORT: int = 6334
|
|
QDRANT_PREFER_GRPC: bool = False
|
|
QDRANT_HTTPS: Optional[bool] = None
|
|
QDRANT_API_KEY: Optional[str] = None
|
|
QDRANT_PREFIX: Optional[str] = None
|
|
QDRANT_TIMEOUT: Optional[float] = None
|
|
QDRANT_HOST: Optional[str] = None
|
|
QDRANT_PATH: Optional[str] = None
|
|
QDRANT_DISTANCE_FUNC: str = "Cosine"
|
|
|
|
# PGVector vectorstore config. Write the URI in whichever form you
|
|
# prefer — ``postgres://``, ``postgresql://``, or even the SQLAlchemy
|
|
# dialect form (``postgresql+psycopg://``) are all accepted and
|
|
# normalized internally for ``psycopg.connect()``.
|
|
PGVECTOR_CONNECTION_STRING: Optional[str] = None
|
|
# Milvus vectorstore config
|
|
MILVUS_COLLECTION_NAME: Optional[str] = "docsgpt"
|
|
MILVUS_URI: Optional[str] = "./milvus_local.db" # milvus lite version as default
|
|
MILVUS_TOKEN: Optional[str] = ""
|
|
|
|
# LanceDB vectorstore config
|
|
LANCEDB_PATH: str = "./data/lancedb" # Path where LanceDB stores its local data
|
|
LANCEDB_TABLE_NAME: Optional[str] = "docsgpts" # Name of the table to use for storing vectors
|
|
|
|
FLASK_DEBUG_MODE: bool = False
|
|
STORAGE_TYPE: str = "local" # local or s3
|
|
|
|
# S3-compatible object storage (used when STORAGE_TYPE=s3). Works with AWS
|
|
# S3 and any S3-compatible service (MinIO, Cloudflare R2, Backblaze B2,
|
|
# DigitalOcean Spaces, ...). For non-AWS services, set S3_ENDPOINT_URL and
|
|
# usually S3_PATH_STYLE=true. The SAGEMAKER_* credentials are still read as
|
|
# a deprecated fallback for backward compatibility.
|
|
S3_BUCKET_NAME: str = "docsgpt-test-bucket"
|
|
S3_ENDPOINT_URL: Optional[str] = None # custom endpoint for S3-compatible services; omit for AWS
|
|
S3_ACCESS_KEY_ID: Optional[str] = None
|
|
S3_SECRET_ACCESS_KEY: Optional[str] = None
|
|
S3_REGION: Optional[str] = None # AWS region; use "auto" for Cloudflare R2
|
|
S3_PATH_STYLE: bool = False # path-style addressing (required by most non-AWS services)
|
|
|
|
# Anonymous startup version check for security issues.
|
|
VERSION_CHECK: bool = True
|
|
URL_STRATEGY: str = "backend" # backend or s3
|
|
|
|
JWT_SECRET_KEY: str = ""
|
|
|
|
# Encryption settings
|
|
ENCRYPTION_SECRET_KEY: str = "default-docsgpt-encryption-key"
|
|
|
|
TTS_PROVIDER: str = "google_tts" # google_tts or elevenlabs
|
|
ELEVENLABS_API_KEY: Optional[str] = None
|
|
STT_PROVIDER: str = "openai" # openai or faster_whisper
|
|
OPENAI_STT_MODEL: str = "gpt-4o-mini-transcribe"
|
|
STT_LANGUAGE: Optional[str] = None
|
|
STT_MAX_FILE_SIZE_MB: int = 50
|
|
STT_ENABLE_TIMESTAMPS: bool = False
|
|
STT_ENABLE_DIARIZATION: bool = False
|
|
|
|
# Tool pre-fetch settings
|
|
ENABLE_TOOL_PREFETCH: bool = True
|
|
|
|
# When True, OpenAI Responses API calls are persisted server-side
|
|
# (store=true) so a previous_response_id can chain turns. When False
|
|
# (the default) Responses calls are stateless (store=false) and any
|
|
# reasoning is carried across the in-turn tool loop via encrypted
|
|
# reasoning items instead.
|
|
OPENAI_RESPONSES_STORE: bool = False
|
|
|
|
# Config-free tools on by default in agentless chats. ``scheduler`` is
|
|
# dual-registered (also in ``BUILTIN_AGENT_TOOLS``) so the same synthetic id
|
|
# resolves whether reached via defaults or the agent picker.
|
|
DEFAULT_CHAT_TOOLS: list = ["memory", "read_webpage", "scheduler"]
|
|
|
|
# Conversation Compression Settings
|
|
ENABLE_CONVERSATION_COMPRESSION: bool = True
|
|
COMPRESSION_THRESHOLD_PERCENTAGE: float = 0.8 # Trigger at 80% of context
|
|
COMPRESSION_MODEL_OVERRIDE: Optional[str] = None # Use different model for compression
|
|
COMPRESSION_PROMPT_VERSION: str = "v1.0" # Track prompt iterations
|
|
COMPRESSION_MAX_HISTORY_POINTS: int = 3 # Keep only last N compression points to prevent DB bloat
|
|
|
|
# Internal SSE push channel (notifications + durable replay journal)
|
|
# Master switch — when False, /api/events emits a "push_disabled" comment
|
|
# and returns; clients fall back to polling. Publisher becomes a no-op.
|
|
ENABLE_SSE_PUSH: bool = True
|
|
# Per-user durable backlog cap (~entries). At typical event rates this
|
|
# gives ~24h of replay; tune up for verbose feeds, down for memory.
|
|
EVENTS_STREAM_MAXLEN: int = 1000
|
|
# Bounds uvicorn's graceful-shutdown drain (uvicorn_worker doesn't forward
|
|
# --graceful-timeout). Keep below the gunicorn --timeout (180) watchdog.
|
|
# Used by gunicorn_worker.BoundedDrainUvicornWorker.
|
|
GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS: int = 30
|
|
# SSE keepalive comment cadence. Must sit under Cloudflare's 100s idle
|
|
# close and iOS Safari's ~60s — 15s gives generous headroom.
|
|
SSE_KEEPALIVE_SECONDS: int = 15
|
|
# Cap on simultaneous SSE connections per user. Each connection holds
|
|
# one WSGI thread (32 per gunicorn worker) and one Redis pub/sub
|
|
# connection. 8 covers normal multi-tab use without letting one user
|
|
# starve the pool. Set to 0 to disable the cap.
|
|
SSE_MAX_CONCURRENT_PER_USER: int = 8
|
|
# Per-request cap on the number of backlog entries XRANGE returns
|
|
# for ``/api/events`` snapshots. Bounds the bytes a single replay
|
|
# can move from Redis to the wire — a malicious client looping
|
|
# ``Last-Event-ID=<oldest>`` reconnects can only enumerate this
|
|
# many entries per round-trip. Combined with the per-user
|
|
# connection cap above and the windowed budget below, total
|
|
# enumeration throughput is bounded.
|
|
EVENTS_REPLAY_MAX_PER_REQUEST: int = 200
|
|
# Sliding-window cap on snapshot replays per user. Once the budget
|
|
# is exhausted the route returns HTTP 429 with the cursor pinned;
|
|
# the client backs off and retries after the window rolls over.
|
|
EVENTS_REPLAY_BUDGET_REQUESTS_PER_WINDOW: int = 30
|
|
EVENTS_REPLAY_BUDGET_WINDOW_SECONDS: int = 60
|
|
|
|
# Retention for the ``message_events`` journal. The ``cleanup_message_events``
|
|
# beat task deletes rows older than this. Reconnect-replay only
|
|
# needs the journal for streams a client could still be tailing,
|
|
# so 14 days is a generous default that covers paused/tool-action
|
|
# flows without unbounded table growth.
|
|
MESSAGE_EVENTS_RETENTION_DAYS: int = 14
|
|
|
|
# Remote Device feature.
|
|
REMOTE_DEVICE_SESSION_IDLE_SECONDS: int = 60
|
|
REMOTE_DEVICE_REQUIRE_SIGNATURE: bool = False
|
|
REMOTE_DEVICE_PAIRING_TTL_SECONDS: int = 600
|
|
# Redis-backed broker tunables (route invocations cross-process so a
|
|
# scheduled/Celery run reaches the web-held device session). The command
|
|
# queue TTL must exceed the max command drain deadline (the tool caps
|
|
# timeout_ms at 600s, drained with a +5s margin = 605s) so a queued command
|
|
# for a briefly-offline device isn't evicted before its own drain gives up.
|
|
REMOTE_DEVICE_CMD_QUEUE_TTL_SECONDS: int = 900
|
|
REMOTE_DEVICE_INVOCATION_TTL_SECONDS: int = 900
|
|
REMOTE_DEVICE_OUTPUT_STREAM_MAXLEN: int = 10_000
|
|
|
|
# Scheduler (see scheduler.md).
|
|
SCHEDULE_DISPATCHER_INTERVAL: int = 30
|
|
SCHEDULE_MIN_INTERVAL: int = 900
|
|
SCHEDULE_MAX_PER_USER: int = 50
|
|
SCHEDULE_RUN_TIMEOUT: int = 600
|
|
SCHEDULE_MISFIRE_GRACE: int = 60
|
|
SCHEDULE_AUTOPAUSE_FAILURES: int = 3
|
|
SCHEDULE_ONCE_MAX_HORIZON: int = 31_536_000
|
|
SCHEDULE_RUN_OUTPUT_RETENTION_DAYS: int = 90
|
|
|
|
# Code-execution sandbox (see artifacts-code-execution-spec.md §4 C2).
|
|
# The app is a CLIENT of an always-on runner; defaults are safe so app
|
|
# import never fails when the sandbox is unconfigured.
|
|
SANDBOX_BACKEND: str = "jupyter" # "jupyter" (self-host) | "daytona" (Daytona Cloud)
|
|
# URL of the Jupyter Kernel Gateway runner (the docsgpt-sandbox service).
|
|
SANDBOX_GATEWAY_URL: str = "http://localhost:8888"
|
|
SANDBOX_GATEWAY_AUTH_TOKEN: Optional[str] = None # gateway auth token, if set
|
|
SANDBOX_KERNEL_NAME: str = "python3" # kernelspec name to launch per session
|
|
SANDBOX_MAX_TTL: int = 1200 # hard cap (s) on agent-selectable keep-alive TTL
|
|
# Per-process/worker cap on concurrent live sandbox sessions. Backend-agnostic
|
|
# (complements DAYTONA_MAX_SANDBOXES); when reached, an LRU-idle session is
|
|
# evicted to make room. This bound is local to each app/worker process.
|
|
# 0 (or any non-positive value) disables the cap (unlimited sessions).
|
|
SANDBOX_MAX_SESSIONS: int = 32
|
|
SANDBOX_EXEC_TIMEOUT: int = 60 # default wall-clock cap (s) per exec call
|
|
SANDBOX_HTTP_TIMEOUT: int = 10 # fixed cap (s) for REST control calls (create/delete/alive/interrupt)
|
|
SANDBOX_MAX_OUTPUT_BYTES: int = 8 * 1024 * 1024 # cap on buffered stdout+stderr per exec
|
|
SANDBOX_MAX_FILE_BYTES: int = 10 * 1024 * 1024 # cap on get_file size routed through stdout
|
|
SANDBOX_MAX_INPUT_BYTES: int = 25 * 1024 * 1024 # cap on an input document staged into a sandbox session
|
|
# Per-agent-node cap on files passed natively to the node's LLM (vision/doc
|
|
# inputs). Files past the cap are extracted to text or dropped, not attached
|
|
# natively, to bound context/cost. Re-uses SANDBOX_MAX_INPUT_BYTES per file.
|
|
WORKFLOW_NODE_NATIVE_MAX_FILES: int = 5
|
|
# Runner container resource caps — consumed by the docsgpt-sandbox compose
|
|
# service (deployment/sandbox), not by the app client. cgroup CPU/mem caps
|
|
# are part of the untrusted-code security boundary.
|
|
SANDBOX_MEMORY: str = "1g" # docker mem_limit for the runner container
|
|
SANDBOX_CPUS: str = "1.0" # docker cpu quota for the runner container
|
|
# Daytona Cloud managed backend (used only when SANDBOX_BACKEND="daytona").
|
|
# The app is a REST client of Daytona Cloud authenticated by DAYTONA_API_KEY;
|
|
# all knobs are optional so app import never fails when the backend is unused.
|
|
DAYTONA_API_KEY: Optional[str] = None # Daytona Cloud API key (secret)
|
|
DAYTONA_API_URL: Optional[str] = None # override Daytona API base URL, if self-targeting
|
|
DAYTONA_TARGET: Optional[str] = None # Daytona region/target, e.g. "us"
|
|
DAYTONA_SNAPSHOT: Optional[str] = None # snapshot/image id to create sandboxes from
|
|
DAYTONA_LANGUAGE: str = "python" # default runtime language for created sandboxes
|
|
DAYTONA_AUTO_STOP_INTERVAL: int = 15 # minutes idle before Daytona auto-stops a sandbox (0 disables)
|
|
DAYTONA_AUTO_DELETE_INTERVAL: int = 60 # minutes after stop before Daytona auto-deletes (-1 disables)
|
|
DAYTONA_MAX_SANDBOXES: int = 50 # cap on concurrent live Daytona sandboxes (cost-DoS guard)
|
|
# Per-user artifact quotas (generous defaults; enforced at persistence time).
|
|
# For all three, 0 (or any non-positive value) disables that quota (unlimited).
|
|
ARTIFACT_MAX_BYTES: int = 50 * 1024 * 1024 # cap on a single stored artifact version's bytes
|
|
ARTIFACT_MAX_COUNT_PER_USER: int = 5000 # cap on artifacts a user may own
|
|
ARTIFACT_MAX_TOTAL_BYTES_PER_USER: int = 5 * 1024 * 1024 * 1024 # cap on a user's total stored bytes
|
|
# Cap on bytes served per MCP ``resources/read`` so a giant artifact never streams into LLM context.
|
|
ARTIFACT_RESOURCE_READ_MAX_BYTES: int = 1 * 1024 * 1024
|
|
|
|
@field_validator("POSTGRES_URI", mode="before")
|
|
@classmethod
|
|
def _normalize_postgres_uri_validator(cls, v):
|
|
return normalize_postgres_uri(v)
|
|
|
|
@field_validator("PGVECTOR_CONNECTION_STRING", mode="before")
|
|
@classmethod
|
|
def _normalize_pgvector_connection_string_validator(cls, v):
|
|
return normalize_pgvector_connection_string(v)
|
|
|
|
@field_validator(
|
|
"API_KEY",
|
|
"OPENAI_API_KEY",
|
|
"ANTHROPIC_API_KEY",
|
|
"GOOGLE_API_KEY",
|
|
"GROQ_API_KEY",
|
|
"HUGGINGFACE_API_KEY",
|
|
"NOVITA_API_KEY",
|
|
"EMBEDDINGS_KEY",
|
|
"FALLBACK_LLM_API_KEY",
|
|
"QDRANT_API_KEY",
|
|
"ELEVENLABS_API_KEY",
|
|
"INTERNAL_KEY",
|
|
mode="before",
|
|
)
|
|
@classmethod
|
|
def normalize_api_key(cls, v: Optional[str]) -> Optional[str]:
|
|
"""
|
|
Normalize API keys: convert 'None', 'none', empty strings,
|
|
and whitespace-only strings to actual None.
|
|
Handles Pydantic loading 'None' from .env as string "None".
|
|
"""
|
|
if v is None:
|
|
return None
|
|
if not isinstance(v, str):
|
|
return v
|
|
stripped = v.strip()
|
|
if stripped == "" or stripped.lower() == "none":
|
|
return None
|
|
return stripped
|
|
|
|
|
|
# Project root is one level above application/
|
|
path = Path(__file__).parent.parent.parent.absolute()
|
|
settings = Settings(_env_file=path.joinpath(".env"), _env_file_encoding="utf-8")
|