Merge pull request #2468 from SillyTavern/wi-scan-state Fix min activations for non-recursable entries

81f65203540ffda9b70b61732d2978475acff4b6

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +78 -22Ignore whitespace
public/scripts/world-info.js+78 -22
@@ -50,6 +50,28 @@ const world_info_logic = {
50 AND_ALL: 3,50 AND_ALL: 3,
51};51};
5252
53/**
54 * @enum {number} Possible states of the WI evaluation
55 */
56const scan_state = {
57 /**
58 * The scan will be stopped.
59 */
60 NONE: 0,
61 /**
62 * Initial state.
63 */
64 INITIAL: 1,
65 /**
66 * The scan is triggered by a recursion step.
67 */
68 RECURSION: 2,
69 /**
70 * The scan is triggered by a min activations depth skew.
71 */
72 MIN_ACTIVATIONS: 2,
73};
74
53const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');75const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
5476
55let world_info = {};77let world_info = {};
@@ -136,6 +158,11 @@ class WorldInfoBuffer {
136 #recurseBuffer = [];158 #recurseBuffer = [];
137159
138 /**160 /**
161 * @type {string[]} Array of strings added by prompt injections that are valid for the current scan
162 */
163 #injectBuffer = [];
164
165 /**
139 * @type {number} The skew of the global scan depth. Used in "min activations"166 * @type {number} The skew of the global scan depth. Used in "min activations"
140 */167 */
141 #skew = 0;168 #skew = 0;
@@ -184,9 +211,10 @@ class WorldInfoBuffer {
184 /**211 /**
185 * Gets all messages up to the given depth + recursion buffer.212 * Gets all messages up to the given depth + recursion buffer.
186 * @param {WIScanEntry} entry The entry that triggered the scan213 * @param {WIScanEntry} entry The entry that triggered the scan
214 * @param {number} scanState The state of the scan
187 * @returns {string} A slice of buffer until the given depth (inclusive)215 * @returns {string} A slice of buffer until the given depth (inclusive)
188 */216 */
189 get(entry) {217 get(entry, scanState) {
190 let depth = entry.scanDepth ?? this.getDepth();218 let depth = entry.scanDepth ?? this.getDepth();
191 if (depth <= this.#startDepth) {219 if (depth <= this.#startDepth) {
192 return '';220 return '';
@@ -204,7 +232,12 @@ class WorldInfoBuffer {
204232
205 let result = this.#depthBuffer.slice(this.#startDepth, depth).join('\n');233 let result = this.#depthBuffer.slice(this.#startDepth, depth).join('\n');
206234
207 if (this.#recurseBuffer.length > 0) {235 if (this.#injectBuffer.length > 0) {
236 result += '\n' + this.#injectBuffer.join('\n');
237 }
238
239 // Min activations should not include the recursion buffer
240 if (this.#recurseBuffer.length > 0 && scanState !== scan_state.MIN_ACTIVATIONS) {
208 result += '\n' + this.#recurseBuffer.join('\n');241 result += '\n' + this.#recurseBuffer.join('\n');
209 }242 }
210243
@@ -259,6 +292,14 @@ class WorldInfoBuffer {
259 }292 }
260293
261 /**294 /**
295 * Adds an injection to the buffer.
296 * @param {string} message The injection to add
297 */
298 addInject(message) {
299 this.#injectBuffer.push(message);
300 }
301
302 /**
262 * Increments skew and sets startDepth to previous depth.303 * Increments skew and sets startDepth to previous depth.
263 */304 */
264 advanceScanPosition() {305 advanceScanPosition() {
@@ -293,10 +334,11 @@ class WorldInfoBuffer {
293 /**334 /**
294 * Gets the match score for the given entry.335 * Gets the match score for the given entry.
295 * @param {WIScanEntry} entry Entry to check336 * @param {WIScanEntry} entry Entry to check
337 * @param {number} scanState The state of the scan
296 * @returns {number} The number of key activations for the given entry338 * @returns {number} The number of key activations for the given entry
297 */339 */
298 getScore(entry) {340 getScore(entry, scanState) {
299 const bufferState = this.get(entry);341 const bufferState = this.get(entry, scanState);
300 let numberOfPrimaryKeys = 0;342 let numberOfPrimaryKeys = 0;
301 let numberOfSecondaryKeys = 0;343 let numberOfSecondaryKeys = 0;
302 let primaryScore = 0;344 let primaryScore = 0;
@@ -3503,12 +3545,12 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3503 if (context.extensionPrompts[key]?.scan) {3545 if (context.extensionPrompts[key]?.scan) {
3504 const prompt = getExtensionPromptByName(key);3546 const prompt = getExtensionPromptByName(key);
3505 if (prompt) {3547 if (prompt) {
3506 buffer.addRecurse(prompt);3548 buffer.addInject(prompt);
3507 }3549 }
3508 }3550 }
3509 }3551 }
35103552
3511 let needsToScan = true;3553 let scanState = scan_state.INITIAL;
3512 let token_budget_overflowed = false;3554 let token_budget_overflowed = false;
3513 let count = 0;3555 let count = 0;
3514 let allActivatedEntries = new Set();3556 let allActivatedEntries = new Set();
@@ -3532,8 +3574,9 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3532 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };3574 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
3533 }3575 }
35343576
3535 while (needsToScan) {3577 while (scanState) {
3536 // Track how many times the loop has run3578 // Track how many times the loop has run. May be useful for debugging.
3579 // eslint-disable-next-line no-unused-vars
3537 count++;3580 count++;
35383581
3539 let activatedNow = new Set();3582 let activatedNow = new Set();
@@ -3587,7 +3630,18 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3587 continue;3630 continue;
3588 }3631 }
35893632
3590 if (allActivatedEntries.has(entry) || entry.disable == true || (count > 1 && world_info_recursive && entry.excludeRecursion) || (count == 1 && entry.delayUntilRecursion)) {3633 if (allActivatedEntries.has(entry) || entry.disable == true) {
3634 continue;
3635 }
3636
3637 // Only use checks for recursion flags if the scan step was activated by recursion
3638 if (scanState !== scan_state.RECURSION && entry.delayUntilRecursion) {
3639 console.debug(`WI entry ${entry.uid} suppressed by delay until recursion`, entry);
3640 continue;
3641 }
3642
3643 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion) {
3644 console.debug(`WI entry ${entry.uid} suppressed by exclude recursion`, entry);
3591 continue;3645 continue;
3592 }3646 }
35933647
@@ -3602,7 +3656,7 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
36023656
3603 primary: for (let key of entry.key) {3657 primary: for (let key of entry.key) {
3604 const substituted = substituteParams(key);3658 const substituted = substituteParams(key);
3605 const textToScan = buffer.get(entry);3659 const textToScan = buffer.get(entry, scanState);
36063660
3607 if (substituted && buffer.matchKeys(textToScan, substituted.trim(), entry)) {3661 if (substituted && buffer.matchKeys(textToScan, substituted.trim(), entry)) {
3608 console.debug(`WI UID ${entry.uid} found by primary match: ${substituted}.`);3662 console.debug(`WI UID ${entry.uid} found by primary match: ${substituted}.`);
@@ -3665,14 +3719,14 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3665 }3719 }
3666 }3720 }
36673721
3668 needsToScan = world_info_recursive && activatedNow.size > 0;3722 scanState = world_info_recursive && activatedNow.size > 0 ? scan_state.RECURSION : scan_state.NONE;
3669 const newEntries = [...activatedNow]3723 const newEntries = [...activatedNow]
3670 .sort((a, b) => sortedEntries.indexOf(a) - sortedEntries.indexOf(b));3724 .sort((a, b) => sortedEntries.indexOf(a) - sortedEntries.indexOf(b));
3671 let newContent = '';3725 let newContent = '';
3672 const textToScanTokens = await getTokenCountAsync(allActivatedText);3726 const textToScanTokens = await getTokenCountAsync(allActivatedText);
3673 const probabilityChecksBefore = failedProbabilityChecks.size;3727 const probabilityChecksBefore = failedProbabilityChecks.size;
36743728
3675 filterByInclusionGroups(newEntries, allActivatedEntries, buffer);3729 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState);
36763730
3677 console.debug('-- PROBABILITY CHECKS BEGIN --');3731 console.debug('-- PROBABILITY CHECKS BEGIN --');
3678 for (const entry of newEntries) {3732 for (const entry of newEntries) {
@@ -3697,7 +3751,7 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3697 console.log('Alerting');3751 console.log('Alerting');
3698 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');3752 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
3699 }3753 }
3700 needsToScan = false;3754 scanState = scan_state.NONE;
3701 token_budget_overflowed = true;3755 token_budget_overflowed = true;
3702 break;3756 break;
3703 }3757 }
@@ -3710,15 +3764,15 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
37103764
3711 if ((probabilityChecksAfter - probabilityChecksBefore) === activatedNow.size) {3765 if ((probabilityChecksAfter - probabilityChecksBefore) === activatedNow.size) {
3712 console.debug('WI probability checks failed for all activated entries, stopping');3766 console.debug('WI probability checks failed for all activated entries, stopping');
3713 needsToScan = false;3767 scanState = scan_state.NONE;
3714 }3768 }
37153769
3716 if (newEntries.length === 0) {3770 if (newEntries.length === 0) {
3717 console.debug('No new entries activated, stopping');3771 console.debug('No new entries activated, stopping');
3718 needsToScan = false;3772 scanState = scan_state.NONE;
3719 }3773 }
37203774
3721 if (needsToScan) {3775 if (scanState) {
3722 const text = newEntries3776 const text = newEntries
3723 .filter(x => !failedProbabilityChecks.has(x))3777 .filter(x => !failedProbabilityChecks.has(x))
3724 .filter(x => !x.preventRecursion)3778 .filter(x => !x.preventRecursion)
@@ -3728,7 +3782,7 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3728 }3782 }
37293783
3730 // world_info_min_activations3784 // world_info_min_activations
3731 if (!needsToScan && !token_budget_overflowed) {3785 if (!scanState && !token_budget_overflowed) {
3732 if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) {3786 if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) {
3733 let over_max = (3787 let over_max = (
3734 world_info_min_activations_depth_max > 0 &&3788 world_info_min_activations_depth_max > 0 &&
@@ -3736,7 +3790,7 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3736 ) || (buffer.getDepth() > chat.length);3790 ) || (buffer.getDepth() > chat.length);
37373791
3738 if (!over_max) {3792 if (!over_max) {
3739 needsToScan = true; // loop3793 scanState = scan_state.MIN_ACTIVATIONS; // loop
3740 buffer.advanceScanPosition();3794 buffer.advanceScanPosition();
3741 }3795 }
3742 }3796 }
@@ -3824,8 +3878,9 @@ async function checkWorldInfo(chat, maxContext, isDryRun) {
3824 * @param {Record<string, WIScanEntry[]>} groups The groups to filter3878 * @param {Record<string, WIScanEntry[]>} groups The groups to filter
3825 * @param {WorldInfoBuffer} buffer The buffer to use for scoring3879 * @param {WorldInfoBuffer} buffer The buffer to use for scoring
3826 * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry3880 * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry
3881 * @param {number} scanState The current scan state
3827 */3882 */
3828function filterGroupsByScoring(groups, buffer, removeEntry) {3883function filterGroupsByScoring(groups, buffer, removeEntry, scanState) {
3829 for (const [key, group] of Object.entries(groups)) {3884 for (const [key, group] of Object.entries(groups)) {
3830 // Group scoring is disabled both globally and for the group entries3885 // Group scoring is disabled both globally and for the group entries
3831 if (!world_info_use_group_scoring && !group.some(x => x.useGroupScoring)) {3886 if (!world_info_use_group_scoring && !group.some(x => x.useGroupScoring)) {
@@ -3833,7 +3888,7 @@ function filterGroupsByScoring(groups, buffer, removeEntry) {
3833 continue;3888 continue;
3834 }3889 }
38353890
3836 const scores = group.map(entry => buffer.getScore(entry));3891 const scores = group.map(entry => buffer.getScore(entry, scanState));
3837 const maxScore = Math.max(...scores);3892 const maxScore = Math.max(...scores);
3838 console.debug(`Group '${key}' max score: ${maxScore}`);3893 console.debug(`Group '${key}' max score: ${maxScore}`);
3839 //console.table(group.map((entry, i) => ({ uid: entry.uid, key: JSON.stringify(entry.key), score: scores[i] })));3894 //console.table(group.map((entry, i) => ({ uid: entry.uid, key: JSON.stringify(entry.key), score: scores[i] })));
@@ -3861,8 +3916,9 @@ function filterGroupsByScoring(groups, buffer, removeEntry) {
3861 * @param {object[]} newEntries Entries activated on current recursion level3916 * @param {object[]} newEntries Entries activated on current recursion level
3862 * @param {Set<object>} allActivatedEntries Set of all activated entries3917 * @param {Set<object>} allActivatedEntries Set of all activated entries
3863 * @param {WorldInfoBuffer} buffer The buffer to use for scanning3918 * @param {WorldInfoBuffer} buffer The buffer to use for scanning
3919 * @param {number} scanState The current scan state
3864 */3920 */
3865function filterByInclusionGroups(newEntries, allActivatedEntries, buffer) {3921function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState) {
3866 console.debug('-- INCLUSION GROUP CHECKS BEGIN --');3922 console.debug('-- INCLUSION GROUP CHECKS BEGIN --');
3867 const grouped = newEntries.filter(x => x.group).reduce((acc, item) => {3923 const grouped = newEntries.filter(x => x.group).reduce((acc, item) => {
3868 item.group.split(/,\s*/).filter(x => x).forEach(group => {3924 item.group.split(/,\s*/).filter(x => x).forEach(group => {
@@ -3891,7 +3947,7 @@ function filterByInclusionGroups(newEntries, allActivatedEntries, buffer) {
3891 }3947 }
3892 }3948 }
38933949
3894 filterGroupsByScoring(grouped, buffer, removeEntry);3950 filterGroupsByScoring(grouped, buffer, removeEntry, scanState);
38953951
3896 for (const [key, group] of Object.entries(grouped)) {3952 for (const [key, group] of Object.entries(grouped)) {
3897 console.debug(`Checking inclusion group '${key}' with ${group.length} entries`, group);3953 console.debug(`Checking inclusion group '${key}' with ${group.length} entries`, group);