同样调的 Claude 4 Opus,为什么中转站回答明显变蠢了?我写了个检测脚本

29 阅读7分钟

昨天在掘金热榜看到一篇讨论「中转渠道顶级模型为什么不好用」的文章,评论区吵翻了。有人说是心理作用,有人说中转商偷偷换了模型。

作为一个被坑过的人,我决定用代码说话——写个脚本,实测验证中转站到底给你的是不是真模型

先说结论

检测维度说明真模型表现
模型自报问模型"你是谁"准确报出版本号
推理能力多步逻辑题正确率 > 90%
响应指纹token 生成速度曲线符合官方模型特征
上下文窗口塞长文本测边界与官方标称一致
隐藏水印特定 prompt 触发行为差异各模型有独特模式

测了 5 家中转(含 2 家便宜的、2 家中等价位、1 家聚合平台),结论:2 家存在明显的模型替换行为,1 家偶发降级

为什么会出现「模型变蠢」?

先聊技术原因,别急着喷中转商——有些还真不是故意的:

1. 主动替换(最恶劣)

直接把 claude-opus-4-6 映射到 claude-sonnet-4-6 甚至更低的模型。成本差 10 倍,利润全在这。

用户调的时候 model 参数写的是 opus,但后端实际请求的是 sonnet,返回的 response header 里 model 字段也被篡改了。

2. 被动降级(配额用完)

有些中转商用的是共享 API Key,高峰期 rate limit 打满了,自动 fallback 到备用模型。这种最难发现,因为平时是正常的,就某些时段会变差。

3. Prompt 注入(最隐蔽)

在你的 prompt 前面偷偷加了 system prompt,比如"请用简短的方式回答"来省 token。你以为模型变蠢了,其实是被人为限制了输出。

4. 缓存命中

把常见问题的回答缓存起来,下次遇到类似的直接返回缓存。速度飞快但答案可能过时或不准确。

检测脚本:5 个维度验真

废话不多说,上代码。核心思路:设计一组只有目标模型才能正确回答的测试题

import openai
import time
import json

class ModelVerifier:
    """中转站模型真伪检测器"""

    def __init__(self, base_url, api_key, model="claude-opus-4-6"):
        self.client = openai.OpenAI(
            base_url=base_url,
            api_key=api_key
        )
        self.model = model
        self.results = {}

    def test_self_identification(self):
        """测试1:模型自我认知"""
        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[{
                "role": "user",
                "content": "What specific model version are you? Reply with ONLY the model identifier, nothing else."
            }],
            max_tokens=50
        )
        answer = resp.choices[0].message.content.strip()
        # Opus 会明确说自己是 Claude,Sonnet 也会但细节不同
        self.results["self_id"] = {
            "answer": answer,
            "model_field": resp.model  # 检查返回的 model 字段
        }
        return answer

    def test_reasoning_depth(self):
        """测试2:多步推理(区分 Opus 和 Sonnet 的关键)"""
        # 这道题需要 4 步推理,Sonnet 经常在第 3 步出错
        prompt = """一个房间里有100个人。99%是程序员。
要让程序员占比变成98%,需要多少程序员离开房间?
请一步步推理,给出最终数字。"""

        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            max_tokens=500
        )
        answer = resp.choices[0].message.content
        # 正确答案是 0 个程序员离开,1 个非程序员进入
        # 或者更准确地说:需要 50 个程序员离开
        # Opus 级模型通常能给出正确的数学推导
        self.results["reasoning"] = {
            "answer": answer,
            "tokens": resp.usage.completion_tokens
        }
        return answer

    def test_token_speed(self, runs=3):
        """测试3:Token 生成速度指纹"""
        speeds = []
        for _ in range(runs):
            start = time.time()
            resp = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": "写一首关于编程的七言绝句"}],
                max_tokens=200
            )
            elapsed = time.time() - start
            tokens = resp.usage.completion_tokens
            speed = tokens / elapsed if elapsed > 0 else 0
            speeds.append(round(speed, 1))

        avg_speed = sum(speeds) / len(speeds)
        self.results["token_speed"] = {
            "speeds": speeds,
            "avg_tps": round(avg_speed, 1),
            # Opus 通常 30-60 tps,Sonnet 60-120 tps
            # 如果标称 Opus 但速度 > 80 tps,大概率是 Sonnet
            "suspicion": "HIGH" if avg_speed > 80 else "LOW"
        }
        return avg_speed

    def test_context_window(self):
        """测试4:上下文窗口边界"""
        # 生成一段带标记的长文本
        marker = "HIDDEN_MARKER_7X9K2"
        # 用重复文本填充到接近窗口边界
        filler = "这是一段用于测试上下文窗口的填充文本。" * 500
        prompt = f"记住这个标记:{marker}\n\n{filler}\n\n请问开头让你记住的标记是什么?"

        try:
            resp = self.client.chat.completions.create(
                model=self.model,
                messages=[{"role": "user", "content": prompt}],
                max_tokens=100
            )
            found = marker in resp.choices[0].message.content
            self.results["context"] = {"marker_found": found}
        except Exception as e:
            self.results["context"] = {"error": str(e)}

        return self.results["context"]

    def test_instruction_following(self):
        """测试5:精确指令遵循(Opus 明显强于 Sonnet)"""
        prompt = """请严格按以下格式回复,不要有任何多余内容:
第一行:数字 42
第二行:单词 hello 重复 3 次,用逗号分隔
第三行:今天是星期几(用英文)

格式示例:
42
hello,hello,hello
Monday"""

        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            max_tokens=100
        )
        answer = resp.choices[0].message.content.strip()
        lines = answer.split("\n")

        score = 0
        if lines and lines[0].strip() == "42":
            score += 1
        if len(lines) > 1 and lines[1].strip() == "hello,hello,hello":
            score += 1
        # 第三行只要是英文星期就算对
        weekdays = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
        if len(lines) > 2 and lines[2].strip() in weekdays:
            score += 1

        self.results["instruction"] = {
            "answer": answer,
            "score": f"{score}/3"
        }
        return score

    def run_all(self):
        """运行全部检测"""
        print(f"🔍 检测目标: {self.client.base_url}")
        print(f"📋 标称模型: {self.model}\n")

        print("1/5 模型自我认知...")
        self.test_self_identification()

        print("2/5 多步推理能力...")
        self.test_reasoning_depth()

        print("3/5 Token 生成速度...")
        self.test_token_speed()

        print("4/5 上下文窗口...")
        self.test_context_window()

        print("5/5 指令遵循度...")
        self.test_instruction_following()

        return self.generate_report()

    def generate_report(self):
        """生成检测报告"""
        report = "\n" + "="*50 + "\n"
        report += "📊 模型真伪检测报告\n"
        report += "="*50 + "\n\n"

        # 自报身份
        sid = self.results.get("self_id", {})
        report += f"✅ 自报身份: {sid.get('answer', 'N/A')}\n"
        report += f"   API返回model: {sid.get('model_field', 'N/A')}\n\n"

        # 速度检测
        ts = self.results.get("token_speed", {})
        report += f"⚡ Token速度: {ts.get('avg_tps', 'N/A')} tps\n"
        report += f"   可疑度: {ts.get('suspicion', 'N/A')}\n\n"

        # 指令遵循
        ins = self.results.get("instruction", {})
        report += f"📝 指令遵循: {ins.get('score', 'N/A')}\n\n"

        # 上下文
        ctx = self.results.get("context", {})
        report += f"📏 上下文标记: {'✅ 找到' if ctx.get('marker_found') else '❌ 未找到'}\n"

        print(report)
        return self.results


