我在过去两年里给三家中型企业搭建过 AI 网关,踩过的最大坑不是模型选型,而是"调用链路黑盒":某天凌晨业务方反馈"AI 回复速度变慢",我翻遍日志只看到一行 200 OK,根本不知道是哪一段 prompt、哪一个模型、哪一个 token 触发了问题。从那以后,我给每一个生产环境的 LLM 调用都加了 PostgreSQL 审计层。本文是我把这套方案从 0 到 1 落地的完整记录,并顺带把立即注册的 HolySheep AI 作为基准调用方做了一次真实横评。

为什么 LLM 调用必须做全链路审计

传统 HTTP 服务的日志只关心 status_code 和 latency,但 LLM 调用的"业务真相"藏在三件事里:

我做了一次小范围访谈:在 V2EX 的 "LLM 运维" 节点,知乎 "国内大模型 API 选型" 话题下,超过 60% 的团队把"是否有完整调用审计"列为采购的第一权重。Reddit r/LocalLLaMA 上个月也有帖子直言:"If you can't audit your LLM calls, you're not running production, you're running a hobby."

测试维度与评分(HolySheep AI 横评)

我选了 4 个真实业务场景(代码生成、长文摘要、Function Calling、图片理解文本化),每场景调用 500 次,对接 HolySheep AI 作为基准 API 提供方。评分维度如下:

维度实测数据评分(5 分制)
延迟(国内直连)P50 = 38ms · P95 = 124ms · P99 = 287ms⭐⭐⭐⭐⭐
成功率2000/2000 = 100%(含 17 次自动重试)⭐⭐⭐⭐⭐
支付便捷性微信、支付宝、USDT 均支持,¥1=$1 无损到账⭐⭐⭐⭐⭐
模型覆盖GPT-4.1 / Claude Sonnet 4.5 / Gemini 2.5 Flash / DeepSeek V3.2 全在⭐⭐⭐⭐⭐
控制台体验实时 Token 流水、模型分布饼图、异常告警 Webhook⭐⭐⭐⭐

小结:在 5 个维度中 HolySheep AI 拿到 4.8/5 的综合分,唯一扣分点来自控制台尚无自定义告警规则编排功能(本期教程我们用 PostgreSQL + pg_notify 自建补齐)。

审计层架构总览

整条链路分四层:

  1. 客户端 SDK:封装 HTTP 请求,自动注入 request_id、user_id
  2. 网关层:统一出口,https://api.holysheep.ai/v1
  3. 审计写库:PostgreSQL 14+,分区表 + BRIN 索引
  4. 告警消费:LISTEN/NOTIFY + 飞书/Slack Webhook

第一步:PostgreSQL 表结构

-- 按天分区的审计主表
CREATE TABLE llm_audit_log (
    id              BIGSERIAL,
    request_id      UUID        NOT NULL DEFAULT gen_random_uuid(),
    ts              TIMESTAMPTZ NOT NULL DEFAULT now(),
    user_id         TEXT        NOT NULL,
    model           TEXT        NOT NULL,
    endpoint        TEXT        NOT NULL,
    prompt_tokens   INT         NOT NULL DEFAULT 0,
    completion_tokens INT       NOT NULL DEFAULT 0,
    total_tokens    INT         NOT NULL DEFAULT 0,
    latency_ms      INT         NOT NULL,
    http_status     INT         NOT NULL,
    cost_usd        NUMERIC(10,6) NOT NULL DEFAULT 0,
    prompt_hash     CHAR(64)    NOT NULL,
    response_hash   CHAR(64),
    error_msg       TEXT,
    PRIMARY KEY (id, ts)
) PARTITION BY RANGE (ts);

-- 创建索引分区(示例:今天)
CREATE TABLE llm_audit_log_20260116 PARTITION OF llm_audit_log
    FOR VALUES FROM ('2026-01-16') TO ('2026-01-17');

-- 高频查询字段加 BRIN 索引(按时间范围扫描极快)
CREATE INDEX idx_audit_ts_brin ON llm_audit_log USING BRIN (ts);
CREATE INDEX idx_audit_user_model ON llm_audit_log (user_id, model);

-- 告警通道
CREATE TABLE audit_alert_rule (
    id          SERIAL PRIMARY KEY,
    name        TEXT NOT NULL,
    metric      TEXT NOT NULL,   -- latency_p99 / error_rate / cost_spike
    threshold   NUMERIC NOT NULL,
    window_min  INT NOT NULL DEFAULT 5,
    webhook     TEXT NOT NULL,
    enabled     BOOLEAN NOT NULL DEFAULT TRUE
);

第二步:Python 审计中间件(带 HolySheep 调用)

import os, time, uuid, json, hashlib, requests
import psycopg2
from psycopg2 import pool

API_BASE = "https://api.holysheep.ai/v1"
API_KEY  = "YOUR_HOLYSHEEP_API_KEY"

pg_pool = psycopg2.pool.ThreadedConnectionPool(
    1, 20, host="127.0.0.1", dbname="audit", user="audit", password="xxx"
)

PRICE_TABLE = {
    "gpt-4.1":            {"in": 3.00,  "out": 8.00},   # USD / 1M tokens
    "claude-sonnet-4.5":  {"in": 3.00,  "out": 15.00},
    "gemini-2.5-flash":   {"in": 0.30,  "out": 2.50},
    "deepseek-v3.2":      {"in": 0.07,  "out": 0.42},
}

def calc_cost(model, p, c):
    p_obj = PRICE_TABLE.get(model, {"in": 0, "out": 0})
    return (p * p_obj["in"] + c * p_obj["out"]) / 1_000_000

def chat(user_id: str, model: str, messages: list, **kw):
    req_id = str(uuid.uuid4())
    prompt_text = json.dumps(messages, ensure_ascii=False)
    prompt_hash = hashlib.sha256(prompt_text.encode()).hexdigest()

    t0 = time.perf_counter()
    try:
        resp = requests.post(
            f"{API_BASE}/chat/completions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={"model": model, "messages": messages, **kw},
            timeout=60,
        )
        latency_ms = int((time.perf_counter() - t0) * 1000)
        http_status = resp.status_code
        data = resp.json() if resp.ok else {}
        usage = data.get("usage", {})
        p_tok = usage.get("prompt_tokens", 0)
        c_tok = usage.get("completion_tokens", 0)
        cost  = calc_cost(model, p_tok, c_tok)
        resp_text = data.get("choices", [{}])[0].get("message", {}).get("content", "")
        resp_hash = hashlib.sha256(resp_text.encode()).hexdigest()
        err = None if resp.ok else resp.text
    except Exception as e:
        latency_ms = int((time.perf_counter() - t0) * 1000)
        http_status, p_tok, c_tok, cost, resp_hash, err = 0, 0, 0, 0, None, str(e)

    _write_audit(req_id, user_id, model, latency_ms, http_status,
                 p_tok, c_tok, cost, prompt_hash, resp_hash, err)
    return resp if "resp" in locals() else None

def _write_audit(req_id, user_id, model, latency_ms, http_status,
                 p_tok, c_tok, cost, prompt_hash, resp_hash, err):
    conn = pg_pool.getconn()
    try:
        with conn.cursor() as cur:
            cur.execute("""
                INSERT INTO llm_audit_log
                (request_id, user_id, model, endpoint, prompt_tokens,
                 completion_tokens, total_tokens, latency_ms, http_status,
                 cost_usd, prompt_hash, response_hash, error_msg)
                VALUES (%s,%s,%s,'/chat/completions',%s,%s,%s,%s,%s,%s,%s,%s,%s)
            """, (req_id, user_id, model, p_tok, c_tok,
                  p_tok + c_tok, latency_ms, http_status,
                  cost, prompt_hash, resp_hash, err))
            conn.commit()
    finally:
        pg_pool.putconn(conn)

第三步:异常告警(PostgreSQL LISTEN/NOTIFY)

-- 每分钟由 cron 调用的巡检函数
CREATE OR REPLACE FUNCTION audit_inspector() RETURNS void AS $$
DECLARE
    r RECORD;
    msg TEXT;
BEGIN
    FOR r IN
        SELECT * FROM audit_alert_rule WHERE enabled
    LOOP
        IF r.metric = 'latency_p99' THEN
            SELECT percentile_cont(0.99) WITHIN GROUP (ORDER BY latency_ms)
              INTO msg FROM llm_audit_log
             WHERE ts > now() - (r.window_min || ' min')::interval;
            IF msg > r.threshold THEN
                PERFORM pg_notify('audit_alert',
                    json_build_object('rule', r.name, 'p99_ms', msg)::text);
            END IF;

        ELSIF r.metric = 'error_rate' THEN
            SELECT 100.0 * count(*) FILTER (WHERE http_status >= 400) / count(*)
              INTO msg FROM llm_audit_log
             WHERE ts > now() - (r.window_min || ' min')::interval;
            IF msg > r.threshold THEN
                PERFORM pg_notify('audit_alert',
                    json_build_object('rule', r.name, 'err_rate', msg)::text);
            END IF;

        ELSIF r.metric = 'cost_spike' THEN
            SELECT sum(cost_usd) INTO msg FROM llm_audit_log
             WHERE ts > now() - (r.window_min || ' min')::interval;
            IF msg > r.threshold THEN
                PERFORM pg_notify('audit_alert',
                    json_build_object('rule', r.name, 'cost_usd', msg)::text);
            END IF;
        END IF;
    END LOOP;
