mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
- buildExtractionMessages marks messages with isContextOnly (seq < startIdx) - formatExtractionTranscript inserts section dividers for mixed context/target - extractor.js Layer 1 prompt splits context review vs extraction target with guidance - Add tests/extraction-context-only-flag.mjs (7 test cases)
141 lines
5.7 KiB
JavaScript
141 lines
5.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import {
|
|
buildExtractionMessages,
|
|
} from "../maintenance/chat-history.js";
|
|
import {
|
|
buildExtractionInputContext,
|
|
formatExtractionTranscript,
|
|
} from "../maintenance/extraction-context.js";
|
|
|
|
// ─── buildExtractionMessages: isContextOnly flag ───
|
|
|
|
const chat = [
|
|
{ is_user: false, is_system: true, mes: "greeting" },
|
|
{ is_user: true, is_system: false, mes: "user-1" },
|
|
{ is_user: false, is_system: false, mes: "assistant-1" },
|
|
{ is_user: true, is_system: false, mes: "user-2" },
|
|
{ is_user: false, is_system: false, mes: "assistant-2" },
|
|
{ is_user: true, is_system: false, mes: "user-3" },
|
|
{ is_user: false, is_system: false, mes: "assistant-3" },
|
|
];
|
|
|
|
{
|
|
const messages = buildExtractionMessages(chat, 4, 6, {
|
|
extractContextTurns: 2,
|
|
});
|
|
const contextOnly = messages.filter((m) => m.isContextOnly);
|
|
const target = messages.filter((m) => !m.isContextOnly);
|
|
|
|
assert.ok(
|
|
contextOnly.length > 0,
|
|
"should have context-only messages when extractContextTurns > 0",
|
|
);
|
|
assert.ok(
|
|
target.length > 0,
|
|
"should have extraction target messages",
|
|
);
|
|
assert.ok(
|
|
contextOnly.every((m) => m.seq < 4),
|
|
"context-only messages should have seq < startIdx",
|
|
);
|
|
assert.ok(
|
|
target.every((m) => m.seq >= 4),
|
|
"target messages should have seq >= startIdx",
|
|
);
|
|
console.log(" ✓ buildExtractionMessages: isContextOnly flag marks context vs target");
|
|
}
|
|
|
|
{
|
|
const messages = buildExtractionMessages(chat, 2, 6, {
|
|
extractContextTurns: 0,
|
|
});
|
|
const contextOnly = messages.filter((m) => m.isContextOnly);
|
|
assert.equal(
|
|
contextOnly.length,
|
|
0,
|
|
"no context-only messages when extractContextTurns=0 and startIdx=2",
|
|
);
|
|
console.log(" ✓ buildExtractionMessages: no context-only when contextTurns=0");
|
|
}
|
|
|
|
{
|
|
const messages = buildExtractionMessages(chat, 1, 6, {
|
|
extractContextTurns: 2,
|
|
});
|
|
const contextOnly = messages.filter((m) => m.isContextOnly);
|
|
assert.equal(
|
|
contextOnly.length,
|
|
0,
|
|
"no context-only when startIdx is already at the beginning",
|
|
);
|
|
console.log(" ✓ buildExtractionMessages: no context-only when startIdx at beginning");
|
|
}
|
|
|
|
// ─── formatExtractionTranscript: section dividers ───
|
|
|
|
{
|
|
const mixed = [
|
|
{ seq: 1, role: "user", content: "context user", speaker: "A", isContextOnly: true },
|
|
{ seq: 2, role: "assistant", content: "context ai", speaker: "B", isContextOnly: true },
|
|
{ seq: 3, role: "user", content: "target user", speaker: "A", isContextOnly: false },
|
|
{ seq: 4, role: "assistant", content: "target ai", speaker: "B", isContextOnly: false },
|
|
];
|
|
const transcript = formatExtractionTranscript(mixed);
|
|
assert.match(transcript, /已提取过/, "transcript should contain context review header");
|
|
assert.match(transcript, /本次需要提取/, "transcript should contain extraction target header");
|
|
assert.ok(
|
|
transcript.indexOf("已提取过") < transcript.indexOf("本次需要提取"),
|
|
"context header should appear before target header",
|
|
);
|
|
assert.match(transcript, /#1.*context user/, "context message should appear");
|
|
assert.match(transcript, /#3.*target user/, "target message should appear");
|
|
console.log(" ✓ formatExtractionTranscript: section dividers for mixed context/target");
|
|
}
|
|
|
|
{
|
|
const allTarget = [
|
|
{ seq: 3, role: "user", content: "user msg", speaker: "A", isContextOnly: false },
|
|
{ seq: 4, role: "assistant", content: "ai msg", speaker: "B", isContextOnly: false },
|
|
];
|
|
const transcript = formatExtractionTranscript(allTarget);
|
|
assert.doesNotMatch(transcript, /已提取过/, "no context header when all are target");
|
|
assert.doesNotMatch(transcript, /本次需要提取/, "no target header when all are target");
|
|
console.log(" ✓ formatExtractionTranscript: no dividers when all messages are targets");
|
|
}
|
|
|
|
{
|
|
const allContext = [
|
|
{ seq: 1, role: "user", content: "user msg", speaker: "A", isContextOnly: true },
|
|
{ seq: 2, role: "assistant", content: "ai msg", speaker: "B", isContextOnly: true },
|
|
];
|
|
const transcript = formatExtractionTranscript(allContext);
|
|
assert.doesNotMatch(transcript, /已提取过/, "no dividers when all are context-only");
|
|
assert.doesNotMatch(transcript, /本次需要提取/, "no dividers when all are context-only");
|
|
console.log(" ✓ formatExtractionTranscript: no dividers when all messages are context-only");
|
|
}
|
|
|
|
// ─── buildExtractionInputContext: isContextOnly propagation ───
|
|
|
|
{
|
|
const inputMessages = [
|
|
{ seq: 1, role: "user", content: "old question", name: "A", speaker: "A", isContextOnly: true },
|
|
{ seq: 2, role: "assistant", content: "old answer", name: "B", speaker: "B", isContextOnly: true },
|
|
{ seq: 3, role: "user", content: "new question", name: "A", speaker: "A", isContextOnly: false },
|
|
{ seq: 4, role: "assistant", content: "new answer", name: "B", speaker: "B", isContextOnly: false },
|
|
];
|
|
const result = buildExtractionInputContext(inputMessages, {
|
|
settings: {},
|
|
userName: "A",
|
|
charName: "B",
|
|
});
|
|
const contextFiltered = result.filteredMessages.filter((m) => m.isContextOnly);
|
|
const targetFiltered = result.filteredMessages.filter((m) => !m.isContextOnly);
|
|
assert.equal(contextFiltered.length, 2, "context messages propagated through filtering");
|
|
assert.equal(targetFiltered.length, 2, "target messages propagated through filtering");
|
|
assert.match(result.filteredTranscript, /已提取过/, "transcript includes context header");
|
|
assert.match(result.filteredTranscript, /本次需要提取/, "transcript includes target header");
|
|
console.log(" ✓ buildExtractionInputContext: isContextOnly propagated to filteredMessages and transcript");
|
|
}
|
|
|
|
console.log("extraction-context-only-flag tests passed");
|