ADR: Centralizar model strings AI en config/settings/base.py
Estado: Aceptada · Aplicada en v1.0.73 (s61 · 14-05-2026) Autores: Edu + Claude Code Opus · PR#28 Decisión en una línea: Todo model string de IA runtime vive exclusivamente en
config/settings/base.py. Cambiar de modelo es 1 env var en Dokploy + redeploy, no N ediciones de código.
Contexto
Tras el audit de drift acordado en la Sesión 61, se detectaron 9 callsites en 8 archivos con model strings hardcoded ("gemma-4-26b-a4b-it", "claude-haiku-4-5-20251001") o con os.environ.get(...) directo en callsites Django (lo que duplica defaults y bypasea el setting centralizado).
La Regla 8 de CLAUDE.md ya exigía “modelo único”, pero su aplicación dependía de disciplina humana. Con 9 callsites independientes el drift era inevitable: basta un PR que añada un callsite sin actualizar los demás para que la política se rompa silenciosamente.
Decisión
Única fuente de verdad por model string: config/settings/base.py, consumido vía django.conf.settings en todo callsite runtime Django.
Settings añadidas (PR#28)
| Setting | Env var | Default | Uso |
|---|---|---|---|
ANTHROPIC_HAIKU_MODEL | ANTHROPIC_HAIKU_MODEL | claude-haiku-4-5-20251001 | CNS / Tutor / Explain / fallback router |
ANTHROPIC_SONNET_MODEL | ANTHROPIC_SONNET_MODEL | claude-sonnet-4-6 | Reservado runtime (actualmente sin callsite activo) |
Settings preexistentes (ahora correctamente consumidas)
| Setting | Env var | Default |
|---|---|---|
GEMMA4_GENAI_MODEL | GEMMA4_GENAI_MODEL | gemma-4-26b-a4b-it |
EDGE_AI_GOOGLE_GENAI_MODEL | EDGE_AI_GOOGLE_GENAI_MODEL | gemma-4-26b-a4b-it |
GEMMA4_GENAI_SEED | GEMMA4_GENAI_SEED | None |
GEMMA4_GENAI_MAX_IMAGE_DIMENSION | GEMMA4_GENAI_MAX_IMAGE_DIMENSION | 2048 |
GEMMA4_GENAI_JPEG_QUALITY | GEMMA4_GENAI_JPEG_QUALITY | 95 |
Callsites migrados (9)
| Archivo | Cambio concreto |
|---|---|
monitoring/services/ai_providers/google_genai.py | Elimina DEFAULT_MODEL = "gemma-..." · _get_model() lee settings.EDGE_AI_GOOGLE_GENAI_MODEL directo |
core/services/ai_providers/router.py | Elimina DEFAULT_GOOGLE_GENAI_MODEL y DEFAULT_CLAUDE_MODEL · _try_google_genai y _try_claude leen settings |
core/services/ai_operations.py | Elimina AIOperations.MODEL = "gemma-..." class attr · _call_ai lee settings.GEMMA4_GENAI_MODEL |
blueprints/services/google_genai_driver.py | 4 env GEMMA4_GENAI_* leídas vía settings · elimina import os |
monitoring/services/ai_providers/claude.py | Elimina CLAUDE_MODEL = "claude-haiku-..." literal |
monitoring/services/tutor_service.py | _ask_claude lee django_settings.ANTHROPIC_HAIKU_MODEL |
monitoring/services/explain_service.py | explain_insight + _revise_claude leen settings (2 callsites) |
.github/scripts/bib_ingest.py | Lee os.getenv("BIB_INGEST_TRIAGE_MODEL", "claude-haiku-4-5") y BIB_INGEST_DEEP_MODEL — NO carga Django, excepción legítima |
Excluidos del refactor (correctos, sin cambio)
scripts/test_vertex_eu_minitest.py,scripts/test_tutor_vlan*.py: literales son parte del input bajo prueba (regression tests). El modelo es datos, no config runtime.
Consecuencias
Positivas
- Cambio de modelo = 1 operación: editar 1 env var en Dokploy panel + redeploy (~2 min). Tiempo de rollback estimado: 90-120 s.
- Drift imposible por construcción: si no hay model strings en el código, no pueden divergir.
- Comportamiento PROD invariante: los defaults son idénticos a los literales anteriores. Cero acción manual post-merge.
- Regla 8 ahora es estructural, no documental.
Negativas / Trade-offs
- Un
settings.NONEXISTENT_ATTRen callsite falla en runtime (antes:os.environ.getcon default local fallaba silenciosamente). Mitigado: smoke check en CI + healthcheck Dokploy. - El script
bib_ingest.pyqueda como excepción explícita y documentada — requiere vigilancia para no crear más excepciones.
Procedimiento de cambio de modelo (resumen ejecutivo)
El procedimiento completo vive en context/AI_CONFIG.md §3. Resumen:
- Validar Regla 8: el modelo nuevo cumple whitelist (Google AI Studio Paid Tier o Anthropic, GDPR EU, sin free tier). Si no → parar.
- Probar en local con
.envlocal → validar CNS + Tutor + MIB. - Dokploy STAGE → editar env var → Redeploy → smoke 24h.
- Dokploy PROD → editar env var → Redeploy → monitorizar 24h (latency
ai_call_duration_ms+ logsmonitoring.ai.*). - Rollback: revertir env var en Dokploy → Redeploy. Sin cambios de código.
- Cuando el nuevo modelo lleva ≥30 días en PROD estable: bump del default en
settings/base.py+ actualizarAI_CONFIG.md+ Regla 8 + crear nuevo ADR wiki.
Whitelist providers (Regla 8)
| Provider | Estado | Nota |
|---|---|---|
Google AI Studio — Gemma 4 (google-genai SDK) | ✅ Default canónico | Paid Tier, billing EU |
| Anthropic Claude (Haiku 4.5 / Sonnet 4.6) | ✅ Alternativo | Paid, EU residency |
| Vertex AI directo | ⏸️ Postpuesto s50 | Investigación residencia datos EU pendiente |
| OpenRouter | ❌ Retirado s55 | No reintroducir |
| Gemini Flash/Pro/Lite | ❌ Excluido | Regla 8 monoproveedor + memoria feedback |
| OpenAI GPT-* | ❌ Excluido | Mismo motivo |
| Free tiers (cualquier proveedor) | ❌ Excluido | SaaS comercial |
| Ollama / self-hosted | ⏸️ Solo dev local | AUTOPLAN_PROVIDER=ollama experimentación · PROD self-host descartado s53 |
Historial de sesiones relevante
| Sesión | Fecha | Hito |
|---|---|---|
| s49 | 03-05-2026 | Migración a google-genai SDK directo, sin OpenRouter intermediario |
| s53 | 08-05-2026 | Self-host inferencia (pve-epyc-02) dado de baja. 100% AI Studio Paid Tier |
| s55 | 11-05-2026 | Cleanup OpenRouter del repo (-1266 LOC). Stack consolidada |
| s58 | 12-05-2026 | translate-wiki.mjs + CF Workers functions/api/wiki/translate.ts migrados a AI Studio directo |
| s61 | 14-05-2026 | Este ADR — 9 callsites runtime centralizados en settings |
Lección sistémica
La política “modelo único” (Regla 8) no se sostiene con disciplina humana cuando el código tiene N callsites con literales. La aplicación tiene que ser estructural: si no hay literales que cambiar, no hay drift posible.
Aplicable a futuros tokens centralizables: URLs de servicio, claves de caché, timeouts AI — todos candidatos al mismo patrón.
Véase también
- [[concept—ai—regla-8-monoproveedor]]
- [[runbook—infra—cambio-modelo-ai]]
- [[feature—monitoring—cns-insight]]
- [[feature—blueprints—auto-plan]]
- [[feature—biblioteca—bib-ingest]]