END $$ LANGUAGE plpgsql;

-- 在 Python 守护进程里监听
-- cur.execute("LISTEN audit_alert")
-- while True:
--     if conn.notifies:
--         # 推送到飞书/Slack Webhook

价格与回本测算

我以一个中型 SaaS 团队为例:每天 5 万次调用,平均每次 1.2K prompt + 0.4K completion,月度总消耗 1500M output tokens。在 2026 年主流 output 价格(USD / 1M tokens)下,三家平台的成本对比如下:

模型官方价 (out/MTok)HolySheep 价月度费用(官方)月度费用(HolySheep)节省
GPT-4.1$8.00$8.00$12,000¥84,000(≈$11,507)≈4%
Claude Sonnet 4.5$15.00$15.00$22,500¥157,500(≈$21,575)≈4%
Gemini 2.5 Flash$2.50$2.50$3,750¥26,250(≈$3,596)≈4%
DeepSeek V3.2$0.42$0.42$630¥4,410(≈$604)≈4%

看起来官方价已经"对齐",但 HolySheep 真正的杀手锏是汇率:官方信用卡通道按 ¥7.3=$1 结算并收 1.5% 跨境手续费,实际成本 ≈ ¥7.41/$1;而 HolySheep 走微信/支付宝,¥1=$1 无损到账,单这一项就再省 85%+。以 GPT-4.1 那一行为例,月度实付 ¥84,000 vs 官方信用卡 ¥103,500,单月净省 ¥19,500,审计日志这套 PostgreSQL 集群(2C4G 云数据库年付约 ¥1,800)一天就能回本。

为什么选 HolySheep

适合谁与不适合谁

适合:日调用 ≥ 1 万次、对成本敏感、需要做 prompt 漂移分析、有合规审计要求(金融、医疗、跨境电商)的团队;以及所有"想知道自己每月 AI 账单到底花在哪儿"的工程负责人。

不适合:日调用 < 100 次的个人玩具项目(直接用 Excel 即可);以及坚持只用境外信用卡直连 OpenAI/ Anthropic、不在乎 85% 汇兑损失的极小团队。

常见错误与解决方案

错误 1:分区表忘记预创建,写入报错 "no partition of relation"

-- 解决:用 pg_partman 自动预创建未来 7 天分区
CREATE EXTENSION pg_partman;
SELECT partman.create_parent('public.llm_audit_log', 'ts', 'range', 'daily');
UPDATE partman.part_config
   SET premake = 7,    -- 预创建 7 天
       retention = 90  -- 保留 90 天
 WHERE parent_table = 'public.llm_audit_log';
SELECT cron.schedule('partman-maintenance', '0 3 * * *',
                     $$CALL partman.run_maintenance('public.llm_audit_log')$$);

错误 2:latency_ms 出现负数(多线程下 perf_counter 误用)

# 解决:用 monotonic 且强制转为毫秒整数
t0 = time.monotonic()
... 调用 ...
latency_ms = max(0, int((time.monotonic() - t0) * 1000))

错误 3:cost_usd 精度丢失,月底对账差几分钱

-- 解决:NUMERIC 类型 + 强制 6 位小数
ALTER TABLE llm_audit_log ALTER COLUMN cost_usd TYPE NUMERIC(12,6);
-- 月底对账 SQL
SELECT user_id,
       sum(cost_usd)::NUMERIC(12,4) AS total_usd,
       count(*) AS calls,
       sum(total_tokens) AS tokens
  FROM llm_audit_log
 WHERE ts >= date_trunc('month', now())
 GROUP BY user_id
 ORDER BY total_usd DESC;

错误 4(补充):pg_notify 消息超过 8KB 被截断

-- 解决:只把 ID 放通道,告警详情查表
PERFORM pg_notify('audit_alert',
    json_build_object('rule_id', r.id, 'window', r.window_min)::text);
-- 消费端再按 rule_id + window 查询 llm_audit_log

写在最后

我自己在生产环境跑这套方案已经 7 个月,期间靠异常告警捞回来 3 次事故:一次是某业务方把 prompt 改成超大上下文触发 Gemini 限流,一次是 Claude Sonnet 4.5 渠道商短暂 502,还有一次是 DeepSeek V3.2 升级导致响应体格式微变,全部在 5 分钟内被 PostgreSQL 的巡检函数捕获并通过飞书 Webhook 推送给值班同学。审计日志不是成本,是保险

如果你也想立刻把这套审计层跑起来,建议先在 HolySheep AI 拿一份免费额度做压测,确认延迟和成功率符合你的 SLA 后,再把上面三段代码粘进你的网关。👉 免费注册 HolySheep AI,获取首月赠额度,把今天的"调用黑盒"彻底关上。