Kosten Endpunkt
This commit is contained in:
143
Scripts/KI-Kostenverfolgung.sql
Normal file
143
Scripts/KI-Kostenverfolgung.sql
Normal file
@@ -0,0 +1,143 @@
|
||||
-- =============================================================================
|
||||
-- KI-Kostenverfolgung für Dokumenten-Check
|
||||
-- Tabelle: dc_ai_cost_log
|
||||
-- Package: DC_COSTS_PKG
|
||||
-- =============================================================================
|
||||
-- Deployment:
|
||||
-- sqlplus user/pass@db @"Scripts/KI-Kostenverfolgung.sql"
|
||||
--
|
||||
-- Skript ist idempotent (DROP IF EXISTS vor jedem CREATE).
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- Tabelle: dc_ai_cost_log
|
||||
-- =============================================================================
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE 'DROP TABLE dc_ai_cost_log CASCADE CONSTRAINTS';
|
||||
EXCEPTION WHEN OTHERS THEN NULL;
|
||||
END;
|
||||
/
|
||||
|
||||
CREATE TABLE dc_ai_cost_log (
|
||||
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
called_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
provider VARCHAR2(30) NOT NULL, -- mistral | ollama
|
||||
model_name VARCHAR2(100) NOT NULL,
|
||||
operation VARCHAR2(30) NOT NULL, -- TRANSLATE | EVALUATE | OCR | CATALOG
|
||||
project_id NUMBER,
|
||||
catalog_id NUMBER,
|
||||
prompt_tokens NUMBER DEFAULT 0 NOT NULL,
|
||||
completion_tokens NUMBER DEFAULT 0 NOT NULL,
|
||||
total_tokens NUMBER DEFAULT 0 NOT NULL,
|
||||
cost_eur NUMBER(14, 8) DEFAULT 0 NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
created_by VARCHAR2(200) DEFAULT 'SYSTEM' NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE dc_ai_cost_log IS 'Protokoll aller KI-API-Aufrufe mit Token-Verbrauch und Kosten';
|
||||
COMMENT ON COLUMN dc_ai_cost_log.operation IS 'TRANSLATE, EVALUATE, OCR, CATALOG';
|
||||
COMMENT ON COLUMN dc_ai_cost_log.cost_eur IS 'Berechnete Kosten in EUR basierend auf konfigurierten Preisen';
|
||||
|
||||
CREATE INDEX idx_ai_cost_called_at ON dc_ai_cost_log (called_at);
|
||||
CREATE INDEX idx_ai_cost_project_id ON dc_ai_cost_log (project_id);
|
||||
CREATE INDEX idx_ai_cost_catalog_id ON dc_ai_cost_log (catalog_id);
|
||||
CREATE INDEX idx_ai_cost_ym ON dc_ai_cost_log (TO_CHAR(called_at, 'YYYY-MM'));
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- PL/SQL Package: DC_COSTS_PKG
|
||||
-- =============================================================================
|
||||
CREATE OR REPLACE PACKAGE DC_COSTS_PKG AS
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- Gesamtkosten + Kosten des laufenden Monats
|
||||
-- -------------------------------------------------------------------------
|
||||
PROCEDURE get_summary(
|
||||
p_total_eur OUT NUMBER,
|
||||
p_total_tokens OUT NUMBER,
|
||||
p_total_calls OUT NUMBER,
|
||||
p_month_eur OUT NUMBER,
|
||||
p_month_tokens OUT NUMBER,
|
||||
p_month_calls OUT NUMBER
|
||||
);
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- Kosten pro Monat + Modell (für APEX-Reports / direkte Abfragen)
|
||||
-- Gibt einen REF CURSOR zurück (Spalten: month, provider, model_name,
|
||||
-- operation, call_count, prompt_tokens, completion_tokens, total_tokens, cost_eur)
|
||||
-- -------------------------------------------------------------------------
|
||||
FUNCTION get_monthly_detail RETURN SYS_REFCURSOR;
|
||||
|
||||
END DC_COSTS_PKG;
|
||||
/
|
||||
|
||||
CREATE OR REPLACE PACKAGE BODY DC_COSTS_PKG AS
|
||||
|
||||
PROCEDURE get_summary(
|
||||
p_total_eur OUT NUMBER,
|
||||
p_total_tokens OUT NUMBER,
|
||||
p_total_calls OUT NUMBER,
|
||||
p_month_eur OUT NUMBER,
|
||||
p_month_tokens OUT NUMBER,
|
||||
p_month_calls OUT NUMBER
|
||||
) IS
|
||||
BEGIN
|
||||
-- Gesamtkosten (all time)
|
||||
SELECT NVL(SUM(cost_eur), 0),
|
||||
NVL(SUM(total_tokens), 0),
|
||||
COUNT(*)
|
||||
INTO p_total_eur, p_total_tokens, p_total_calls
|
||||
FROM dc_ai_cost_log;
|
||||
|
||||
-- Laufender Monat
|
||||
SELECT NVL(SUM(cost_eur), 0),
|
||||
NVL(SUM(total_tokens), 0),
|
||||
COUNT(*)
|
||||
INTO p_month_eur, p_month_tokens, p_month_calls
|
||||
FROM dc_ai_cost_log
|
||||
WHERE called_at >= TRUNC(SYSDATE, 'MM');
|
||||
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
p_total_eur := 0; p_total_tokens := 0; p_total_calls := 0;
|
||||
p_month_eur := 0; p_month_tokens := 0; p_month_calls := 0;
|
||||
END get_summary;
|
||||
|
||||
|
||||
FUNCTION get_monthly_detail RETURN SYS_REFCURSOR IS
|
||||
v_cur SYS_REFCURSOR;
|
||||
BEGIN
|
||||
OPEN v_cur FOR
|
||||
SELECT
|
||||
TO_CHAR(called_at, 'YYYY-MM') AS month,
|
||||
provider,
|
||||
model_name,
|
||||
operation,
|
||||
COUNT(*) AS call_count,
|
||||
SUM(prompt_tokens) AS prompt_tokens,
|
||||
SUM(completion_tokens) AS completion_tokens,
|
||||
SUM(total_tokens) AS total_tokens,
|
||||
ROUND(SUM(cost_eur), 6) AS cost_eur
|
||||
FROM dc_ai_cost_log
|
||||
WHERE called_at >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -11)
|
||||
GROUP BY TO_CHAR(called_at, 'YYYY-MM'), provider, model_name, operation
|
||||
ORDER BY month DESC, cost_eur DESC;
|
||||
RETURN v_cur;
|
||||
END get_monthly_detail;
|
||||
|
||||
END DC_COSTS_PKG;
|
||||
/
|
||||
|
||||
-- =============================================================================
|
||||
-- Kurztest (optionale Verifikation nach Deployment)
|
||||
-- =============================================================================
|
||||
-- DECLARE
|
||||
-- v_te NUMBER; v_tt NUMBER; v_tc NUMBER;
|
||||
-- v_me NUMBER; v_mt NUMBER; v_mc NUMBER;
|
||||
-- BEGIN
|
||||
-- DC_COSTS_PKG.get_summary(v_te,v_tt,v_tc,v_me,v_mt,v_mc);
|
||||
-- DBMS_OUTPUT.PUT_LINE('Total EUR: ' || v_te || ', Calls: ' || v_tc);
|
||||
-- DBMS_OUTPUT.PUT_LINE('Month EUR: ' || v_me || ', Calls: ' || v_mc);
|
||||
-- END;
|
||||
-- /
|
||||
@@ -1026,6 +1026,136 @@ BEGIN
|
||||
]'
|
||||
);
|
||||
|
||||
-- ===========================================================================
|
||||
-- TEMPLATE 17: ai-costs (POST = Eintrag speichern, GET = Auswertung)
|
||||
-- ===========================================================================
|
||||
ORDS.DEFINE_TEMPLATE(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'ai-costs',
|
||||
p_priority => 0,
|
||||
p_comments => 'KI-Kostenprotokoll: POST speichert Aufruf, GET liefert Auswertung'
|
||||
);
|
||||
|
||||
-- 17a. POST /api/dc/ai-costs
|
||||
-- Body: {"provider":"mistral","model_name":"mistral-small-latest","operation":"TRANSLATE",
|
||||
-- "project_id":42,"catalog_id":null,
|
||||
-- "prompt_tokens":1234,"completion_tokens":567,"total_tokens":1801,"cost_eur":0.000157}
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'ai-costs',
|
||||
p_method => 'POST',
|
||||
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
|
||||
p_comments => '201 Created mit neuer cost_log id',
|
||||
p_source => q'[
|
||||
DECLARE
|
||||
v_body CLOB;
|
||||
v_new_id NUMBER;
|
||||
BEGIN
|
||||
v_body := :body_text;
|
||||
|
||||
INSERT INTO dc_ai_cost_log (
|
||||
provider, model_name, operation,
|
||||
project_id, catalog_id,
|
||||
prompt_tokens, completion_tokens, total_tokens,
|
||||
cost_eur
|
||||
) VALUES (
|
||||
JSON_VALUE(v_body, '$.provider'),
|
||||
JSON_VALUE(v_body, '$.model_name'),
|
||||
JSON_VALUE(v_body, '$.operation'),
|
||||
TO_NUMBER(JSON_VALUE(v_body, '$.project_id')),
|
||||
TO_NUMBER(JSON_VALUE(v_body, '$.catalog_id')),
|
||||
NVL(TO_NUMBER(JSON_VALUE(v_body, '$.prompt_tokens')), 0),
|
||||
NVL(TO_NUMBER(JSON_VALUE(v_body, '$.completion_tokens')), 0),
|
||||
NVL(TO_NUMBER(JSON_VALUE(v_body, '$.total_tokens')), 0),
|
||||
NVL(TO_NUMBER(JSON_VALUE(v_body, '$.cost_eur')), 0)
|
||||
)
|
||||
RETURNING id INTO v_new_id;
|
||||
|
||||
:status_code := 201;
|
||||
HTP.P('{"id":' || v_new_id || '}');
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
ROLLBACK;
|
||||
:status_code := 500;
|
||||
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
|
||||
END;
|
||||
]'
|
||||
);
|
||||
|
||||
-- 17b. GET /api/dc/ai-costs
|
||||
-- Liefert: Gesamtkosten, laufender Monat, Monats-Aufschlüsselung (12 Monate)
|
||||
ORDS.DEFINE_HANDLER(
|
||||
p_module_name => 'frigosped.dc',
|
||||
p_pattern => 'ai-costs',
|
||||
p_method => 'GET',
|
||||
p_source_type => ORDS.SOURCE_TYPE_PLSQL,
|
||||
p_comments => 'Kostenauswertung: total, current_month, monatliche Aufschluesselung via DC_COSTS_PKG',
|
||||
p_source => q'[
|
||||
DECLARE
|
||||
v_total_eur NUMBER;
|
||||
v_total_tokens NUMBER;
|
||||
v_total_calls NUMBER;
|
||||
v_month_eur NUMBER;
|
||||
v_month_tokens NUMBER;
|
||||
v_month_calls NUMBER;
|
||||
v_monthly_json CLOB;
|
||||
BEGIN
|
||||
DC_COSTS_PKG.get_summary(
|
||||
v_total_eur, v_total_tokens, v_total_calls,
|
||||
v_month_eur, v_month_tokens, v_month_calls
|
||||
);
|
||||
|
||||
SELECT JSON_ARRAYAGG(
|
||||
JSON_OBJECT(
|
||||
'month' VALUE month,
|
||||
'cost_eur' VALUE ROUND(cost_eur, 6),
|
||||
'prompt_tokens' VALUE prompt_tokens,
|
||||
'completion_tokens' VALUE completion_tokens,
|
||||
'total_tokens' VALUE total_tokens,
|
||||
'call_count' VALUE call_count
|
||||
)
|
||||
ORDER BY month DESC
|
||||
RETURNING CLOB
|
||||
)
|
||||
INTO v_monthly_json
|
||||
FROM (
|
||||
SELECT
|
||||
TO_CHAR(called_at, 'YYYY-MM') AS month,
|
||||
SUM(cost_eur) AS cost_eur,
|
||||
SUM(prompt_tokens) AS prompt_tokens,
|
||||
SUM(completion_tokens) AS completion_tokens,
|
||||
SUM(total_tokens) AS total_tokens,
|
||||
COUNT(*) AS call_count
|
||||
FROM dc_ai_cost_log
|
||||
WHERE called_at >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -11)
|
||||
GROUP BY TO_CHAR(called_at, 'YYYY-MM')
|
||||
);
|
||||
|
||||
:status_code := 200;
|
||||
HTP.P(
|
||||
'{"total":{'
|
||||
|| '"cost_eur":' || ROUND(NVL(v_total_eur, 0), 6)
|
||||
|| ',"total_tokens":'|| NVL(v_total_tokens, 0)
|
||||
|| ',"call_count":' || NVL(v_total_calls, 0)
|
||||
|| '},"current_month":{'
|
||||
|| '"month":"' || TO_CHAR(SYSDATE, 'YYYY-MM') || '"'
|
||||
|| ',"cost_eur":' || ROUND(NVL(v_month_eur, 0), 6)
|
||||
|| ',"total_tokens":'|| NVL(v_month_tokens, 0)
|
||||
|| ',"call_count":' || NVL(v_month_calls, 0)
|
||||
|| '},"monthly":' || NVL(v_monthly_json, '[]')
|
||||
|| '}'
|
||||
);
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
:status_code := 500;
|
||||
HTP.P('{"error":"' || REPLACE(SQLERRM, '"', '''') || '"}');
|
||||
END;
|
||||
]'
|
||||
);
|
||||
|
||||
|
||||
COMMIT;
|
||||
END;
|
||||
/
|
||||
|
||||
Reference in New Issue
Block a user