fix(recall): reuse user-floor cache without target index

This commit is contained in:
Youzini-afk
2026-04-30 22:17:14 +08:00
parent e4f7959b03
commit 3a157f5fc1
2 changed files with 114 additions and 11 deletions

View File

@@ -94,30 +94,20 @@ function buildPersistedRecallReuseResult(record = {}) {
function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) {
const generationType = String(recallInput?.generationType || "normal").trim() || "normal";
const targetUserMessageIndex = Number.isFinite(recallInput?.targetUserMessageIndex)
let targetUserMessageIndex = Number.isFinite(recallInput?.targetUserMessageIndex)
? Math.floor(Number(recallInput.targetUserMessageIndex))
: null;
if (!Number.isFinite(targetUserMessageIndex)) return null;
const targetMessage = Array.isArray(chat) ? chat[targetUserMessageIndex] : null;
if (!targetMessage?.is_user) return null;
const readPersistedRecallFromUserMessage = runtime.readPersistedRecallFromUserMessage;
if (typeof readPersistedRecallFromUserMessage !== "function") return null;
const record = readPersistedRecallFromUserMessage(chat, targetUserMessageIndex);
if (!record?.injectionText) return null;
const normalizeText = (value = "") =>
typeof runtime.normalizeRecallInputText === "function"
? runtime.normalizeRecallInputText(value)
: String(value ?? "")
.replace(/\r\n/g, "\n")
.trim();
const currentUserFloorText = normalizeText(targetMessage?.mes || "");
const currentRecallInputText = normalizeText(recallInput?.userMessage || "");
const recordRecallInput = normalizeText(record?.recallInput || "");
const boundUserFloorText = normalizeText(record?.boundUserFloorText || "");
const recallSource = String(recallInput?.source || "").trim();
const activeInputSources = new Set([
"send-intent",
@@ -129,6 +119,50 @@ function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) {
]);
const isActiveInputSource = activeInputSources.has(recallSource);
if (!Number.isFinite(targetUserMessageIndex)) {
if (!currentRecallInputText || isActiveInputSource || !Array.isArray(chat)) {
return null;
}
for (let index = chat.length - 1; index >= 0; index--) {
const message = chat[index];
if (!message?.is_user) continue;
const candidateRecord = readPersistedRecallFromUserMessage(chat, index);
if (!candidateRecord?.injectionText) continue;
const candidateUserFloorText = normalizeText(message?.mes || "");
const candidateBoundUserFloorText = normalizeText(
candidateRecord?.boundUserFloorText || "",
);
if (
candidateBoundUserFloorText &&
candidateUserFloorText !== candidateBoundUserFloorText
) {
continue;
}
const candidateRecallInput = normalizeText(candidateRecord?.recallInput || "");
if (
currentRecallInputText === candidateUserFloorText ||
(candidateBoundUserFloorText &&
currentRecallInputText === candidateBoundUserFloorText) ||
(candidateRecallInput && currentRecallInputText === candidateRecallInput)
) {
targetUserMessageIndex = index;
break;
}
}
}
if (!Number.isFinite(targetUserMessageIndex)) return null;
const targetMessage = Array.isArray(chat) ? chat[targetUserMessageIndex] : null;
if (!targetMessage?.is_user) return null;
const record = readPersistedRecallFromUserMessage(chat, targetUserMessageIndex);
if (!record?.injectionText) return null;
const currentUserFloorText = normalizeText(targetMessage?.mes || "");
const recordRecallInput = normalizeText(record?.recallInput || "");
const boundUserFloorText = normalizeText(record?.boundUserFloorText || "");
const matchesBoundUserFloor = Boolean(
currentUserFloorText &&
boundUserFloorText &&

View File

@@ -708,4 +708,73 @@ assert.equal(
console.log(" ✓ runRecallController reuses user-floor record with empty recallInput");
// ═══════════════════════════════════════════════════════════════
// 5. runRecallController: normal generation below an assistant reuses user-floor record
// ═══════════════════════════════════════════════════════════════
const assistantTailChat = [
{ is_user: true, mes: "今晚去海边看烟花" },
{ is_user: false, mes: "好,我会准备好相机。", is_system: false },
];
const assistantTailRecord = buildPersistedRecallRecord({
injectionText: "注入:今晚去海边看烟花",
selectedNodeIds: ["node-fireworks"],
recallInput: "今晚去海边看烟花",
recallSource: "chat-latest-user",
hookName: "GENERATION_AFTER_COMMANDS",
tokenEstimate: 4,
manuallyEdited: false,
boundUserFloorText: "今晚去海边看烟花",
});
writePersistedRecallToUserMessage(assistantTailChat, 0, assistantTailRecord);
let assistantTailRetrieveCalled = false;
const assistantTailRuntime = {
...rerollRuntime,
getContext: () => ({ chat: assistantTailChat, chatId: "chat-assistant-tail" }),
readPersistedRecallFromUserMessage,
retrieve: async () => {
assistantTailRetrieveCalled = true;
return {
injectionText: "fresh recall should not run",
selectedNodeIds: ["node-fresh"],
};
},
resolveRecallInput: (chat, limit, override) => ({
userMessage: normalizeRecallInputText(
override?.overrideUserMessage || override?.userMessage || "今晚去海边看烟花",
),
generationType: String(override?.generationType || "normal"),
targetUserMessageIndex: Number.isFinite(override?.targetUserMessageIndex)
? override.targetUserMessageIndex
: null,
source: override?.overrideSource || "chat-latest-user",
sourceLabel: override?.overrideSourceLabel || "最近用户消息",
reason: "assistant-tail-normal-generation",
authoritativeInputUsed: Boolean(override?.authoritativeInputUsed),
boundUserFloorText: normalizeRecallInputText(
override?.boundUserFloorText || "今晚去海边看烟花",
),
recentMessages: [],
hookName: override?.hookName || "",
deliveryMode: "immediate",
}),
};
const assistantTailResult = await runRecallController(assistantTailRuntime, {
overrideUserMessage: "今晚去海边看烟花",
generationType: "normal",
overrideSource: "chat-latest-user",
hookName: "GENERATION_AFTER_COMMANDS",
deliveryMode: "immediate",
});
assert.equal(assistantTailResult.status, "completed");
assert.equal(
assistantTailRetrieveCalled,
false,
"normal generation below an assistant should find and reuse the matching user-floor persisted recall",
);
assert.equal(assistantTailResult.reason, "persisted-user-floor-reused");
console.log(" ✓ runRecallController reuses user-floor record below assistant tail");
console.log("recall-reroll-reuse tests passed");