Dev AI config: env var overrides in config_reader, LM Studio via .env

config_reader.py now merges environment variables (AI_PROVIDER,
LMSTUDIO_BASE_URL, LMSTUDIO_API_KEY, LMSTUDIO_MODEL, OLLAMA_*,
ANTHROPIC_*) on top of the JSON config file, so the dev .env file
can pin the AI connection without writing to the shared config volume.

docker-compose.dev.yml loads features/doc-service/.env (gitignored)
into the doc-service container so the token is never committed.

.env.example updated with all supported override variables and comments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-04-14 11:48:15 +02:00
parent 1cdc532fff
commit 52a2967f61
3 changed files with 78 additions and 3 deletions
+1
View File
@@ -25,5 +25,6 @@ services:
doc-service:
command: sh scripts/start_dev.sh
env_file: ./features/doc-service/.env # gitignored — holds local AI credentials
volumes:
- ./features/doc-service:/app
+18
View File
@@ -1,3 +1,21 @@
DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/destroying_sap
DATA_DIR=/data/documents
CONFIG_PATH=/config/doc_service_config.json
# Optional AI provider overrides — if set, these take precedence over
# whatever is stored in doc_service_config.json.
# Useful for pinning a dev environment to a specific local AI instance
# without touching the shared config volume.
#
# AI_PROVIDER=lmstudio
# LMSTUDIO_BASE_URL=http://host.docker.internal:1234/v1
# LMSTUDIO_API_KEY=your-lm-studio-token
# LMSTUDIO_MODEL=local-model
#
# AI_PROVIDER=ollama
# OLLAMA_BASE_URL=http://host.docker.internal:11434/v1
# OLLAMA_MODEL=llama3.2
#
# AI_PROVIDER=anthropic
# ANTHROPIC_API_KEY=sk-ant-...
# ANTHROPIC_MODEL=claude-haiku-4-5-20251001
@@ -2,10 +2,23 @@
Reads doc_service_config.json from the shared config volume.
Caches the result for 30 seconds to avoid hitting the filesystem on every request.
Uses asyncio.to_thread so the synchronous file read doesn't block the event loop.
Env var overrides (take precedence over the JSON config file, never committed):
AI_PROVIDER — "lmstudio" | "ollama" | "anthropic"
LMSTUDIO_BASE_URL — e.g. http://host.docker.internal:1234/v1
LMSTUDIO_API_KEY
LMSTUDIO_MODEL
OLLAMA_BASE_URL
OLLAMA_MODEL
OLLAMA_API_KEY
ANTHROPIC_API_KEY
ANTHROPIC_MODEL
"""
import asyncio
import json
import os
import time
from copy import deepcopy
from pathlib import Path
from app.core.config import settings
@@ -31,9 +44,52 @@ _CACHE_TTL = 30.0
def _read_config_sync() -> dict:
path = Path(settings.CONFIG_PATH)
if not path.exists():
return _DEFAULT_CONFIG.copy()
base = deepcopy(_DEFAULT_CONFIG)
else:
with open(path) as f:
return json.load(f)
base = json.load(f)
return _apply_env_overrides(base)
def _apply_env_overrides(config: dict) -> dict:
"""
Merge environment variable overrides into the config dict.
Env vars win over whatever is stored in the JSON file.
This lets the dev .env file pin the AI connection without writing to the
shared volume (which would affect all users).
"""
cfg = deepcopy(config)
ai = cfg.setdefault("ai", {})
if provider := os.environ.get("AI_PROVIDER"):
ai["provider"] = provider
# LM Studio
lms = ai.setdefault("lmstudio", {})
if v := os.environ.get("LMSTUDIO_BASE_URL"):
lms["base_url"] = v
if v := os.environ.get("LMSTUDIO_API_KEY"):
lms["api_key"] = v
if v := os.environ.get("LMSTUDIO_MODEL"):
lms["model"] = v
# Ollama
oll = ai.setdefault("ollama", {})
if v := os.environ.get("OLLAMA_BASE_URL"):
oll["base_url"] = v
if v := os.environ.get("OLLAMA_MODEL"):
oll["model"] = v
if v := os.environ.get("OLLAMA_API_KEY"):
oll["api_key"] = v
# Anthropic
ant = ai.setdefault("anthropic", {})
if v := os.environ.get("ANTHROPIC_API_KEY"):
ant["api_key"] = v
if v := os.environ.get("ANTHROPIC_MODEL"):
ant["model"] = v
return cfg
async def load_doc_config() -> dict: