CCAI-Demo / backend /app /config.py
Jordan Miller
Load cross-project shared.env before project .env for local dev.
40dee71
Raw
History Blame Contribute Delete
11.1 kB
from __future__ import annotations
import os
from pathlib import Path
from pydantic import AliasChoices, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
_ENV_FILE = _PROJECT_ROOT / ".env"
def _shared_env_candidates() -> list[Path]:
"""Locations for the cross-project shared.env (never committed)."""
explicit = (os.getenv("SHARED_ENV_PATH") or os.getenv("SHARED_ENV_FILE") or "").strip()
if explicit:
return [Path(explicit).expanduser()]
return [
Path.home() / ".secrets" / "shared.env",
Path.home() / "Downloads" / "shared.env",
]
def _resolve_shared_env() -> Path | None:
for candidate in _shared_env_candidates():
if candidate.is_file():
return candidate
return None
def _settings_env_files() -> tuple[str, ...]:
"""Shared secrets first; project .env overrides."""
files: list[str] = []
shared = _resolve_shared_env()
if shared:
files.append(str(shared))
if _ENV_FILE.is_file():
files.append(str(_ENV_FILE))
return tuple(files)
class Settings(BaseSettings):
hana_base_url: str = "https://hana.neonaialpha.com"
hana_username: str = "guest"
hana_password: str = "password"
# BrainForge/Security (4090 x1-3): separate HANA login — use HANA_KLATCHAT_PASSWORD or HANA_PASSWORD_KLATCHAT in project-root .env
hana_username_klatchat: str = ""
# Same value as HuggingFace Space secret API_KEY for 4090-x1-3 — OpenAI-compatible Bearer, NOT HANA /auth/login password.
hana_password_klatchat: str = Field(
default="",
validation_alias=AliasChoices("HANA_KLATCHAT_PASSWORD", "HANA_PASSWORD_KLATCHAT"),
)
# Direct vLLM base (no /v1); matches brainforge-webapp docker config 4090-x1-3 host.
neon_security_vllm_base_url: str = Field(
default="https://4090-x1-3.neonaiservices2.com/vllm0",
validation_alias=AliasChoices("NEON_SECURITY_VLLM_BASE_URL", "VLLM_BASE_URL"),
)
# Comma-separated model_id values to merge via get_personas when get_models omits them (needs HANA access)
hana_neon_model_supplement_ids: str = "BrainForge/Security@2026.03.18"
# OpenAI-compatible Bearer token for direct vLLM endpoints
# (e.g. https://4090-x1-3.neonaiservices2.com/vllm0/v1). Distinct
# from any HANA login credential. Sent as Authorization: Bearer.
vllm_api_key: str = ""
fireworks_api_key: str = ""
together_api_key: str = ""
openai_api_key: str = ""
gemini_api_key: str = ""
mistral_api_key: str = ""
orchestrator_model: str = "gpt-4o-mini"
# Lightweight model for addressed-to / status classifiers. Falls back
# to orchestrator_model when unset or unresolvable.
orchestrator_fast_model: str = "gemini-2.0-flash"
speed_priority: bool = False
cors_origins: str = "http://localhost:3000,http://localhost:3001,http://localhost:3002"
model_config = SettingsConfigDict(
env_file=_settings_env_files(),
env_file_encoding="utf-8",
)
@field_validator("neon_security_vllm_base_url", mode="before")
@classmethod
def _strip_vllm_v1_suffix(cls, value: object) -> object:
if isinstance(value, str):
return value.rstrip("/").removesuffix("/v1")
return value
@property
def cors_origin_list(self) -> list[str]:
return [o.strip() for o in self.cors_origins.split(",") if o.strip()]
def _neon_security_direct_vllm_enabled(self, hana_model_id: str) -> bool:
"""BrainForge/Security on 4090-x1-3: same pattern as brainforge-webapp (direct vLLM + API key).
Gated on VLLM_API_KEY (the Bearer token sent to the vLLM
/v1/chat/completions endpoint), NOT the HANA klatchat password.
"""
if "security" not in (hana_model_id or "").lower():
return False
return bool((self.vllm_api_key or "").strip() and (self.neon_security_vllm_base_url or "").strip())
@property
def providers(self) -> list[dict]:
"""Build the flat list of all available LLM providers and their models."""
providers: list[dict] = []
fw_url = "https://api.fireworks.ai/inference/v1"
fw_key = self.fireworks_api_key
fw_ok = fw_key and fw_key != "your-fireworks-api-key-here"
tg_url = "https://api.together.xyz/v1"
tg_key = self.together_api_key
tg_ok = tg_key and tg_key != "your-together-api-key-here"
if fw_ok:
providers.append({
"id": "kimi",
"name": "Kimi",
"base_url": fw_url,
"api_key": fw_key,
"models": [
{"id": "accounts/fireworks/models/kimi-k2-thinking", "name": "Kimi K2 Thinking", "params": "1T (32B active)"},
{"id": "accounts/fireworks/models/kimi-k2-instruct-0905", "name": "Kimi K2 Instruct 0905", "params": "1T (32B active)"},
{"id": "accounts/fireworks/models/kimi-k2p5", "name": "Kimi K2.5", "params": "1T (32B active)"},
],
})
providers.append({
"id": "deepseek",
"name": "DeepSeek",
"base_url": fw_url,
"api_key": fw_key,
"models": [
{"id": "accounts/fireworks/models/deepseek-v3p1", "name": "DeepSeek V3.1", "params": "671B (37B active)"},
{"id": "accounts/fireworks/models/deepseek-v3p2", "name": "DeepSeek V3.2", "params": "671B (37B active)"},
],
})
oai_ok = self.openai_api_key and self.openai_api_key != "your-openai-api-key-here"
if oai_ok or fw_ok or tg_ok:
oai_models = []
if oai_ok:
oai_models.extend([
{"id": "gpt-5.4", "name": "GPT-5.4", "params": "Undisclosed"},
{"id": "gpt-4.1", "name": "GPT-4.1", "params": "Undisclosed"},
{"id": "gpt-4o", "name": "GPT-4o", "params": "~200B (estimated)"},
{"id": "gpt-4o-mini", "name": "GPT-4o Mini", "params": "~8B (estimated)"},
{"id": "gpt-4.1-mini", "name": "GPT-4.1 Mini", "params": "Undisclosed"},
{"id": "o4-mini", "name": "o4-Mini", "params": "Undisclosed"},
])
if fw_ok:
oai_models.append({
"id": "accounts/fireworks/models/gpt-oss-120b",
"name": "GPT-OSS 120B",
"params": "117B (5.1B active)",
"base_url": fw_url,
"api_key": fw_key,
})
if tg_ok:
oai_models.append({
"id": "openai/gpt-oss-20b",
"name": "GPT-OSS 20B",
"params": "~20B",
"base_url": tg_url,
"api_key": tg_key,
})
if oai_models:
providers.append({
"id": "openai",
"name": "OpenAI",
"base_url": "https://api.openai.com/v1",
"api_key": self.openai_api_key if oai_ok else "",
"models": oai_models,
})
mistral_ok = self.mistral_api_key and self.mistral_api_key != "your-mistral-api-key-here"
if mistral_ok:
providers.append({
"id": "mistral",
"name": "Mistral",
"base_url": "https://api.mistral.ai/v1",
"api_key": self.mistral_api_key,
"models": [
{"id": "mistral-small-2506", "name": "Mistral Small 3.2", "params": "24B"},
{"id": "mistral-small-2603", "name": "Mistral Small 4", "params": "119B"},
{"id": "devstral-2512", "name": "Devstral2", "params": "123B"},
],
})
providers.append({
"id": "qwen",
"name": "Qwen",
"base_url": tg_url,
"api_key": tg_key,
"models": [
{"id": "Qwen/Qwen3-VL-8B-Instruct", "name": "Qwen3 VL 8B", "params": "8B"},
],
})
providers.append({
"id": "meta",
"name": "Meta Llama",
"base_url": tg_url,
"api_key": tg_key,
"models": [
{"id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", "name": "Llama 3.3 70B Turbo", "params": "70B"},
{"id": "meta-llama/Meta-Llama-3-8B-Instruct-Lite", "name": "Llama 3 8B Lite", "params": "8B"},
],
})
if self.gemini_api_key and self.gemini_api_key != "your-gemini-api-key-here":
providers.append({
"id": "gemini",
"name": "Google Gemini",
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
"api_key": self.gemini_api_key,
"models": [
{"id": "gemini-2.0-flash", "name": "Gemini 2.0 Flash", "params": "Undisclosed"},
{"id": "gemini-2.5-flash", "name": "Gemini 2.5 Flash", "params": "Undisclosed"},
{"id": "gemini-2.5-pro", "name": "Gemini 2.5 Pro", "params": "Undisclosed"},
],
})
return providers
def resolve_model(self, model_id: str) -> dict | None:
"""Given a model_id, return {base_url, api_key, model_id, ...} or None.
Handles both external providers and Neon HANA models (prefixed with 'neon:').
"""
if model_id.startswith("neon:"):
parts = model_id.split(":", 2)
if len(parts) == 3:
hana_model_id = parts[1]
persona_name = parts[2]
out: dict = {
"is_neon": True,
"model_id": model_id,
"hana_model_id": hana_model_id,
"persona_name": persona_name,
"display_name": persona_name,
"provider": "Neon",
"base_url": self.hana_base_url,
"api_key": "",
}
if self._neon_security_direct_vllm_enabled(hana_model_id):
out["neon_direct_vllm"] = True
out["vllm_base_url"] = f"{self.neon_security_vllm_base_url.rstrip('/')}/v1"
out["vllm_api_key"] = self.vllm_api_key
return out
for prov in self.providers:
for m in prov["models"]:
if m["id"] == model_id:
return {
"base_url": m.get("base_url", prov["base_url"]),
"api_key": m.get("api_key", prov["api_key"]),
"model_id": m["id"],
"display_name": m["name"],
"provider": prov["name"],
}
return None
settings = Settings()