| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- 'use strict';
- /**
- * 智能问数智能体 · 数字溯源 post-check
- * ----------------------------------------------------------------------------
- * final 返回后的零 token 规则校验(turn-assessment 同哲学):从回答文本抽取数值,
- * 逐一在本次运行工具结果的数值集合中做容差匹配;匹配不到的记为「未溯源」并随
- * 运行留痕展示(警示不阻断)。换算形态自动覆盖:百分比显示值(8.21 vs 0.0821)、
- * 万元/亿元换算(4.2万 vs 42000)。日期、TopN、题干序号等模式先行剔除降噪。
- */
- function stripNoise(text) {
- return String(text || '')
- .replace(/\d{4}-\d{1,2}-\d{1,2}/g, ' ') // 2026-10-01
- .replace(/\b\d{8}\b/g, ' ') // 20261001
- .replace(/top\s*\d+/gi, ' ') // Top5
- .replace(/turn\s*\d+/gi, ' ') // Turn 2
- .replace(/v\d+/gi, ' '); // verdict 引用 v3
- }
- function extractNumbers(text) {
- const out = [];
- const re = /-?\d+(?:,\d{3})*(?:\.\d+)?/g;
- const cleaned = stripNoise(text);
- let m;
- while ((m = re.exec(cleaned))) {
- const n = Number(m[0].replace(/,/g, ''));
- if (Number.isFinite(n)) out.push(n);
- }
- return out;
- }
- /** 递归收集工具产物里的数值(深度/广度有界,防大产物拖慢) */
- function collectFromData(data, into, depth = 0) {
- if (data == null || typeof data !== 'object' || depth > 4) return;
- if (Array.isArray(data)) {
- for (const item of data.slice(0, 60)) collectFromData(item, into, depth + 1);
- return;
- }
- for (const v of Object.values(data)) {
- if (typeof v === 'number' && Number.isFinite(v)) into.push(v);
- else if (v && typeof v === 'object') collectFromData(v, into, depth + 1);
- }
- }
- function collectCandidates(steps) {
- const cands = [];
- for (const step of steps || []) {
- if (step.type !== 'tool' || !step.ok) continue;
- collectFromData(step.data, cands);
- for (const n of extractNumbers(step.summary || '')) cands.push(n);
- }
- return cands;
- }
- function matches(cands, n) {
- return cands.some((c) => {
- if (Math.abs(c - n) <= Math.max(Math.abs(c) * 0.005, 0.01)) return true;
- // 展示换算:百分比(×100)、万元(×1e4)、亿元(×1e8),以及反向
- for (const k of [100, 1e4, 1e8]) {
- if (Math.abs(c - n * k) <= Math.max(Math.abs(c) * 0.005, 0.01)) return true;
- if (Math.abs(c * k - n) <= Math.max(Math.abs(n) * 0.005, 0.01)) return true;
- }
- return false;
- });
- }
- /**
- * @returns {{total:number, untraced:number[], ratio:number}}
- */
- function checkProvenance(answer, steps) {
- const nums = extractNumbers(answer);
- if (!nums.length) return { total: 0, untraced: [], ratio: 1 };
- const cands = collectCandidates(steps);
- if (!cands.length) return { total: nums.length, untraced: nums.slice(0, 12), ratio: 0 };
- const untraced = [];
- for (const n of nums) {
- if (!matches(cands, n)) untraced.push(n);
- }
- return { total: nums.length, untraced: untraced.slice(0, 12), ratio: nums.length ? 1 - untraced.length / nums.length : 1 };
- }
- module.exports = { extractNumbers, collectCandidates, checkProvenance };
|