ToseaAI/llm-fingerprint-detector
GitHub: ToseaAI/llm-fingerprint-detector
一个基于单 token 行为指纹的 LLM 验证工具,用于检测 OpenAI 兼容 API 是否真的提供了所声称的模型。
Stars: 3 | Forks: 1
# llm-fingerprint-detector
[](LICENSE)
[](package.json)
[](package.json)
[](https://arxiv.org/abs/2607.10252)
**那个 API 真的在提供它所声称的模型吗?** 通过单 token 输出分布对任何兼容 OpenAI 的 LLM endpoint 进行指纹识别和验证——不需要 logits,不需要 weights,也不需要特权访问。只需大约 100–400 次廉价的单词补全。能够发现 API 经销商、网关和聚合器造成的**模型替换**、静默量化和服务端 prompt 注入。
这是一个独立的开源 TypeScript 实现,实现了以下内容:
本包**与论文作者没有任何隶属关系**——它是从零开始对已发表方法的工程实现,被打造成一个可复用的库 + CLI。如果你在研究中使用该方法,请引用原论文。
- **零运行时依赖** — 仅使用 Node ≥ 18 内置的 `fetch`,没有其他依赖
- **库 + CLI** — 可以嵌入使用,也可以在 CI 中运行 `llm-fingerprint verify`
- **感知推理模型** — 自动检测如何禁用隐藏的“思考”过程(OpenRouter / Zhipu / OpenAI 风格),并带有优雅的降级处理
- **抗过滤的探针** — 每个探针都是从复述池中提取的普通语义问题;网关无法对其进行针对性的特殊处理
- **内置示例参考** 包含 11 种流行模型的指纹,源自论文的公开数据集
## 工作原理
LLM 在回答“*说出一个 1 到 100 之间的随机数*”时,会表现出特定于模型且极其稳定的偏见(GPT 系列模型偏爱 42 和 73;其他系列模型偏好 57、37、7……)。论文的关键结论是:在此类小规模任务测试组中,**单词答案的经验分布**是一种可靠的*行为指纹*——对于同一个模型,这种指纹在不同时间、负载和提供商之间保持稳定,而在不同模型之间则存在显著差异。
```
probe battery (task × language cells) collect at temperature 1.0
┌──────────────────────────────┐ ┌─────────────────────────┐
│ random number 1-100 (en/zh) │ │ "42" ×19 "73" ×4 ... │
│ random color / letter / city │ ──25×──▶ │ per-cell answer │
│ coin flip / animal / fav-num │ │ distributions │
└──────────────────────────────┘ └───────────┬─────────────┘
▼
reference fingerprint ──── mean per-cell Jensen-Shannon
(trusted endpoint) divergence (base 2) ──▶ verdict
```
1. **探测** — 用英文和中文提出单词问题(随机数、颜色、字母、抛硬币结果等),设置 `temperature=1.0`,`max_tokens=16`,使用固定的最小化 system prompt,并禁用隐藏的推理过程。每次调用的请求都会经过乱序处理和复述改写。
2. **归一化** — 进行 NFC 规范化、标点/表情符号剥离、大小写折叠、提取首词、数字统一(`seven`/`七`/`٧`/`7` → `7`)、颜色和硬币词汇的标准化;答案会被分类为有效 / 无效 / 拒绝 / 空。
3. **比较** — 计算每个 cell 的 Jensen-Shannon divergence(以 2 为底,取值范围 0–1 bit),并在双方均拥有 ≥10 个有效样本的 cell 上取平均值。
4. **判定** — 根据论文的基线校准出三个区间(参见[解读结果](#interpreting-results))。
## 安装
```
npm install llm-fingerprint-detector # library + `llm-fingerprint` CLI
# 或者直接运行 CLI:
npx llm-fingerprint-detector --help
```
要求 Node ≥ 18.17(内置 `fetch`)。核心库与运行时无关,也可以在浏览器/edge runtime 中运行;只有 CLI 和内置参考加载器会接触文件系统。
## 快速开始 — CLI
```
# 1. 对你信任的 endpoint 进行 fingerprint(key 从 OPENAI_API_KEY 读取)
export OPENAI_API_KEY=sk-...
llm-fingerprint fingerprint \
--base-url https://api.openai.com/v1 \
--model gpt-4o-mini \
--out reference.gpt-4o-mini.json
# 2. 验证你不信任的 endpoint
export LLM_FINGERPRINT_API_KEY=sk-... # key for the endpoint under test
llm-fingerprint verify \
--base-url https://cheap-llm-reseller.example.com/v1 \
--model gpt-4o-mini \
--reference reference.gpt-4o-mini.json
```
```
Verdict: MISMATCH — behavior differs from the reference
Mean JSD: 0.481 over 8 comparable cell(s)
Interpretation scale (paper baselines, arXiv:2607.10252):
same model ≈ 0.14 · same model, other provider ≈ 0.227 · different model ≈ 0.463
thresholds: match ≤ 0.25 < uncertain ≤ 0.35 < mismatch
Per-cell JSD (most divergent first):
random-number-1-100:en 0.712 (24 vs 25 valid)
...
```
退出码对 CI 友好:`0` 匹配 · `2` 不匹配 · `3` 不确定 · `4` 样本不足 · `1` 错误。API key 仅从环境变量中读取(`--api-key-env NAME`,默认依次为 `LLM_FINGERPRINT_API_KEY` 和 `OPENAI_API_KEY`),且绝对不会被记录到日志中。
了解更多:`llm-fingerprint --help`,[`examples/cli-examples.sh`](examples/cli-examples.sh)。
## 快速开始 — 库
```
import { fingerprint, compare, verify } from 'llm-fingerprint-detector'
// Collect a fingerprint
const run = await fingerprint(
{ baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o-mini', apiKey: process.env.OPENAI_API_KEY },
{ cells: 8, samplesPerCell: 25, onProgress: (e) => console.log(e.done, '/', e.total) },
)
console.log(run.fingerprint) // JSON-serializable artifact
console.log(run.splitHalfJsd) // self-consistency (≈0.14 is normal)
// Verify another endpoint against it
const result = await verify(
{ baseUrl: 'https://suspect.example.com/v1', model: 'gpt-4o-mini', apiKey: process.env.SUSPECT_KEY },
run.fingerprint,
)
console.log(result.verdict, result.meanJsd) // 'match' | 'uncertain' | 'mismatch' | 'insufficient'
// Or compare two saved fingerprints offline
const distance = compare(fingerprintA, fingerprintB)
```
内置示例参考(仅限 Node):
```
import { listBundledReferences, loadBundledReference } from 'llm-fingerprint-detector/references'
const reference = loadBundledReference('openai/gpt-4o-mini')
const result = await verify(suspectEndpoint, reference)
```
可运行示例:[`examples/01-fingerprint-endpoint.mjs`](examples/01-fingerprint-endpoint.mjs),[`examples/02-verify-endpoint.mjs`](examples/02-verify-endpoint.mjs)。
## 教程:“我买的廉价 API 真的是 GPT-4o / Claude / DeepSeek 吗?”
你半价从某家经销商/聚合器那里购买了 API 访问权限。你得到的是真正的模型、更廉价的替代品,还是量化后的克隆版?只需十分钟:
1. **收集可信的参考基准。** 对相关模型的*官方* API(或任何你完全信任的 endpoint)进行指纹识别:
llm-fingerprint fingerprint --base-url https://api.openai.com/v1 \
--model gpt-4o-mini --api-key-env OFFICIAL_KEY --out ref.json
没有官方访问权限?从内置的示例开始(`llm-fingerprint references`)——作为初步信号已经足够好了,但需注意下文提到的注意事项。
2. **使用相同的 model id 验证可疑的 endpoint**:
llm-fingerprint verify --base-url https://reseller.example.com/v1 \
--model gpt-4o-mini --api-key-env RESELLER_KEY --reference ref.json
3. **查看判定结果。**
- `match` — 该 endpoint 的单 token 行为在统计上与你的参考基准一致。这是证明它是同一个模型的有力(但非绝对)证据。
- `mismatch` — 其行为偏离参考基准的程度,通常相当于*不同*模型之间的差异。在实践中,常见原因包括:被替换为更廉价的模型、进行了重度量化部署,或是被注入了 system prompt。
- `uncertain` — 灰色地带。可以提高 `--samples`(例如设为 40),使用 `--preset strict`(覆盖全部 16 个 cell),或者更新你的参考基准——因为当提供商发布更新时,模型会发生漂移。
- 此外还要注意警告信息:较高的 **split-half JSD** 意味着该 endpoint 在你自己测试的前后两半运行中*自身表现不一致*——这是聚合器轮换多个后端服务的典型特征。
4. **在下任何结论之前请重新运行。** 在不同日期进行的两次独立的不匹配测试,其信号强度远高于单次测试。
## 解读结果
距离指标为平均 Jensen-Shannon divergence(以 2 为底),因此 0 = 行为完全相同,1 = 答案集互斥。论文基线如下:
| meanJsd | 解读 |
|---|---|
| ≈ 0.14 | 相同模型,相同 endpoint(采样噪声基线) |
| ≈ 0.227 | 相同模型,不同提供商(中位数) |
| **≤ 0.25** | → 判定为 **match(匹配)** |
| 0.25 – 0.35 | → 判定为 **uncertain(不确定)** |
| **> 0.35** | → 判定为 **mismatch(不匹配)** |
| ≈ 0.463 | 不同模型(中位数) |
**错误率(论文数据):** 在区分同模型对不同模型对时,使用 8 个 cell 的等错误率约为 **10.6%**,使用 40 个 cell 时约为 **7.3%**。单次运行只能提供*证据*,而非最终证明——请谨慎对待判定结果。
### 必须了解的局限性
- **指纹会漂移。** 提供商会悄无声息地更新模型;3 个月前的参考基准与今天的部署版本发生合理的不匹配是完全正常的。务必检查 `collectedAt`,并定期更新参考基准。
- **你需要一个可信的参考基准。** 该方法比较的是两个 endpoint;它无法凭空得出基准真相。如果你的参考基准是错的,你的判定结果也就是错的。
- **双方的 system prompt 必须完全一致。** 仅替换 system prompt 就会使指纹产生约 0.44–0.46 的 JSD 偏移——这相当于替换整个模型的幅度。该工具会自动在双方固定相同的最小化 system prompt;如果某个 endpoint *注入*了其自身的服务端 prompt,这(正确地)会表现为分布的差异。
- **推理降级会降低置信度。** 当隐藏的推理过程无法被禁用时,运行将被标记为 `postReasoning`——在该通道中,分布会发生显著的偏移。
- **量化/服务技术栈的变动** 即便是相同的权重,也可能导致距离被推移到不确定的区间。
- **不匹配是一种统计观察结果,而非指控。** 不要将任何单次判定结果视为提供商欺诈的最终证据;在得出结论之前,请进行调查、重新测试并比对记录。
## 内置示例参考
`data/reference-fingerprints.sample.json` 附带了 11 种流行模型(GPT-4o、GPT-4o-mini、GPT-4.1-mini、Claude Sonnet 4.5、Gemini 2.5 Flash、DeepSeek-chat、Llama-3.1-8B、Qwen3-30B-A3B、Mistral Small 3.2、GLM-4.5、Kimi K2)的指纹,这些数据源自论文的公开数据集:
这些样本是由论文的测试工具(通过 OpenRouter)在论文设定的 prompt 协议下收集的——与本包的测试组非常接近,但并非完全相同。`compare()` 会将此类配对标记为 `protocolMismatch: true`,在解读判定结果时应将其视为*指示性*的。如果验证结果对你非常重要,请收集你自己的参考基准:
```
llm-fingerprint fingerprint --base-url --model --out my-reference.json
```
如果要从 Zenodo 数据集重建或扩展内置样本,请下载该数据集,然后执行:
```
npm run build
node scripts/build-sample-references.mjs path/to/distributions.json --models openai/gpt-4o,another/model
```
## API 概览(TypeScript,完全类型化)
| 导出项 | 功能说明 |
|---|---|
| `fingerprint(endpoint, options?)` | 探测 endpoint → 返回 `FingerprintRun`(指纹、适配器、split-half、警告信息) |
| `compare(a, b)` | 输入两个指纹 → 返回 `ComparisonResult`(meanJsd、每 cell 的 JSD、判定结果、基线) |
| `verify(endpoint, reference, options?)` | 在一次调用中完成指纹识别和比较 → 返回 `VerifyResult` |
| `normalizeAnswer(raw, domain)` | 完整的归一化流水线(纯函数,经过单元测试) |
| `jensenShannonDivergence(p, q)` | 基于计数的映射(count maps)计算以 2 为底的 JSD |
| `splitHalfJsd(samplesByCell)` | endpoint 自洽性检查 |
| `detectReasoningAdapter(endpoint)` | 探测 endpoint 支持哪个禁用推理的字段 |
| `PROBE_TASKS`, `CELL_PRIORITY_ORDER`, `SYSTEM_PROMPTS` | 测试组本身 |
| `llm-fingerprint-detector/references` | 内置示例加载器(仅限 Node) |
所有选项(`cells`、`samplesPerCell`、`concurrency`、`timeoutMs`、`maxRetries`、`signal`、`onProgress` 等)均在 [`src/types.ts`](src/types.ts) 中有详细说明。
## 开发
```
git clone https://github.com/ToseaAI/llm-fingerprint-detector.git
cd llm-fingerprint-detector
npm install
npm run build # tsc → dist/
npm test # builds, then runs node --test against the built output
```
无需测试框架,无需打包工具——仅使用 TypeScript 和 Node 内置的测试运行程序。
## 引用与许可
方法:请引用原论文——
```
@article{bruckner2026onetoken,
title = {One Token Is Enough: Fingerprinting and Verifying Large Language Models
from Single-Token Output Distributions},
author = {Bruckner, Tom{\'a}{\v s}},
journal = {arXiv preprint arXiv:2607.10252},
year = {2026}
}
```
本包:[MIT](LICENSE)。内置样本数据:CC-BY-4.0,© Tomáš Bruckner(如上文所述)。
*由 [Tosea.ai](https://tosea.ai) 构建并维护。想要零配置版本吗? → [LLM API Fingerprint Checker](https://tosea.ai/free-tools/llm-api-fingerprint-checker)(在浏览器中运行,你的 API key 永远不会离开本机)。*
标签:API验证, C2日志可视化, DLL 劫持, MITM代理, TypeScript, 大语言模型, 安全插件, 指纹识别, 文档结构分析, 模型行为分析, 自动化攻击