ruoxi_sun 58657935fc bundle fingerprint tool repos into evalstone for self-containment
Vendor LLMmap / llm-verify / llm-fingerprint-detector under
bash/fingerprint/tools so the three fingerprint benchmarks run with only
/data1/eval mounted (no /data1/xii dependency):
- run.py DEFAULT_TOOLS_ROOT prefers builtin tools/, falls back to /data1/xii
- exclude .git / node_modules / template backups
- detector dist/ (pre-built) retained; node_modules not needed at runtime
2026-09-03 06:45:46 +00:00

235 lines
6.8 KiB
TypeScript
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.

/**
* Probe battery: 8 tasks × 2 languages = 16 cells.
*
* Design (arXiv:2607.10252):
* - ≥3 paraphrases per task per language, drawn at random per request — every
* probe is a plain semantic question, so gateways cannot keyword-filter them;
* - a fixed minimal system prompt forces a one-word answer; max_tokens=16
* truncates stragglers;
* - cells are ordered by empirical discriminativeness (random-number tasks
* separate models best); presets take a prefix of that order.
*/
import type {
CellId,
ProbeLang,
ProbePreset,
ProbePresetId,
ProbeTaskId,
ProbeTaskSpec,
} from './types.js'
/**
* Fixed minimal system prompt — a critical invariant.
*
* The reference fingerprint and the endpoint under test MUST be collected
* under the *same* system prompt. Swapping only the system prompt (all else
* equal) can shift a model's fingerprint by JSD ≈ 0.440.46 — the same
* magnitude as swapping the model itself — which would turn a prompt
* difference into a false "different model" verdict.
*/
export const SYSTEM_PROMPTS: Record<ProbeLang, string> = {
en: 'Answer with exactly one word. No punctuation, no explanation.',
zh: '只回答一个词,不要标点,不要解释。',
}
export const PROBE_TASKS: Record<ProbeTaskId, ProbeTaskSpec> = {
'random-number-1-100': {
id: 'random-number-1-100',
domain: { kind: 'int', min: 1, max: 100 },
paraphrases: {
en: [
'Name a random number between 1 and 100.',
'Pick a random number from 1 to 100.',
'Give me a random number between 1 and 100.',
'Choose any number between 1 and 100 at random.',
],
zh: [
'说一个 1 到 100 之间的随机数。',
'随机挑一个 1 到 100 的数字。',
'给我一个 1 至 100 之间的随机数字。',
'从 1 到 100 里随便选一个数。',
],
},
},
'random-number-1-10': {
id: 'random-number-1-10',
domain: { kind: 'int', min: 1, max: 10 },
paraphrases: {
en: [
'Name a random number between 1 and 10.',
'Pick a random number from 1 to 10.',
'Give me a random number between 1 and 10.',
],
zh: [
'说一个 1 到 10 之间的随机数。',
'随机挑一个 1 到 10 的数字。',
'从 1 到 10 里随便选一个数。',
],
},
},
'random-letter': {
id: 'random-letter',
domain: { kind: 'letter' },
paraphrases: {
en: [
'Name a random letter of the alphabet.',
'Pick a random letter from A to Z.',
'Give me one random letter.',
],
zh: [
'说一个随机的英文字母。',
'从 A 到 Z 里随便挑一个字母。',
'随机给我一个英文字母。',
],
},
},
'random-color': {
id: 'random-color',
domain: { kind: 'color' },
paraphrases: {
en: [
'Name a random color.',
'Pick a color at random.',
'Give me one random color.',
],
zh: [
'说一个随机的颜色。',
'随便说一种颜色。',
'随机挑一个颜色告诉我。',
],
},
},
'coin-flip': {
id: 'coin-flip',
domain: { kind: 'coin' },
paraphrases: {
en: [
'Flip a coin. Answer heads or tails.',
'Toss a coin and tell me the result: heads or tails.',
'Imagine flipping a coin. Which side came up, heads or tails?',
],
zh: [
'抛一枚硬币,回答正面还是反面。',
'掷一次硬币,告诉我结果:正面或反面。',
'想象抛硬币,落地是正面还是反面?',
],
},
},
'random-animal': {
id: 'random-animal',
domain: { kind: 'word' },
paraphrases: {
en: [
'Name a random animal.',
'Pick an animal at random.',
'Give me one random animal.',
],
zh: [
'说一个随机的动物。',
'随便说一种动物。',
'随机挑一个动物告诉我。',
],
},
},
'random-city': {
id: 'random-city',
domain: { kind: 'word' },
paraphrases: {
en: [
'Name a random city.',
'Pick a city at random.',
'Give me the name of one random city.',
],
zh: [
'说一个随机的城市。',
'随便说一座城市。',
'随机挑一个城市告诉我。',
],
},
},
'favorite-number': {
id: 'favorite-number',
domain: { kind: 'int', min: 0, max: 10_000 },
paraphrases: {
en: [
'What is your favorite number?',
'Tell me your favourite number.',
'If you had to pick a favorite number, what would it be?',
],
zh: [
'你最喜欢的数字是什么?',
'说说你最爱的数字。',
'如果必须选一个最喜欢的数字,你选哪个?',
],
},
},
}
export function makeCellId(task: ProbeTaskId, lang: ProbeLang): CellId {
return `${task}:${lang}`
}
export function parseCellId(cellId: CellId): { task: ProbeTaskId; lang: ProbeLang } {
const idx = cellId.lastIndexOf(':')
return {
task: cellId.slice(0, idx) as ProbeTaskId,
lang: cellId.slice(idx + 1) as ProbeLang,
}
}
export function isCellId(value: string): value is CellId {
const idx = value.lastIndexOf(':')
if (idx <= 0) return false
const task = value.slice(0, idx)
const lang = value.slice(idx + 1)
return task in PROBE_TASKS && (lang === 'en' || lang === 'zh')
}
/**
* All 16 cells, ordered by discriminativeness (random-number tasks first,
* per the paper and our own cross-model measurements). Presets take a prefix.
*/
export const CELL_PRIORITY_ORDER: CellId[] = [
'random-number-1-100:en',
'random-number-1-100:zh',
'random-color:en',
'random-animal:en',
'random-number-1-10:en',
'random-letter:en',
'random-color:zh',
'coin-flip:en',
'favorite-number:en',
'random-city:en',
'random-number-1-10:zh',
'coin-flip:zh',
'random-letter:zh',
'random-animal:zh',
'random-city:zh',
'favorite-number:zh',
]
export const PROBE_PRESETS: Record<ProbePresetId, ProbePreset> = {
quick: { id: 'quick', cellCount: 4, samplesPerCell: 15 },
standard: { id: 'standard', cellCount: 8, samplesPerCell: 25 },
strict: { id: 'strict', cellCount: 16, samplesPerCell: 25 },
}
export function getCellsForPreset(preset: ProbePresetId): CellId[] {
return CELL_PRIORITY_ORDER.slice(0, PROBE_PRESETS[preset].cellCount)
}
export function getTaskSpec(cellId: CellId): ProbeTaskSpec {
return PROBE_TASKS[parseCellId(cellId).task]
}
export function getSystemPrompt(cellId: CellId): string {
return SYSTEM_PROMPTS[parseCellId(cellId).lang]
}
export function pickParaphrase(cellId: CellId, random: () => number = Math.random): string {
const { task, lang } = parseCellId(cellId)
const pool = PROBE_TASKS[task].paraphrases[lang]
return pool[Math.floor(random() * pool.length)] ?? pool[0]
}