diff --git a/retrieval/recall-controller.js b/retrieval/recall-controller.js index 3018c87..e1fc4b1 100644 --- a/retrieval/recall-controller.js +++ b/retrieval/recall-controller.js @@ -91,40 +91,81 @@ function buildPersistedRecallReuseResult(record = {}) { }; } -function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) { - const generationType = String(recallInput?.generationType || "normal").trim() || "normal"; +export function normalizeRecallGenerationType(value = "normal") { + return String(value || "normal").trim() || "normal"; +} - let targetUserMessageIndex = Number.isFinite(recallInput?.targetUserMessageIndex) - ? Math.floor(Number(recallInput.targetUserMessageIndex)) - : null; +export function normalizeRecallTargetUserMessageIndex(value) { + return Number.isFinite(value) ? Math.floor(Number(value)) : null; +} - const readPersistedRecallFromUserMessage = runtime.readPersistedRecallFromUserMessage; - if (typeof readPersistedRecallFromUserMessage !== "function") return null; +export function normalizeRecallTextForRuntime(runtime, value = "") { + return typeof runtime?.normalizeRecallInputText === "function" + ? runtime.normalizeRecallInputText(value) + : String(value ?? "") + .replace(/\r\n/g, "\n") + .trim(); +} - const normalizeText = (value = "") => - typeof runtime.normalizeRecallInputText === "function" - ? runtime.normalizeRecallInputText(value) - : String(value ?? "") - .replace(/\r\n/g, "\n") - .trim(); - const currentRecallInputText = normalizeText(recallInput?.userMessage || ""); - const recallSource = String(recallInput?.source || "").trim(); - const activeInputSources = new Set([ +export function isActiveRecallInputSource(source = "") { + return new Set([ "send-intent", "generation-started-send-intent", "generation-started-textarea", "host-generation-lifecycle", "textarea-live", "planner-handoff", - ]); - const isActiveInputSource = activeInputSources.has(recallSource); - const noNewUserGenerationTypes = new Set([ - "swipe", - "regenerate", - "continue", - "history", - ]); - const isNoNewUserGeneration = noNewUserGenerationTypes.has(generationType); + ]).has(String(source || "").trim()); +} + +export function isNoNewUserGenerationType(generationType = "normal") { + return new Set(["swipe", "regenerate", "continue", "history"]).has( + normalizeRecallGenerationType(generationType), + ); +} + +export function isTrustedUserFloorRecallSource(source = "") { + return new Set([ + "chat-last-user", + "chat-latest-user", + "chat-tail-user", + "message-sent", + "persisted-user-floor", + ]).has(String(source || "").trim()); +} + +export function buildPersistedReuseRecallInput(recallInput = {}, record = {}, runtime) { + const boundUserFloorText = normalizeRecallTextForRuntime( + runtime, + record.boundUserFloorText || recallInput.boundUserFloorText || "", + ); + return { + ...recallInput, + source: "persisted-user-floor", + sourceLabel: "复用用户楼层召回", + reason: "persisted-user-floor-reuse", + authoritativeInputUsed: Boolean( + record.authoritativeInputUsed || recallInput.authoritativeInputUsed, + ), + boundUserFloorText, + }; +} + +function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) { + const generationType = normalizeRecallGenerationType(recallInput?.generationType); + + let targetUserMessageIndex = normalizeRecallTargetUserMessageIndex( + recallInput?.targetUserMessageIndex, + ); + + const readPersistedRecallFromUserMessage = runtime.readPersistedRecallFromUserMessage; + if (typeof readPersistedRecallFromUserMessage !== "function") return null; + + const normalizeText = (value = "") => normalizeRecallTextForRuntime(runtime, value); + const currentRecallInputText = normalizeText(recallInput?.userMessage || ""); + const recallSource = String(recallInput?.source || "").trim(); + const isActiveInputSource = isActiveRecallInputSource(recallSource); + const isNoNewUserGeneration = isNoNewUserGenerationType(generationType); if (isActiveInputSource && !isNoNewUserGeneration) { return null; } @@ -213,18 +254,11 @@ function resolveReusablePersistedRecallRecord(chat, recallInput, runtime) { !isActiveInputSource && String(record?.injectionText || "").trim(), ); - const userFloorSources = new Set([ - "chat-last-user", - "chat-latest-user", - "chat-tail-user", - "message-sent", - "persisted-user-floor", - ]); const canTrustUserFloorRecord = Boolean( (!isActiveInputSource || isNoNewUserGeneration) && !boundUserFloorText && !recordRecallInputMismatch && - (generationType !== "normal" || userFloorSources.has(recallSource)), + (generationType !== "normal" || isTrustedUserFloorRecallSource(recallSource)), ); if ( @@ -618,31 +652,11 @@ export async function runRecallController(runtime, options = {}) { runtime, ); if (earlyPersistedReuse) { - const normalizedBoundUserFloorText = - typeof runtime.normalizeRecallInputText === "function" - ? runtime.normalizeRecallInputText( - earlyPersistedReuse.record.boundUserFloorText || - recallInput.boundUserFloorText || - "", - ) - : String( - earlyPersistedReuse.record.boundUserFloorText || - recallInput.boundUserFloorText || - "", - ) - .replace(/\r\n/g, "\n") - .trim(); - const effectiveRecallInput = { - ...recallInput, - source: "persisted-user-floor", - sourceLabel: "复用用户楼层召回", - reason: "persisted-user-floor-reuse", - authoritativeInputUsed: Boolean( - earlyPersistedReuse.record.authoritativeInputUsed || - recallInput.authoritativeInputUsed, - ), - boundUserFloorText: normalizedBoundUserFloorText, - }; + const effectiveRecallInput = buildPersistedReuseRecallInput( + recallInput, + earlyPersistedReuse.record, + runtime, + ); const reusedResult = buildPersistedRecallReuseResult(earlyPersistedReuse.record); const applied = runtime.applyRecallInjection( settings, diff --git a/tests/recall-controller-helpers.mjs b/tests/recall-controller-helpers.mjs new file mode 100644 index 0000000..d56521c --- /dev/null +++ b/tests/recall-controller-helpers.mjs @@ -0,0 +1,89 @@ +import assert from 'node:assert/strict'; + +import { + buildPersistedReuseRecallInput, + isActiveRecallInputSource, + isNoNewUserGenerationType, + isTrustedUserFloorRecallSource, + normalizeRecallGenerationType, + normalizeRecallTargetUserMessageIndex, + normalizeRecallTextForRuntime, +} from '../retrieval/recall-controller.js'; + +assert.equal(normalizeRecallGenerationType(' regenerate '), 'regenerate'); +assert.equal(normalizeRecallGenerationType(''), 'normal'); +assert.equal(normalizeRecallGenerationType(null), 'normal'); + +assert.equal(normalizeRecallTargetUserMessageIndex(3.9), 3); +assert.equal(normalizeRecallTargetUserMessageIndex(Number.NaN), null); +assert.equal(normalizeRecallTargetUserMessageIndex('3'), null); + +assert.equal(normalizeRecallTextForRuntime(null, ' a\r\nb '), 'a\nb'); +assert.equal( + normalizeRecallTextForRuntime({ normalizeRecallInputText: (value) => `x:${String(value).trim()}` }, ' a '), + 'x:a', +); + +for (const source of [ + 'send-intent', + 'generation-started-send-intent', + 'generation-started-textarea', + 'host-generation-lifecycle', + 'textarea-live', + 'planner-handoff', +]) { + assert.equal(isActiveRecallInputSource(source), true, source); +} +assert.equal(isActiveRecallInputSource('chat-last-user'), false); + +for (const generationType of ['swipe', 'regenerate', 'continue', 'history']) { + assert.equal(isNoNewUserGenerationType(generationType), true, generationType); +} +assert.equal(isNoNewUserGenerationType('normal'), false); + +for (const source of [ + 'chat-last-user', + 'chat-latest-user', + 'chat-tail-user', + 'message-sent', + 'persisted-user-floor', +]) { + assert.equal(isTrustedUserFloorRecallSource(source), true, source); +} +assert.equal(isTrustedUserFloorRecallSource('textarea-live'), false); + +{ + const recallInput = { + source: 'chat-last-user', + sourceLabel: '历史最后用户楼层', + reason: 'chat-tail-fallback', + authoritativeInputUsed: false, + boundUserFloorText: ' fallback floor ', + deliveryMode: 'deferred', + }; + const record = { + authoritativeInputUsed: true, + boundUserFloorText: ' persisted floor ', + }; + const result = buildPersistedReuseRecallInput(recallInput, record, { + normalizeRecallInputText: (value) => String(value || '').trim().toUpperCase(), + }); + assert.equal(result.source, 'persisted-user-floor'); + assert.equal(result.sourceLabel, '复用用户楼层召回'); + assert.equal(result.reason, 'persisted-user-floor-reuse'); + assert.equal(result.authoritativeInputUsed, true); + assert.equal(result.boundUserFloorText, 'PERSISTED FLOOR'); + assert.equal(result.deliveryMode, 'deferred'); +} + +{ + const result = buildPersistedReuseRecallInput( + { authoritativeInputUsed: true, boundUserFloorText: 'input\r\ntext' }, + {}, + null, + ); + assert.equal(result.authoritativeInputUsed, true); + assert.equal(result.boundUserFloorText, 'input\ntext'); +} + +console.log('recall-controller-helpers tests passed');