技术教程 · 阅读约 10 分钟

30 行 Python,给你的 AI 接口加一个「降智检测器」

30 行 Python,给你的 AI 接口加一个「降智检测器」

很多人用 API 调模型的时候,从来不知道自己到底在和哪个版本说话。

OpenAI 官方早就承认过「模型漂移」这个现象——同一个模型标识符,不同时间返回的能力可能差异显著。最近 V2EX 上有人做了一个 AI 模型降智监测页面,引发了不少讨论。这件事本身挺有意思:你付了钱,调了接口,但你甚至不知道对面那个模型是不是在「摸鱼」。

这篇文章就教你从零搭一个自己的降智监测脚本,持续追踪模型的推理能力基线。


核心思路

降智检测本质上是「能力基准测试的持续化」。每隔一段时间,用同一套题目问模型,把结果存下来,跟历史对比。题目需要覆盖几个维度:

  • 逻辑推理:需要多步推导的数学题
  • 代码能力:让它写一段有明确边界条件的代码
  • 指令遵循:给出严格格式要求,看它能不能准确执行
  • 上下文理解:带有隐含前提的问题

每个维度都有一个「标准答案」或「评分规则」,跑完之后算出一个综合分,写进数据库,画出折线图。


完整实现

依赖极少,openai + sqlite3(标准库),直接跑。


import sqlite3

import json

import time

from datetime import datetime

from openai import OpenAI



# 改一行 base_url,国内直连,比官方便宜 60%+

client = OpenAI(

    api_key="your_api_key_here",

    base_url="https://api2everything.xyz/v1"

)



MODEL = "gpt-4o"  # 换成你要监测的模型



# ── 评测题库 ──────────────────────────────────────────

BENCHMARKS = [

    {

        "id": "logic_math",

        "prompt": "一个房间里有 3 盏灯,门外有 3 个开关,每个开关控制一盏灯。你只能进房间一次,如何判断哪个开关控制哪盏灯?请给出完整步骤。",

        "keywords": ["开一盏", "摸", "热", "温度"],  # 答案必须包含「先开一个等一会儿」的思路

        "max_tokens": 300,

    },

    {

        "id": "code_gen",

        "prompt": "用 Python 写一个函数 flatten(lst),将任意深度嵌套的列表展平。要求:不使用 itertools,处理空列表,不修改原列表。只输出函数体,不要解释。",

        "keywords": ["isinstance", "list", "yield", "recursive", "def flatten"],

        "max_tokens": 200,

    },

    {

        "id": "instruction_follow",

        "prompt": '请严格按照以下格式回复,不要有任何多余内容:\nRESULT: <把 "hello world" 反转后大写>',

        "keywords": ["RESULT:", "DLROW OLLEH"],

        "max_tokens": 50,

    },

    {

        "id": "context_implicit",

        "prompt": "我昨天买了一本书,今天又买了同样数量的两倍,现在一共有 9 本。问:我昨天买了几本?只回答数字。",

        "keywords": ["3"],

        "max_tokens": 20,

    },

]



# ── 数据库初始化 ──────────────────────────────────────

def init_db(db_path="benchmark.db"):

    conn = sqlite3.connect(db_path)

    conn.execute("""

        CREATE TABLE IF NOT EXISTS results (

            id        INTEGER PRIMARY KEY AUTOINCREMENT,

            ts        TEXT,

            model     TEXT,

            bench_id  TEXT,

            score     REAL,

            response  TEXT

        )

    """)

    conn.commit()

    return conn



# ── 单题评分 ──────────────────────────────────────────

def score_response(response: str, keywords: list[str]) -> float:

    """简单关键词命中率评分,生产环境可换成 LLM-as-judge"""

    hits = sum(1 for kw in keywords if kw.lower() in response.lower())

    return round(hits / len(keywords), 2)



# ── 执行一轮测试 ──────────────────────────────────────

