server/migrations/0007_ai_config.sql
vegard 832ea7117a AI-administrasjon: modellstyring, tokenregnskap, admin-panel
- Migrasjon 0007: ai_model_aliases, ai_model_providers, ai_job_routing, ai_usage_log
- Worker: token-logging fra AI Gateway-respons til ai_usage_log
- Config-generering: POST /api/admin/ai/generate-config bygger config.yaml fra PG
- Admin-panel /admin/ai: aliaser, leverandører, jobbruting, tokenforbruk
- CRUD API for aliaser, providers og routing
- Workspace-forbruk API: GET /api/ai/usage?days=30

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 03:03:12 +01:00

78 lines
3.8 KiB
PL/PgSQL

-- 0007_ai_config.sql
-- AI-administrasjon: modellaliaser, leverandører, jobbruting og tokenlogging.
-- PG som source of truth for LiteLLM config-generering.
BEGIN;
-- === Modellaliaser (globale, ikke per workspace) ===
CREATE TABLE ai_model_aliases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
alias TEXT NOT NULL UNIQUE, -- f.eks. "sidelinja/rutine"
description TEXT, -- kort beskrivelse av aliaset
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- === Leverandør-modeller per alias med prioritet ===
CREATE TABLE ai_model_providers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
alias_id UUID NOT NULL REFERENCES ai_model_aliases(id) ON DELETE CASCADE,
priority INT NOT NULL DEFAULT 1, -- lavere = høyere prioritet i LiteLLM
litellm_model TEXT NOT NULL, -- f.eks. "gemini/gemini-2.5-flash-lite"
api_key_env TEXT NOT NULL, -- f.eks. "GEMINI_API_KEY"
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (alias_id, priority)
);
-- === Jobbtype → alias mapping ===
CREATE TABLE ai_job_routing (
job_type TEXT PRIMARY KEY, -- f.eks. "ai_text_process"
alias_id UUID NOT NULL REFERENCES ai_model_aliases(id) ON DELETE RESTRICT,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- === Per-kall token-logging (workspace-scopet) ===
CREATE TABLE ai_usage_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
job_id UUID REFERENCES job_queue(id) ON DELETE SET NULL,
job_type TEXT NOT NULL,
model_alias TEXT NOT NULL, -- alias brukt (snapshot)
model_actual TEXT, -- faktisk modell fra respons
prompt_tokens INT NOT NULL DEFAULT 0,
completion_tokens INT NOT NULL DEFAULT 0,
total_tokens INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_ai_usage_log_workspace ON ai_usage_log (workspace_id, created_at DESC);
CREATE INDEX idx_ai_usage_log_alias ON ai_usage_log (model_alias, created_at DESC);
-- === Seed-data: matcher nåværende config.yaml ===
-- Aliaser
INSERT INTO ai_model_aliases (alias, description) VALUES
('sidelinja/rutine', 'Billig, høyt volum — tekstrensing, faktauthenting, oversettelse'),
('sidelinja/resonering', 'Presis, lav volum — kompleks analyse, research');
-- Leverandører for sidelinja/rutine
INSERT INTO ai_model_providers (alias_id, priority, litellm_model, api_key_env) VALUES
((SELECT id FROM ai_model_aliases WHERE alias = 'sidelinja/rutine'), 1, 'gemini/gemini-2.5-flash-lite', 'GEMINI_API_KEY'),
((SELECT id FROM ai_model_aliases WHERE alias = 'sidelinja/rutine'), 2, 'gemini/gemini-2.5-flash', 'GEMINI_API_KEY'),
((SELECT id FROM ai_model_aliases WHERE alias = 'sidelinja/rutine'), 3, 'openrouter/google/gemini-2.5-flash-preview', 'OPENROUTER_API_KEY');
-- Leverandører for sidelinja/resonering
INSERT INTO ai_model_providers (alias_id, priority, litellm_model, api_key_env) VALUES
((SELECT id FROM ai_model_aliases WHERE alias = 'sidelinja/resonering'), 1, 'openrouter/anthropic/claude-sonnet-4', 'OPENROUTER_API_KEY'),
((SELECT id FROM ai_model_aliases WHERE alias = 'sidelinja/resonering'), 2, 'gemini/gemini-2.5-flash', 'GEMINI_API_KEY');
-- Jobbruting
INSERT INTO ai_job_routing (job_type, alias_id, description) VALUES
('ai_text_process', (SELECT id FROM ai_model_aliases WHERE alias = 'sidelinja/rutine'), 'Tekstrensing og AI-behandling via ✨-knappen');
COMMIT;