mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
Grades fast-model activity-label headers against a fixed corpus so instruction changes are measured rather than eyeballed on one conversation. This existed untracked while the continuity work was developed; committing it because it is the only reproducible record of WHY `ACTIVITY_INSTRUCTION` is ordered and capped the way it is. - captured.json: 9 real production payloads pulled verbatim from Langfuse with the headers that shipped. Irreplaceable — traces age out. - corpus.js: 17 cases / 28 steps. The captured run replays as one sequence, plus synthetic cases for the modes it never exercised (all-failed, partial, parallel batches, rapid near-duplicates, entry overflow, truncated output, error-shaped success). Multi-step cases chain each generated label into the next step's context, which is what makes cross-batch redundancy measurable at all. - prompt.js: faithful port of the SDK's buildActivityLabelPrompt so synthetic cases render the bytes production sends, plus a previousLabelCap knob for continuity-window experiments. - variants.js: single-factor instruction variants. The baseline is read from the BUILT package (workspace resolution, then dist, then LABEL_EVAL_DIST) so a variant can never be graded against a stale copy of the shipped instruction. - checks.js: length/punctuation/markdown/tool-echo/count-echo, plus overlap split into `restate` (adds nothing over an earlier header) vs `template` (same frame, new payload — often fine). - run.js / rescore.js: live runner on the production wire shape (max_tokens 256) and an offline re-grader, so metric fixes never require re-spending on the API. Results are gitignored — regenerable, and 292K of the 364K. A full sweep is ~$0.03 per variant and ~45s. Findings are recorded in the README, two of them counter-intuitive: enumerating acceptable opening verbs ANCHORED the model rather than diversifying it (Confirmed 18→23, opener diversity halved), and diverse examples alone changed nothing. Sentence order is load-bearing, so a tidying reshuffle of ACTIVITY_INSTRUCTION regresses real output.
156 lines
4.4 KiB
JavaScript
156 lines
4.4 KiB
JavaScript
/**
|
|
* Mechanical label checks. These catch format violations and the two
|
|
* measurable prose failures (register collapse via first-word tallies,
|
|
* cross-batch redundancy via content-word overlap); commit-log READABILITY
|
|
* still needs the human pass over results/latest.md.
|
|
*/
|
|
const STOPWORDS = new Set([
|
|
'the',
|
|
'a',
|
|
'an',
|
|
'to',
|
|
'of',
|
|
'and',
|
|
'or',
|
|
'with',
|
|
'for',
|
|
'in',
|
|
'on',
|
|
'at',
|
|
'is',
|
|
'are',
|
|
'was',
|
|
'were',
|
|
'be',
|
|
'been',
|
|
'it',
|
|
'its',
|
|
'as',
|
|
'by',
|
|
'from',
|
|
'that',
|
|
'this',
|
|
'both',
|
|
'all',
|
|
'no',
|
|
'not',
|
|
'via',
|
|
]);
|
|
|
|
function words(label) {
|
|
return label.trim().split(/\s+/).filter(Boolean);
|
|
}
|
|
|
|
const SUFFIXES = ['ations', 'ation', 'ence', 'ance', 'ings', 'ing', 'ed', 'es', 's'];
|
|
|
|
/** Crude suffix stemmer so persists/persistence/persisted collide — enough
|
|
* for overlap detection; linguistic correctness is not the goal. */
|
|
function stem(word) {
|
|
if (word.length < 5) {
|
|
return word;
|
|
}
|
|
for (const suffix of SUFFIXES) {
|
|
if (word.endsWith(suffix) && word.length - suffix.length >= 4) {
|
|
return word.slice(0, word.length - suffix.length);
|
|
}
|
|
}
|
|
return word;
|
|
}
|
|
|
|
function contentWords(label) {
|
|
return words(label.toLowerCase().replace(/[^a-z0-9/._-]+/g, ' '))
|
|
.filter((word) => !STOPWORDS.has(word))
|
|
.map(stem);
|
|
}
|
|
|
|
/** Payload tokens carry the informative delta between template-shaped
|
|
* labels: numbers, versions, paths, filenames. */
|
|
function isPayload(word) {
|
|
return /\d/.test(word) || word.includes('/') || word.includes('.');
|
|
}
|
|
|
|
function jaccard(a, b) {
|
|
const setA = new Set(a);
|
|
const setB = new Set(b);
|
|
if (setA.size === 0 || setB.size === 0) {
|
|
return 0;
|
|
}
|
|
let intersection = 0;
|
|
for (const word of setA) {
|
|
if (setB.has(word)) {
|
|
intersection += 1;
|
|
}
|
|
}
|
|
return intersection / (setA.size + setB.size - intersection);
|
|
}
|
|
|
|
const GENERIC_OPENER = /^(ran|used|executed|called|invoked|performed)\b/i;
|
|
const COUNT_ECHO = /\b\d+\s+(tools?|commands?|calls?)\b/i;
|
|
const DUP_THRESHOLD = 0.5;
|
|
|
|
/**
|
|
* @param label generated label text
|
|
* @param entries the batch's tool entries (for tool-name echo detection)
|
|
* @param previousLabels labels generated EARLIER in the same case chain,
|
|
* regardless of whether the variant saw them — redundancy is measured
|
|
* uniformly so continuity variants can be compared against blind ones.
|
|
*/
|
|
function checkLabel(label, { entries = [], previousLabels = [] } = {}) {
|
|
const flags = [];
|
|
const wordList = words(label);
|
|
if (wordList.length < 4 || wordList.length > 9) {
|
|
flags.push(`len:${wordList.length}`);
|
|
}
|
|
if (/[.!?,;:]$/.test(label.trim())) {
|
|
flags.push('punct');
|
|
}
|
|
if (/^["'`]|["'`]$/.test(label.trim())) {
|
|
flags.push('quote');
|
|
}
|
|
if (/[*`]|^#|\[.*\]\(/.test(label)) {
|
|
flags.push('md');
|
|
}
|
|
if (GENERIC_OPENER.test(label.trim())) {
|
|
flags.push('opener');
|
|
}
|
|
const lower = label.toLowerCase();
|
|
for (const entry of entries) {
|
|
const name = String(entry.toolName ?? '').toLowerCase();
|
|
if (name.length > 3 && (lower.includes(name) || lower.includes(name.replace(/_/g, ' ')))) {
|
|
flags.push(`tool-echo:${entry.toolName}`);
|
|
break;
|
|
}
|
|
}
|
|
if (COUNT_ECHO.test(label)) {
|
|
flags.push('count-echo');
|
|
}
|
|
/** Overlap splits into two flags: `restate` (high overlap, no payload
|
|
* delta — the line adds nothing over a previous header; the production
|
|
* 2/3 and 7/8 failure) and `template` (high overlap but the differing
|
|
* tokens are numbers/paths — same sentence frame, new information, e.g.
|
|
* fib(1)→fib(2). Often fine, arguably better than synonym churn). */
|
|
const own = contentWords(label);
|
|
let maxOverlap = 0;
|
|
let worst = null;
|
|
for (const previous of previousLabels) {
|
|
const other = contentWords(previous);
|
|
const overlap = jaccard(own, other);
|
|
if (overlap > maxOverlap) {
|
|
maxOverlap = overlap;
|
|
worst = other;
|
|
}
|
|
}
|
|
if (maxOverlap > DUP_THRESHOLD && worst != null) {
|
|
const otherSet = new Set(worst);
|
|
const ownSet = new Set(own);
|
|
const differing = [
|
|
...own.filter((word) => !otherSet.has(word)),
|
|
...worst.filter((word) => !ownSet.has(word)),
|
|
];
|
|
const informativeDelta = differing.some(isPayload);
|
|
flags.push(`${informativeDelta ? 'template' : 'restate'}:${maxOverlap.toFixed(2)}`);
|
|
}
|
|
return { flags, wordCount: wordList.length, firstWord: wordList[0] ?? '', maxOverlap };
|
|
}
|
|
|
|
module.exports = { checkLabel, contentWords, jaccard, words, stem };
|