def run_benchmark(conn: sqlite3.Connection):

    ts = datetime.utcnow().isoformat()

    scores = {}



    for bench in BENCHMARKS:

        try:

            resp = client.chat.completions.create(

                model=MODEL,

                messages=[{"role": "user", "content": bench["prompt"]}],

                max_tokens=bench["max_tokens"],

                temperature=0,  # 固定温度,结果才可比

            )

            text = resp.choices[0].message.content or ""

        except Exception as e:

            print(f"[ERROR] {bench['id']}: {e}")

            text = ""



        score = score_response(text, bench["keywords"])

        scores[bench["id"]] = score



        conn.execute(

            "INSERT INTO results (ts, model, bench_id, score, response) VALUES (?,?,?,?,?)",

            (ts, MODEL, bench["id"], score, text[:500]),

        )

        print(f"  {bench['id']:25s} score={score:.2f}  → {text[:60].strip()!r}")

        time.sleep(1)  # 避免触发限速



    conn.commit()

    avg = sum(scores.values()) / len(scores)

    print(f"\n[{ts}] 综合得分: {avg:.2f} / 1.00\n")

    return avg



# ── 趋势报告(最近 10 次)────────────────────────────

def print_trend(conn: sqlite3.Connection):

    rows = conn.execute("""

        SELECT ts, AVG(score) as avg_score

        FROM results

        WHERE model = ?

        GROUP BY ts

        ORDER BY ts DESC

        LIMIT 10

    """, (MODEL,)).fetchall()



    print("── 最近 10 次综合得分趋势 ──")

    for ts, avg in reversed(rows):

        bar = "█" * int(avg * 20)

        print(f"  {ts[:16]}  {bar:<20} {avg:.2f}")



# ── 主入口 ────────────────────────────────────────────

if __name__ == "__main__":

    conn = init_db()

    print(f"正在测试模型: {MODEL}\n")

    run_benchmark(conn)

    print_trend(conn)

    conn.close()


跑起来


pip install openai

python benchmark.py

第一次跑完大概是这样:


  logic_math                score=0.75  → '第一步,打开第一个开关,等待5分钟...'

  code_gen                  score=1.00  → 'def flatten(lst):\n    result = []\n    for...'

  instruction_follow        score=1.00  → 'RESULT: DLROW OLLEH'

  context_implicit          score=1.00  → '3'



[2025-07-16T08:32:11] 综合得分: 0.94 / 1.00

然后用 cron 每天跑一次,数据沉淀几周之后,折线图就有参考价值了。


进阶方向

把关键词评分换成 LLM-as-judge:让一个轻量模型(比如 gpt-4o-mini)来判断答案是否正确,准确率更高。题目里加一个 grader_prompt 字段,把模型回复传进去,返回 0/1。

多模型对比:把 MODEL 换成一个列表,同一轮题目全部跑一遍,横向比较 gpt-4o、claude-sonnet、deepseek-v3 的当前状态,发现哪个「今天状态好」。

接入告警:如果当日得分比近 7 天均值低 15% 以上,发一条钉钉/Telegram 通知。


关于 API 费用

这套脚本每天跑一次,一个模型消耗 token 大概在 1000 以内,费用极低。不过如果你要同时监测 5-10 个模型,或者加了更多测试题,成本就不容忽视了。

我自己在用无量 Api(api2everything.xyz),国内直连不需要梯子,支持 300+ 模型,定价比官方便宜 65% 左右。上面脚本里的 base_url 已经填好了,注册就送 ¥1 余额,拿来跑这个监测脚本完全够用。格式是标准 OpenAI 兼容的,所以代码零改动。


最后

「降智」这个话题本身就说明,用 AI API 做产品的话,把模型当黑盒是不够的,需要主动建立一套可观测性体系。今天这个脚本只是最简版,但架子搭起来之后,往上加东西很容易。

你在用 AI API 的过程中有没有遇到过明显的能力下降?欢迎评论区聊聊,或者直接把你的监测数据贴出来对比。


推广链接:无量 Api —— 国内直连 300+ 模型,比官方便宜 65%