- 新增 example_llm_call(97)、example_run/example_run_receipt/example_quality_result(98) - 加固:全对象幂等(IF NOT EXISTS/OR REPLACE)、append-only 表加 TRUNCATE 防护、 运行终态闭合约束、token/成本非负与 model_match 一致性 CHECK(已 DROP 空表重 apply 验证) - db.py:新增 execparams 参数化写(%s 服务端绑定+语句白名单+stdin 大对象); query 改只读连接(拒 INSERT...RETURNING);tables 用 sql.Identifier 安全拼接;connect() 统一入口 - 表映射登记 97/98、96 标不启用(仅本轮改动,未含他处未提交内容)
68 lines
4.3 KiB
PL/PgSQL
68 lines
4.3 KiB
PL/PgSQL
-- example_llm_call:逐次模型调用明细账本(运行态设施,非业务实体)。
|
||
-- 设计拍板 2026-07-30(docs/2026-07-30-落库与看板设计.md §2.1,已吸收四路评审 + Codex 加固)。
|
||
-- 与 example_llm_quota(95,按 5 小时窗的 $/次 聚合水位)互补、不重叠:
|
||
-- 额度账本是按窗 ON CONFLICT += 的累加器(且非 MiniMax 成本记 0),回答"限速/预算";
|
||
-- 本表是逐次调用明细(含真实单次成本、用量、提示词哈希、raw 指针),回答"每次调了什么、花了多少"。
|
||
-- 刻意不带业务公共列(updater/update_time/deleted/tenant_id):运行态设施、append-only、无业务归属、无软删语义(仿 95)。
|
||
-- 只保留 creator/call_time 记录"何时由哪个调用方写入"(call_time 兼作创建时间)。
|
||
-- 哈希列按落库设计 §1 归一规则存裸 64 位小写 hex(剥 sha256: 前缀)。
|
||
-- append-only:禁止 UPDATE/DELETE/TRUNCATE,错误/重试产生新行。
|
||
-- 全部对象用 IF NOT EXISTS / OR REPLACE,重复 apply 幂等。
|
||
CREATE TABLE IF NOT EXISTS example_llm_call (
|
||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||
window_key VARCHAR(16), -- → example_llm_quota.window_key(软引用,可空:跨窗或无窗调用)
|
||
run_id VARCHAR(64), -- → example_run.run_id(软引用,可空:清洗/拆书批处理无 run,靠 caller 区分)
|
||
caller VARCHAR(64) NOT NULL DEFAULT '', -- 调用方(clean/parse-book/extractor/review-cards/migrate-upgrade…)
|
||
requested_model_id VARCHAR(64) NOT NULL, -- 请求的模型
|
||
actual_model_id VARCHAR(64) NOT NULL, -- 实际执行的模型(降级后可能不同)
|
||
model_match BOOLEAN NOT NULL DEFAULT TRUE, -- 请求 == 实际(CHECK 强制与两个模型 ID 一致)
|
||
in_tokens INTEGER NOT NULL DEFAULT 0, -- 输入 token
|
||
cached_tokens INTEGER NOT NULL DEFAULT 0, -- 命中缓存 token
|
||
out_tokens INTEGER NOT NULL DEFAULT 0, -- 输出 token
|
||
cost_usd NUMERIC(14,8) NOT NULL DEFAULT 0, -- 单次美元成本(本地估算,非网关权威——网关权威在 New-API)
|
||
stop_reason VARCHAR(32), -- 结束原因
|
||
duration_ms INTEGER, -- 耗时(毫秒)
|
||
prompt_sha256 CHAR(64), -- 提示词哈希(裸 64 hex;提示词全文落 example_raw_content)
|
||
raw_content_id BIGINT, -- → example_raw_content.id(完整 prompt/response,受访问控制)
|
||
call_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
creator VARCHAR(64) NOT NULL DEFAULT '',
|
||
CONSTRAINT chk_example_llm_call_prompt_sha256
|
||
CHECK (prompt_sha256 IS NULL OR prompt_sha256 ~ '^[0-9a-f]{64}$'),
|
||
CONSTRAINT chk_example_llm_call_model_match
|
||
CHECK (model_match = (requested_model_id = actual_model_id)),
|
||
CONSTRAINT chk_example_llm_call_non_negative
|
||
CHECK (in_tokens >= 0 AND cached_tokens >= 0 AND out_tokens >= 0
|
||
AND cost_usd >= 0 AND (duration_ms IS NULL OR duration_ms >= 0))
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_example_llm_call_window ON example_llm_call(window_key);
|
||
CREATE INDEX IF NOT EXISTS idx_example_llm_call_caller ON example_llm_call(caller, call_time);
|
||
CREATE INDEX IF NOT EXISTS idx_example_llm_call_run ON example_llm_call(run_id) WHERE run_id IS NOT NULL;
|
||
|
||
-- 共用:append-only 表禁 TRUNCATE(行级 UPDATE/DELETE 触发器拦不住 TRUNCATE,需语句级触发器)。
|
||
-- 97/98 各自 OR REPLACE 同一函数(体相同,幂等),任一文件可独立 apply。
|
||
CREATE OR REPLACE FUNCTION example_reject_truncate()
|
||
RETURNS TRIGGER
|
||
LANGUAGE plpgsql
|
||
AS $$
|
||
BEGIN
|
||
RAISE EXCEPTION '% 是 append-only 表,禁止 TRUNCATE', TG_TABLE_NAME;
|
||
END;
|
||
$$;
|
||
|
||
CREATE OR REPLACE FUNCTION reject_example_llm_call_mutation()
|
||
RETURNS TRIGGER
|
||
LANGUAGE plpgsql
|
||
AS $$
|
||
BEGIN
|
||
RAISE EXCEPTION 'example_llm_call 是 append-only 表,禁止 UPDATE/DELETE';
|
||
END;
|
||
$$;
|
||
|
||
CREATE OR REPLACE TRIGGER trg_example_llm_call_append_only
|
||
BEFORE UPDATE OR DELETE ON example_llm_call
|
||
FOR EACH ROW EXECUTE FUNCTION reject_example_llm_call_mutation();
|
||
|
||
CREATE OR REPLACE TRIGGER trg_example_llm_call_no_truncate
|
||
BEFORE TRUNCATE ON example_llm_call
|
||
FOR EACH STATEMENT EXECUTE FUNCTION example_reject_truncate();
|