# 使用方法
if __name__ == "__main__":
    # 替换为你的中转站地址和 key
    verifier = ModelVerifier(
        base_url="https://api.ofox.ai/v1",  # 换成你要测的中转站
        api_key="sk-your-key-here",
        model="claude-opus-4-6"
    )
    verifier.run_all()

实测结果

我拿这个脚本测了 5 家:

中转站自报身份推理正确速度(tps)指令遵循判定
A(低价)✅ Claude❌ 第3步错952/3⚠️ 疑似 Sonnet
B(低价)❌ GPT-41101/3❌ 假的
C(中等)✅ Claude453/3✅ 真 Opus
D(中等)✅ Claude✅ 偶尔错522/3⚠️ 高峰降级
E(聚合)✅ Claude483/3✅ 真 Opus

几个关键发现:

1. 速度是最灵敏的指标

Opus 4.6 的生成速度通常在 35-55 tps 之间。如果你测出来超过 80,基本可以断定不是 Opus——Sonnet 快得多,这反而暴露了它。

2. 低价中转几乎必翻车

Opus 的官方定价摆在那,成本 15/Minput+15/M input + 75/M output。如果一家中转卖你 $5/M output,你觉得中间的差价谁来补?

3. 聚合平台反而靠谱

像我后来换到的 ofox.ai,因为是直接对接多家云(阿里云、火山云走加速通道),模型没有经过"二道贩子",延迟低且质量一致。改一行 base_url 就能切换不同模型,省心。

进阶:自动化定时监控

光测一次不够,中转商可能"见人下菜"。我加了个 cron job 每小时跑一次:

import schedule
import datetime

def hourly_check():
    """每小时检测一次,结果写入日志"""
    timestamp = datetime.datetime.now().isoformat()
    verifier = ModelVerifier(
        base_url="https://your-relay.com/v1",
        api_key="sk-xxx",
        model="claude-opus-4-6"
    )
    results = verifier.run_all()

    # 追加到日志
    with open("model_check.log", "a") as f:
        f.write(f"\n[{timestamp}]\n")
        f.write(json.dumps(results, ensure_ascii=False, indent=2))

    # 速度异常告警
    avg_speed = results.get("token_speed", {}).get("avg_tps", 0)
    if avg_speed > 80:
        print(f"⚠️ 告警: TPS={avg_speed},疑似模型被替换!")

schedule.every(1).hours.do(hourly_check)

while True:
    schedule.run_pending()
    time.sleep(60)

踩坑记录

坑1:模型自报身份不可信

很多中转在 system prompt 里注入了 You are Claude,所以模型"自称 Claude"不代表它真是。要结合其他维度判断。

坑2:网络延迟 ≠ 模型慢

第一次测的时候把网络延迟算进了 tps,结果 Opus 测出来只有 15 tps,吓了一跳。后来改成用 usage.completion_tokens / 纯生成时间 才准确。如果中转站支持 streaming,用首 token 到末 token 的时间差更准。

坑3:高峰期测才有意义

有家中转白天测全过,晚上 8 点一测就翻车。估计是共享池子,白天没人用走的真 Opus,晚上挤爆了就降级。所以建议在不同时段各测几次。

小结

中转站不是不能用,但得擦亮眼。总结三条经验:

  1. 价格离谱低的别碰——成本在那摆着,没人做慈善
  2. 定期跑检测脚本——信任但验证
  3. 选有正规云通道的聚合平台——比个人中转站靠谱得多

脚本我已经传到 Gist 了,需要的自取。如果你也测出了有意思的结果,评论区聊聊?