File size: 6,758 Bytes
027123c
 
19bd037
 
027123c
 
 
 
 
 
 
 
 
 
 
 
 
 
81e5fe7
079782d
 
 
 
81e5fe7
0e02a0f
 
 
 
0721bb4
 
3743cfe
 
 
 
 
 
027123c
 
 
 
 
 
 
d65c41d
 
 
 
 
027123c
b68816f
 
 
 
 
 
 
027123c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
079782d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b68816f
 
 
 
 
 
 
027123c
 
 
 
 
 
19bd037
 
027123c
 
 
 
 
 
 
 
2ba0613
 
c7d59cf
2ba0613
 
f282b15
 
 
 
 
 
 
 
 
027123c
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""Centralized configuration management using pydantic-settings."""

# import os
# from typing import Optional
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    """Application settings loaded from environment variables."""

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

    # Feature flags
    # REMOVED 2026-07-02: the former ENABLE_SLOW_PATH flag is gone. `structured_flow`
    # now always runs the analytical slow path (Planner -> TaskRunner -> Assembler), so
    # every structured question persists a report_inputs record (reports no longer
    # depend on flipping a flag). extra="allow" ignores a stale ENABLE_SLOW_PATH in .env.

    # DEPRECATED 2026-06-24: the problem_validated gate was removed (the goal is now
    # user-entered objective + business_questions, no agent validation). This flag no
    # longer has any effect — the gate call site in ChatHandler is commented out. Kept
    # to avoid .env churn; remove once no environment references it.
    enable_gate: bool = Field(alias="enable_gate", default=False)

    # Skip init_db() (create_all + startup DDL) on boot. TRUE by default post-dedorch
    # cutover: Go owns the dedorch schema, so Python (consumer-only role) must NOT run
    # init_db — its ALTER/index DDL on Go-owned tables fails with InsufficientPrivilege
    # ("must be owner of table rooms"). Set to false only for a local Python-owned DB.
    skip_init_db: bool = Field(alias="SKIP_INIT_DB", default=True)

    # Database
    postgres_connstring: str

    # Redis
    redis_url: str
    redis_prefix: str = "dataeyond-agent-service_"

    # Azure OpenAI - GPT-5.4-mini (map to .env names with double underscores)
    azureai_api_key_54m: str = Field(alias="azureai__api_key__54m", default="")
    azureai_endpoint_url_54m: str = Field(alias="azureai__endpoint__url__54m", default="")
    azureai_deployment_name_54m: str = Field(alias="azureai__deployment__name__54m", default="")
    azureai_api_version_54m: str = Field(alias="azureai__api__version__54m", default="")

    # Azure OpenAI - GPT-5.4-nano (knowledge-extraction pipeline; all four
    # branches route here — see KNOWLEDGE_PIPELINE_TODO.md D3)
    azureai_api_key_54n: str = Field(alias="azureai__api_key__54n", default="")
    azureai_endpoint_url_54n: str = Field(alias="azureai__endpoint__url__54n", default="")
    azureai_deployment_name_54n: str = Field(alias="azureai__deployment__name__54n", default="")
    azureai_api_version_54n: str = Field(alias="azureai__api__version__54n", default="")

    # Azure OpenAI - Embeddings
    azureai_api_key_embedding: str = Field(alias="azureai__api_key__embedding", default="")
    azureai_endpoint_url_embedding: str = Field(alias="azureai__endpoint__url__embedding", default="")
    azureai_deployment_name_embedding: str = Field(alias="azureai__deployment__name__embedding", default="")
    azureai_api_version_embedding: str = Field(alias="azureai__api__version__embedding", default="")

    # Azure Document Intelligence
    azureai_docintel_endpoint: str = Field(alias="azureai__docintel__endpoint", default="")
    azureai_docintel_key: str = Field(alias="azureai__docintel__key", default="")

    # Azure Blob Storage
    azureai_blob_sas: str = Field(alias="azureai__blob__sas", default="")
    azureai_container_endpoint: str = Field(alias="azureai__container__endpoint", default="")
    azureai_container_name: str = Field(alias="azureai__container__name", default="")
    azureai_container_account_name: str = Field(alias="azureai__container__account__name", default="")

    # Object storage provider toggle. Mirrors the Go data plane's `storage.provider`
    # (see Orchestrator config: azure_blob | supabase_s3). Blank falls back to azure_blob.
    # Tabular query execution reads the processed Parquet from whichever backend is active.
    storage_provider: str = Field(alias="STORAGE_PROVIDER", default="azure_blob")

    # Supabase S3-compatible object storage (active when storage_provider == "supabase_s3").
    # BRIDGE (Option C): lets Python read the same bucket Go writes to, until Go exposes a
    # source-data endpoint (see docs/proposal_go_source_data_endpoint.md) — then this client
    # is removed and Python stops touching storage directly.
    supabase_s3_bucket: str = Field(alias="SUPABASE_S3_BUCKET", default="")
    supabase_s3_endpoint: str = Field(alias="SUPABASE_S3_ENDPOINT", default="")
    supabase_s3_region: str = Field(alias="SUPABASE_S3_REGION", default="")
    supabase_s3_access_key_id: str = Field(alias="SUPABASE_S3_ACCESS_KEY_ID", default="")
    supabase_s3_secret_access_key: str = Field(alias="SUPABASE_S3_SECRET_ACCESS_KEY", default="")

    # Mistral OCR — the knowledge-parsing primary backend (no GPU). Read here so the
    # key reaches a deployed HuggingFace Space the same way every other credential
    # does, instead of a bare os.environ lookup that a Space's secret injection can
    # miss. The standalone parser (`py -m src.knowledge_parsing.run`) still falls
    # back to the environment / repo .env — see src/knowledge_parsing/mistral_ocr.py.
    mistral_api_key: str = Field(alias="MISTRAL_API_KEY", default="")

    # Langfuse
    LANGFUSE_PUBLIC_KEY: str
    LANGFUSE_SECRET_KEY: str
    LANGFUSE_HOST: str

    # MongoDB (for users - existing)
    # emarcal_mongo_endpoint_url: str = Field(alias="emarcal__mongo__endpoint__url", default="")
    # emarcal_buma_mongo_dbname: str = Field(alias="emarcal__buma__mongo__dbname", default="")

    # JWT (for users - existing)
    emarcal_jwt_secret_key: str = Field(alias="emarcal__jwt__secret_key", default="")
    emarcal_jwt_algorithm: str = Field(alias="emarcal__jwt__algorithm", default="HS256")

    # Bcrypt salt (for users - existing)
    emarcal_bcrypt_salt: str = Field(alias="emarcal__bcrypt__salt", default="")

    # DB credential encryption (Fernet key for user-registered database creds)
    dataeyond_db_credential_key: str = Field(
        alias="dataeyond__db__credential__key"
    )

    # Shared service secret for the F-2 gate — UNWIRED 2026-07-27 (DEV_PLAN #37). The
    # gate is no longer mounted (see main.py), so this value is read by nothing. Kept
    # commented rather than deleted so the whole feature restores in one place. If a
    # deployment still has `dataeyond__service__secret` in its .env it is simply
    # ignored (extra="allow" on Settings). Restore alongside the main.py dependency.
    # dataeyond_service_secret: str = Field(
    #     alias="dataeyond__service__secret", default=""
    # )


# Singleton instance
settings = Settings()