muse-agent-example/db/ddl/97-example模型调用明细.sql
zizi bb1dcc1a58 框架(数据库): 落库 97/98 运行级账本四表并加固,db skill 增参数化通道与只读 query
- 新增 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 标不启用(仅本轮改动,未含他处未提交内容)
2026-07-31 00:30:54 +08:00

68 lines
4.3 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 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();