Files
UHD_Media_Tool/packages/agent/src/prompt/report-lint.ts
T

77 lines
3.3 KiB
TypeScript

import { CAPABILITY_IDS } from "@umt/contracts";
// 汇报格式的机器核对(AG-5 评测用):fake 模型评测与真实模型评测共用。
// 只查能机械判断的项;"结论在第一句"按开头的套话与是否含状态词近似判断。
export interface ReportLintIssue {
rule:
| "too_long"
| "uuid"
| "hash"
| "raw_bytes"
| "iso_time"
| "internal_field"
| "capability_id"
| "heading"
| "no_conclusion_first"
| "asks_user_for_id"
| "lecture";
detail: string;
}
export interface ReportLintOptions {
/** 最多几行(非空行);缺省 8 */
maxLines?: number;
}
const UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
const HASH = /\b[0-9a-f]{40}(?:[0-9a-f]{24})?\b/i;
/** 7 位以上的裸数字(原始字节数、原始计数);年份、日期、带千分位的不算 */
const RAW_NUMBER = /(?<![\d.:\-/])\d{7,}(?![\d.:\-/])/;
const ISO_TIME = /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
/** 内部字段名:snake_case、camelCase 与已知的内部词 */
const INTERNAL_WORDS =
/\b(?:adapter|healthStatus|health_status|mediaEntityId|slotId|planId|runId|taskId|downloadTaskId|sourceReleaseId|payloadHash|idempotency|capabilityId|invocationId)\b/;
const SNAKE = /\b[a-z]+_[a-z_]+\b/;
const CAMEL = /\b[a-z]+[A-Z][A-Za-z]+\b/;
/** 开头套话(结论没放第一句) */
const WEAK_OPENERS = /^(?:#|\*\*?检查结果|检查结果|结论[::]|以下|下面|好的|当然|首先|根据|经过|我(?:已|先|查)|让我)/;
const ASK_ID = /(?:请|麻烦)?(?:提供|给(?:我)?(?:一个)?)[^。\n]{0,12}(?:ID|hash|编号|哈希)/i;
const LECTURE = /(?:不等于|并不代表|不能证明|无证据|建议定期|值得注意的是|需要注意的是)/;
/** 允许出现在汇报里的英文词(技术词保留原文) */
const ALLOWED_TOKENS = new Set(["qBittorrent", "iPhone", "macOS"]);
/** 核对一段汇报;返回空数组 = 通过 */
export function lintReport(text: string, opts: ReportLintOptions = {}): ReportLintIssue[] {
const maxLines = opts.maxLines ?? 8;
const issues: ReportLintIssue[] = [];
const lines = text
.split("\n")
.map((l) => l.trim())
.filter((l) => l !== "" && !/^\|?\s*:?-{3,}/.test(l));
if (lines.length > maxLines) issues.push({ rule: "too_long", detail: `${lines.length} 行,超过 ${maxLines} 行` });
const find = (re: RegExp) => text.match(re)?.[0];
const hit = (rule: ReportLintIssue["rule"], re: RegExp) => {
const m = find(re);
if (m) issues.push({ rule, detail: m });
};
hit("uuid", UUID);
hit("hash", HASH);
hit("raw_bytes", RAW_NUMBER);
hit("iso_time", ISO_TIME);
hit("internal_field", INTERNAL_WORDS);
const snake = find(SNAKE);
if (snake) issues.push({ rule: "internal_field", detail: snake });
const camel = text.match(new RegExp(CAMEL, "g"))?.find((w) => !ALLOWED_TOKENS.has(w));
if (camel) issues.push({ rule: "internal_field", detail: camel });
const cap = CAPABILITY_IDS.find((id) => text.includes(id));
if (cap) issues.push({ rule: "capability_id", detail: cap });
if (/^#{1,6} /m.test(text)) issues.push({ rule: "heading", detail: "用了 Markdown 标题" });
const first = lines[0] ?? "";
if (!first || WEAK_OPENERS.test(first)) issues.push({ rule: "no_conclusion_first", detail: first.slice(0, 40) });
hit("asks_user_for_id", ASK_ID);
hit("lecture", LECTURE);
return issues;
}