feat(ai): migrate to Google Gemini 2.5 Flash-Lite, drop Mistral/Ollama

Replace the Mistral + Ollama AI stack with a single Google Gemini provider
backed by google.golang.org/genai. API key moves from env/Helm to the DB
(AES-256-GCM, key derived from JWT_SECRET via HKDF) so it can be rotated
via the admin UI without a pod restart.

New:
- pkg/crypto/secretbox — AES-256-GCM encrypt/decrypt for secrets at rest
- pkg/ai/gemini — GeminiProvider with grounding, structured output, usage
  recording, and hot-reload (Reinitialize swaps client under mutex)
- pkg/ai/usage — UsageRecorder interface + UsageEvent struct
- domain/settings/store — DB-backed settings (model, grounding toggle, key)
- domain/settings/usage — UsageRepo implementing UsageRecorder; ai_usage table
- migrations 000021 (system_settings) + 000022 (ai_usage)
- settings API: GET /ai, POST /ai/key, POST /ai/model, POST /ai/grounding,
  GET /ai/usage
- admin UI: 4-card settings page — provider status, model selector, grounding
  toggle with quota, usage rollups + recent-calls table

Removed:
- pkg/ai/ollama, mistral_provider, ratelimiter (+ tests)
- Helm AI_API_KEY, AI_PROVIDER, AI_MODEL_COMPLEX, AI_AGENT_DISCOVERY,
  AI_RATE_LIMIT_RPS env vars

Call sites set Grounded+CallType: research (true/"research"), enrich Pass B
(true/"enrich_b"), similarity (false/"similarity"). Integration test updated
to use a stub ai.Provider instead of a fake Ollama HTTP server.
This commit is contained in:
2026-04-25 09:54:49 +02:00
parent 80149de317
commit 3ddfd87408
40 changed files with 1392 additions and 897 deletions

View File

@@ -1,4 +1,4 @@
{{- if or .Values.ai.apiKey .Values.turnstile.secretKey .Values.discovery.token }}
{{- if or .Values.turnstile.secretKey .Values.discovery.token }}
apiVersion: v1
kind: Secret
metadata:
@@ -8,9 +8,6 @@ metadata:
{{- include "marktvogt-backend.labels" . | nindent 4 }}
type: Opaque
stringData:
{{- if .Values.ai.apiKey }}
AI_API_KEY: {{ .Values.ai.apiKey | quote }}
{{- end }}
{{- if .Values.turnstile.secretKey }}
TURNSTILE_SECRET_KEY: {{ .Values.turnstile.secretKey | quote }}
{{- end }}

View File

@@ -51,7 +51,7 @@ spec:
- secretRef:
name: {{ include "marktvogt-backend.fullname" . }}-smtp
{{- end }}
{{- if or .Values.ai.apiKey .Values.turnstile.secretKey .Values.discovery.token }}
{{- if or .Values.turnstile.secretKey .Values.discovery.token }}
# AI, Turnstile + Discovery credentials (Helm-managed, passed via CI)
- secretRef:
name: {{ include "marktvogt-backend.fullname" . }}-ci-secrets
@@ -97,10 +97,6 @@ spec:
secretKeyRef:
name: {{ include "marktvogt-backend.fullname" . }}-ci-secrets
key: DISCOVERY_TOKEN
- name: AI_AGENT_DISCOVERY
value: {{ .Values.ai.agentDiscovery | quote }}
- name: AI_RATE_LIMIT_RPS
value: {{ .Values.ai.rateLimitRps | default 1 | quote }}
- name: DISCOVERY_BATCH_SIZE
value: {{ .Values.discovery.batchSize | default 4 | quote }}
- name: DISCOVERY_FORWARD_MONTHS

View File

@@ -81,8 +81,6 @@ config:
SMTP_FROM: "noreply@marktvogt.de"
ADMIN_EMAIL: "contact@marktvogt.de"
FRONTEND_URL: "https://marktvogt.de"
AI_MODEL_COMPLEX: "mistral-large-latest"
# Name of the manually-created Secret containing:
# JWT_SECRET, SENTRY_DSN,
# OAUTH_{GOOGLE,APPLE,FACEBOOK,GITHUB}_{CLIENT_ID,CLIENT_SECRET}
@@ -95,12 +93,6 @@ smtp:
user: ""
password: ""
# AI research credentials — passed via Woodpecker secrets during deploy.
ai:
apiKey: ""
agentDiscovery: "" # set via CI secret in production
rateLimitRps: 1
# Discovery cron — token passed via CI secrets during deploy.
discovery:
enabled: true