import type { FrontendToken } from '../api/GLTR_API'; /** * 验证值是否为合法的 real_topk 元组 */ export const isValidRealTopkTuple = (value: unknown): value is [number, number] => { return Array.isArray(value) && value.length === 2 && value.every((item) => typeof item === 'number' && Number.isFinite(item)); }; /** * 验证 token 的概率数据 */ export const validateTokenProbabilities = ( tokens: Array<{ real_topk?: [number, number] }> ): string | null => { if (!Array.isArray(tokens) || tokens.length === 0) { return null; } for (let i = 0; i < tokens.length; i++) { const tuple = tokens[i]?.real_topk; if (!isValidRealTopkTuple(tuple)) { return `Token #${i} 缺少合法 real_topk 数据,已取消本次处理。`; } } return null; }; /** * 验证值是否为合法的 pred_topk 条目 */ export const isValidPredTopkEntry = (value: unknown): value is [string, number] => { return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'number' && Number.isFinite(value[1]); }; /** * 验证 token 的预测数据 * 注意:pred_topk 可以为空数组(例如内存优化策略跳过 TopK 计算时),这是正常情况 */ export const validateTokenPredictions = ( tokens: Array<{ pred_topk?: [string, number][] }> ): string | null => { if (!Array.isArray(tokens) || tokens.length === 0) { return '返回数据缺少 token 序列,已取消本次处理。'; } for (let i = 0; i < tokens.length; i++) { const list = tokens[i]?.pred_topk; // pred_topk 必须存在且为数组类型(允许为空数组) if (!Array.isArray(list)) { return `Token #${i} 缺少合法 pred_topk 数组,已取消本次处理。`; } // 只有当 pred_topk 不为空时,才验证其内容格式 if (list.length > 0) { for (let j = 0; j < list.length; j++) { if (!isValidPredTopkEntry(list[j])) { return `Token #${i} 的 pred_topk 项 #${j} 格式非法,已取消本次处理。`; } } } } return null; }; /** * 格式化 token 预览文本(用于错误消息) */ export const formatTokenPreview = (text: string): string => { if (!text) { return '[空]'; } const chars = Array.from(text); if (chars.length <= 12) { return text; } const head = chars.slice(0, 6).join(''); const tail = chars.slice(-3).join(''); return `${head}…${tail}`; }; /** * 验证 token 数据的一致性(offset 和 raw 是否匹配) */ export const validateTokenConsistency = ( bpeStrings: Array<{ offset?: [number, number]; raw?: string }>, originalText: string, options: { allowOverlap?: boolean } = {} ): string | null => { const { allowOverlap = false } = options; if (!Array.isArray(bpeStrings) || bpeStrings.length === 0) { return null; } if (typeof originalText !== 'string') { return '响应缺少原始文本,无法校验 token 数据,已取消本次 demo。'; } const charArray = Array.from(originalText); const totalChars = charArray.length; for (let i = 0; i < bpeStrings.length; i++) { const token = bpeStrings[i]; if (!token) { return `Token #${i} 数据缺失,已取消本次 demo。`; } const offset = token.offset; if (!Array.isArray(offset) || offset.length !== 2) { return `Token #${i} 缺少合法 offset,已取消本次 demo。`; } const [start, end] = offset; if (!Number.isInteger(start) || !Number.isInteger(end)) { return `Token #${i} 的 offset (${start}, ${end}) 不是整数,已取消本次 demo。`; } if (start < 0 || end < start || end > totalChars) { return `Token #${i} 的 offset (${start}, ${end}) 超出原文范围,已取消本次 demo。`; } if (!allowOverlap && i > 0) { const prevOffset = bpeStrings[i - 1]?.offset; if (Array.isArray(prevOffset) && prevOffset.length === 2) { const prevEnd = prevOffset[1]; if (Number.isInteger(prevEnd) && start < prevEnd) { return `Token #${i} 的 offset (${start}, ${end}) 与 Token #${i - 1} 重叠,已取消本次 demo。`; } } } const expected = charArray.slice(start, end).join(''); const raw = token.raw ?? ''; if (expected !== raw) { const previewExpected = formatTokenPreview(expected); const previewRaw = formatTokenPreview(raw); return `Token #${i} 数据异常:offset(${start}, ${end}) 对应原文 "${previewExpected}",但 raw 为 "${previewRaw}"。请重新分析或修复数据。`; } } return null; };