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.
239 lines
7.3 KiB
JavaScript
239 lines
7.3 KiB
JavaScript
/**
|
||
* Instruction variants under test. Each is a SINGLE-factor change against the
|
||
* production instruction so a result implicates one hypothesis:
|
||
*
|
||
* - baseline — ACTIVITY_INSTRUCTION exactly as the branch ships it
|
||
* - verbs — H: the Good-example verb distribution seeds register
|
||
* collapse (6/9 production labels opened "Confirmed")
|
||
* - ordered — H: the 4–9 word cap gets crowded out mid-paragraph; moving
|
||
* format constraints last improves adherence
|
||
* - continuity — H: showing the run's previous headers kills cross-batch
|
||
* redundancy (production pairs 2/3 and 7/8)
|
||
*
|
||
* The baseline is required from packages/api/dist so drift against the branch
|
||
* is impossible; the sentence table below is asserted against it so composed
|
||
* variants can never silently diverge from what production actually sends.
|
||
*/
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..', '..');
|
||
|
||
/**
|
||
* The shipped instruction, read from the BUILT package so a variant can never
|
||
* be graded against a stale copy of it. Tries the workspace resolution first
|
||
* (an installed checkout), then the dist path directly, so the harness works
|
||
* whether or not `node_modules` is populated. `LABEL_EVAL_DIST` points it at
|
||
* another checkout's build — useful for grading one branch's instruction from
|
||
* a worktree that has not been built.
|
||
*/
|
||
function loadShippedInstruction() {
|
||
const candidates = [
|
||
process.env.LABEL_EVAL_DIST,
|
||
'@librechat/api',
|
||
path.join(ROOT, 'packages/api/dist/index.cjs'),
|
||
].filter(Boolean);
|
||
for (const candidate of candidates) {
|
||
try {
|
||
const { ACTIVITY_INSTRUCTION } = require(candidate);
|
||
if (typeof ACTIVITY_INSTRUCTION === 'string' && ACTIVITY_INSTRUCTION.length > 0) {
|
||
return ACTIVITY_INSTRUCTION;
|
||
}
|
||
} catch {
|
||
/* try the next candidate */
|
||
}
|
||
}
|
||
throw new Error(
|
||
'Could not load ACTIVITY_INSTRUCTION from a built @librechat/api.\n' +
|
||
'Build it first (from the repo root):\n' +
|
||
' npm run build:data-provider && npm run build:data-schemas && npm run build:api\n' +
|
||
'Or point at an existing build:\n' +
|
||
' LABEL_EVAL_DIST=/path/to/packages/api/dist/index.cjs node scripts/activity-labels/run.js',
|
||
);
|
||
}
|
||
|
||
const ACTIVITY_INSTRUCTION = loadShippedInstruction();
|
||
|
||
const S = {
|
||
role: 'You write the one-line header above a group of tool calls an AI agent just made.',
|
||
register:
|
||
'Write it like a git commit subject: past tense, verb first, leading with the most distinctive file, name, or finding.',
|
||
outcome:
|
||
'Say what the calls established or produced — the outcome, not the attempt. If they answered a question, the answer is the line.',
|
||
prohibitions:
|
||
'Never name the tools, never count them, never echo the arguments: the cards below the header already show all three.',
|
||
format: 'Write 4 to 9 words, sentence case, no trailing punctuation, no quotes or markdown.',
|
||
good: 'Good: "Confirmed /mnt/data resets between calls". "Traced the leak to formatAgentMessages". "Found 3 failing auth tests".',
|
||
bad: 'Bad: "Ran 1 command". "Used bash_tool twice". "Executed ls /mnt/data". "Searched the codebase".',
|
||
failure: 'If every call failed, say what failed and why, plainly.',
|
||
output: 'Output only the line.',
|
||
};
|
||
|
||
const CONTINUITY_SENTENCE =
|
||
'A "Previous headers" list may precede the batch: never restate one — if this batch continues that activity, say only what is new.';
|
||
|
||
/** Pre-P1 instruction — kept as the `legacy` variant for regression sweeps. */
|
||
const LEGACY_ORDER = [
|
||
S.role,
|
||
S.register,
|
||
S.outcome,
|
||
S.prohibitions,
|
||
S.format,
|
||
S.good,
|
||
S.bad,
|
||
S.failure,
|
||
S.output,
|
||
];
|
||
|
||
/** The shipped instruction (P1): ordered structure + continuity clause. */
|
||
const SHIPPED_ORDER = [
|
||
S.role,
|
||
S.outcome,
|
||
S.register,
|
||
S.good,
|
||
S.bad,
|
||
S.failure,
|
||
CONTINUITY_SENTENCE,
|
||
S.prohibitions,
|
||
S.format,
|
||
S.output,
|
||
];
|
||
|
||
/** `baseline` is whatever the BUILT dist ships. Before the P1 rebuild that is
|
||
* the legacy order, after it the shipped order; anything else means the
|
||
* sentence table here has drifted and composed variants are stale. */
|
||
if (
|
||
LEGACY_ORDER.join(' ') !== ACTIVITY_INSTRUCTION &&
|
||
SHIPPED_ORDER.join(' ') !== ACTIVITY_INSTRUCTION
|
||
) {
|
||
console.warn(
|
||
'WARN: variants.js sentence table has drifted from ACTIVITY_INSTRUCTION — composed variants are stale',
|
||
);
|
||
}
|
||
|
||
const VERB_CHOICE =
|
||
'Open with whichever past-tense verb the outcome dictates — confirmed, found, traced, measured, wrote, ruled out, failed — not the same verb every time.';
|
||
const DIVERSE_GOOD =
|
||
'Good: "Traced the leak to formatAgentMessages". "Ruled out DNS as the failure cause". "Measured cold start at 412ms". "Found 3 failing auth tests".';
|
||
const CONTINUITY =
|
||
'A "Previous headers" list may precede the batch: those lines already stand above earlier groups, so never write a line that merely restates one. If this batch continues that same activity, lead with what is new or different in THIS batch.';
|
||
const CONTINUITY_TIGHT =
|
||
'A "Previous headers" list may precede the batch: never restate one — if this batch continues that activity, say only what is new.';
|
||
const FORMAT_HARD =
|
||
'Write 4 to 9 words, sentence case, no trailing punctuation, no quotes or markdown; when a batch found many things, keep only the most load-bearing one or two.';
|
||
|
||
const variants = [
|
||
{
|
||
name: 'baseline',
|
||
usePreviousLabels: SHIPPED_ORDER.join(' ') === ACTIVITY_INSTRUCTION,
|
||
instruction: ACTIVITY_INSTRUCTION,
|
||
},
|
||
{
|
||
name: 'legacy',
|
||
usePreviousLabels: false,
|
||
instruction: LEGACY_ORDER.join(' '),
|
||
},
|
||
{
|
||
name: 'verbs',
|
||
usePreviousLabels: false,
|
||
instruction: [
|
||
S.role,
|
||
S.register,
|
||
VERB_CHOICE,
|
||
S.outcome,
|
||
S.prohibitions,
|
||
S.format,
|
||
DIVERSE_GOOD,
|
||
S.bad,
|
||
S.failure,
|
||
S.output,
|
||
].join(' '),
|
||
},
|
||
{
|
||
name: 'ordered',
|
||
usePreviousLabels: false,
|
||
instruction: [
|
||
S.role,
|
||
S.outcome,
|
||
S.register,
|
||
S.good,
|
||
S.bad,
|
||
S.failure,
|
||
S.prohibitions,
|
||
S.format,
|
||
S.output,
|
||
].join(' '),
|
||
},
|
||
{
|
||
name: 'continuity',
|
||
usePreviousLabels: true,
|
||
instruction: [
|
||
S.role,
|
||
S.register,
|
||
S.outcome,
|
||
S.prohibitions,
|
||
S.format,
|
||
S.good,
|
||
S.bad,
|
||
S.failure,
|
||
CONTINUITY,
|
||
S.output,
|
||
].join(' '),
|
||
},
|
||
{
|
||
name: 'examples',
|
||
usePreviousLabels: false,
|
||
instruction: [
|
||
S.role,
|
||
S.register,
|
||
S.outcome,
|
||
S.prohibitions,
|
||
S.format,
|
||
DIVERSE_GOOD,
|
||
S.bad,
|
||
S.failure,
|
||
S.output,
|
||
].join(' '),
|
||
},
|
||
{
|
||
name: 'composed',
|
||
usePreviousLabels: true,
|
||
instruction: [
|
||
S.role,
|
||
S.outcome,
|
||
S.register,
|
||
DIVERSE_GOOD,
|
||
S.bad,
|
||
S.failure,
|
||
CONTINUITY_TIGHT,
|
||
S.prohibitions,
|
||
FORMAT_HARD,
|
||
S.output,
|
||
].join(' '),
|
||
},
|
||
{
|
||
name: 'shipping-full',
|
||
usePreviousLabels: true,
|
||
/** Whole-run history instead of the 3-label recency window: does more
|
||
* story beat recency, or does it dilute the batch content? */
|
||
previousLabelCap: Infinity,
|
||
instruction: SHIPPED_ORDER.join(' '),
|
||
},
|
||
{
|
||
name: 'shipping',
|
||
usePreviousLabels: true,
|
||
instruction: [
|
||
S.role,
|
||
S.outcome,
|
||
S.register,
|
||
S.good,
|
||
S.bad,
|
||
S.failure,
|
||
CONTINUITY_TIGHT,
|
||
S.prohibitions,
|
||
S.format,
|
||
S.output,
|
||
].join(' '),
|
||
},
|
||
];
|
||
|
||
module.exports = { variants };
|