Summarize: configurable system/user role for the summary prompt Reviewed-by: auto-review
| @@ -136,8 +136,20 @@ const defaultSettings = { | ||
| 136 | 136 | maxMessagesPerRequestMax: 250, |
| 137 | 137 | maxMessagesPerRequestStep: 1, |
| 138 | 138 | prompt_builder: prompt_builders.DEFAULT, |
| 139 | + summaryPromptRole: extension_prompt_roles.SYSTEM, | |
| 139 | 140 | }; |
| 140 | 141 | |
| 142 | +/** | |
| 143 | + * Resolve the configured role for the summarization request prompt. | |
| 144 | + * Only SYSTEM and USER are supported; anything else falls back to SYSTEM. | |
| 145 | + * @returns {number} One of extension_prompt_roles.SYSTEM or extension_prompt_roles.USER | |
| 146 | + */ | |
| 147 | +function getSummaryPromptRole() { | |
| 148 | + return Number(extension_settings.memory.summaryPromptRole) === extension_prompt_roles.USER | |
| 149 | + ? extension_prompt_roles.USER | |
| 150 | + : extension_prompt_roles.SYSTEM; | |
| 151 | +} | |
| 152 | + | |
| 141 | 153 | function loadSettings() { |
| 142 | 154 | if (Object.keys(extension_settings.memory).length === 0) { |
| 143 | 155 | Object.assign(extension_settings.memory, defaultSettings); |
| @@ -158,6 +170,7 @@ function loadSettings() { | ||
| 158 | 170 | $('#memory_template').val(extension_settings.memory.template).trigger('input'); |
| 159 | 171 | $('#memory_depth').val(extension_settings.memory.depth).trigger('input'); |
| 160 | 172 | $('#memory_role').val(extension_settings.memory.role).trigger('input'); |
| 173 | + $('#memory_summary_prompt_role').val(extension_settings.memory.summaryPromptRole).trigger('input'); | |
| 161 | 174 | $(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input'); |
| 162 | 175 | $('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input'); |
| 163 | 176 | $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); |
| @@ -314,6 +327,12 @@ function onMemoryRoleInput() { | ||
| 314 | 327 | saveSettingsDebounced(); |
| 315 | 328 | } |
| 316 | 329 | |
| 330 | +function onMemorySummaryPromptRoleInput() { | |
| 331 | + const value = $(this).val(); | |
| 332 | + extension_settings.memory.summaryPromptRole = Number(value); | |
| 333 | + saveSettingsDebounced(); | |
| 334 | +} | |
| 335 | + | |
| 317 | 336 | function onMemoryPositionChange(e) { |
| 318 | 337 | const value = e.target.value; |
| 319 | 338 | extension_settings.memory.position = value; |
| @@ -515,15 +534,22 @@ async function summarizeCallback(args, text) { | ||
| 515 | 534 | |
| 516 | 535 | const source = args.source || extension_settings.memory.source; |
| 517 | 536 | const prompt = substituteParamsExtended((args.prompt || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords }); |
| 537 | + const useUserRole = getSummaryPromptRole() === extension_prompt_roles.USER; | |
| 518 | 538 | |
| 519 | 539 | try { |
| 520 | 540 | switch (source) { |
| 521 | 541 | case summary_sources.extras: |
| 522 | 542 | return await callExtrasSummarizeAPI(text); |
| 523 | 543 | case summary_sources.main: { |
| 524 | - return removeReasoningFromString(await generateRaw({ prompt: text, systemPrompt: prompt, responseLength: extension_settings.memory.overrideResponseLength })); | |
| 544 | + // When the instruction should be a USER message, combine it into the | |
| 545 | + // user content and clear the system prompt. Otherwise keep it as system. | |
| 546 | + const rawPrompt = useUserRole ? [prompt, text].filter(x => x).join('\n\n') : text; | |
| 547 | + const systemPrompt = useUserRole ? '' : prompt; | |
| 548 | + return removeReasoningFromString(await generateRaw({ prompt: rawPrompt, systemPrompt: systemPrompt, responseLength: extension_settings.memory.overrideResponseLength })); | |
| 549 | + } | |
| 525 | 550 | case summary_sources.webllm: { |
| 526 | - const messages = [{ role: 'system', content: prompt }, { role: 'user', content: text }].filter(m => m.content); | |
| 551 | + const promptRole = useUserRole ? 'user' : 'system'; | |
| 552 | + const messages = [{ role: promptRole, content: prompt }, { role: 'user', content: text }].filter(m => m.content); | |
| 527 | 553 | const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; |
| 528 | 554 | return await generateWebLlmChatPrompt(messages, params); |
| 529 | 555 | } |
| @@ -646,8 +672,9 @@ async function summarizeChatWebLLM(context, force) { | ||
| 646 | 672 | return null; |
| 647 | 673 | } |
| 648 | 674 | |
| 675 | + const promptRole = getSummaryPromptRole() === extension_prompt_roles.USER ? 'user' : 'system'; | |
| 649 | 676 | const messages = [ |
| 650 | 677 | { role: 'system'promptRole, content: prompt }, |
| 651 | 678 | { role: 'user', content: rawPrompt }, |
| 652 | 679 | ]; |
| 653 | 680 | |
| @@ -692,13 +719,30 @@ async function summarizeChatMain(context, force, skipWIAN) { | ||
| 692 | 719 | if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) { |
| 693 | 720 | try { |
| 694 | 721 | inApiCall = true; |
| 695 | - /** @type {import('../../../script.js').GenerateQuietPromptParams} */ | |
| 722 | + // generateQuietPrompt always injects the instruction as a SYSTEM message | |
| 696 | - const params = { | |
| 723 | + // (the QUIET_PROMPT extension prompt has no exposed role parameter). | |
| 697 | - quietPrompt: prompt, | |
| 724 | + // When a USER role is requested, assemble the chat transcript ourselves | |
| 698 | - skipWIAN: skipWIAN, | |
| 725 | + // (same source as the raw builder) and deliver the instruction as USER | |
| 699 | - responseLength: extension_settings.memory.overrideResponseLength, | |
| 726 | + // content via generateRaw, so the model still receives the chat to | |
| 700 | - }; | |
| 727 | + // summarize instead of just the bare instruction. | |
| 701 | - summary = await generateQuietPrompt(params); | |
| 728 | + if (getSummaryPromptRole() === extension_prompt_roles.USER) { | |
| 729 | + const { rawPrompt } = await getRawSummaryPrompt(context, prompt); | |
| 730 | + /** @type {import('../../../script.js').GenerateRawParams} */ | |
| 731 | + const params = { | |
| 732 | + prompt: [prompt, rawPrompt].filter(x => x).join('\n\n'), | |
| 733 | + systemPrompt: '', | |
| 734 | + responseLength: extension_settings.memory.overrideResponseLength, | |
| 735 | + }; | |
| 736 | + summary = removeReasoningFromString(await generateRaw(params)); | |
| 737 | + } else { | |
| 738 | + /** @type {import('../../../script.js').GenerateQuietPromptParams} */ | |
| 739 | + const params = { | |
| 740 | + quietPrompt: prompt, | |
| 741 | + skipWIAN: skipWIAN, | |
| 742 | + responseLength: extension_settings.memory.overrideResponseLength, | |
| 743 | + }; | |
| 744 | + summary = await generateQuietPrompt(params); | |
| 745 | + } | |
| 702 | 746 | } finally { |
| 703 | 747 | inApiCall = false; |
| 704 | 748 | } |
| @@ -722,10 +766,14 @@ async function summarizeChatMain(context, force, skipWIAN) { | ||
| 722 | 766 | return null; |
| 723 | 767 | } |
| 724 | 768 | |
| 769 | + // When the summary instruction should be a USER message, deliver it as | |
| 770 | + // user content and leave the system prompt empty. Otherwise keep the | |
| 771 | + // existing behavior of sending it as the system prompt. | |
| 772 | + const useUserRole = getSummaryPromptRole() === extension_prompt_roles.USER; | |
| 725 | 773 | /** @type {import('../../../script.js').GenerateRawParams} */ |
| 726 | 774 | const params = { |
| 727 | - prompt: rawPrompt, | |
| 775 | + prompt: useUserRole ? [prompt, rawPrompt].filter(x => x).join('\n\n') : rawPrompt, | |
| 728 | 776 | systemPrompt: useUserRole ? '' : prompt, |
| 729 | 777 | responseLength: extension_settings.memory.overrideResponseLength, |
| 730 | 778 | }; |
| 731 | 779 | const rawSummary = await generateRaw(params); |
| @@ -1047,6 +1095,7 @@ function setupListeners() { | ||
| 1047 | 1095 | $('#memory_template').off('input').on('input', onMemoryTemplateInput); |
| 1048 | 1096 | $('#memory_depth').off('input').on('input', onMemoryDepthInput); |
| 1049 | 1097 | $('#memory_role').off('input').on('input', onMemoryRoleInput); |
| 1098 | + $('#memory_summary_prompt_role').off('input').on('input', onMemorySummaryPromptRoleInput); | |
| 1050 | 1099 | $('input[name="memory_position"]').off('change').on('change', onMemoryPositionChange); |
| 1051 | 1100 | $('#memory_prompt_words_force').off('input').on('input', onMemoryPromptWordsForceInput); |
| 1052 | 1101 | $('#memory_prompt_builder_default').off('input').on('input', onMemoryPromptBuilderInput); |
| @@ -69,6 +69,12 @@ | ||
| 69 | 69 | </div> |
| 70 | 70 | </label> |
| 71 | 71 | <textarea id="memory_prompt" class="text_pole textarea_compact" rows="6" data-i18n="[placeholder]ext_sum_prompt_placeholder" placeholder="This prompt will be sent to AI to request the summary generation. {{words}} will resolve to the 'Number of words' parameter."></textarea> |
| 72 | + <label for="memory_summary_prompt_role" data-i18n="ext_sum_prompt_role">Summary Prompt Role</label> | |
| 73 | + <select id="memory_summary_prompt_role" class="text_pole"> | |
| 74 | + <option value="0" data-i18n="System">System</option> | |
| 75 | + <option value="1" data-i18n="User">User</option> | |
| 76 | + </select> | |
| 77 | + <small data-i18n="ext_sum_prompt_role_desc">The role used to send the summary request instruction to the model.</small> | |
| 72 | 78 | <label for="memory_prompt_words"><span data-i18n="ext_sum_target_length_1">Target summary length</span> <span data-i18n="ext_sum_target_length_2">(</span><span id="memory_prompt_words_value"></span><span data-i18n="ext_sum_target_length_3"> words)</span></label> |
| 73 | 79 | <input id="memory_prompt_words" type="range" value="{{defaultSettings.promptWords}}" min="{{defaultSettings.promptMinWords}}" max="{{defaultSettings.promptMaxWords}}" step="{{defaultSettings.promptWordsStep}}" /> |
| 74 | 80 | <label for="memory_override_response_length"> |