Summarize: configurable system/user role for the summary prompt Reviewed-by: auto-review
| @@ -136,8 +136,20 @@ const defaultSettings = { | |||
| 136 | maxMessagesPerRequestMax: 250, | 136 | maxMessagesPerRequestMax: 250, |
| 137 | maxMessagesPerRequestStep: 1, | 137 | maxMessagesPerRequestStep: 1, |
| 138 | prompt_builder: prompt_builders.DEFAULT, | 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 | function loadSettings() { | 153 | function loadSettings() { |
| 142 | if (Object.keys(extension_settings.memory).length === 0) { | 154 | if (Object.keys(extension_settings.memory).length === 0) { |
| 143 | Object.assign(extension_settings.memory, defaultSettings); | 155 | Object.assign(extension_settings.memory, defaultSettings); |
| @@ -158,6 +170,7 @@ function loadSettings() { | |||
| 158 | $('#memory_template').val(extension_settings.memory.template).trigger('input'); | 170 | $('#memory_template').val(extension_settings.memory.template).trigger('input'); |
| 159 | $('#memory_depth').val(extension_settings.memory.depth).trigger('input'); | 171 | $('#memory_depth').val(extension_settings.memory.depth).trigger('input'); |
| 160 | $('#memory_role').val(extension_settings.memory.role).trigger('input'); | 172 | $('#memory_role').val(extension_settings.memory.role).trigger('input'); |
| 173 | $('#memory_summary_prompt_role').val(extension_settings.memory.summaryPromptRole).trigger('input'); | ||
| 161 | $(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input'); | 174 | $(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input'); |
| 162 | $('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input'); | 175 | $('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input'); |
| 163 | $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); | 176 | $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input'); |
| @@ -314,6 +327,12 @@ function onMemoryRoleInput() { | |||
| 314 | saveSettingsDebounced(); | 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 | function onMemoryPositionChange(e) { | 336 | function onMemoryPositionChange(e) { |
| 318 | const value = e.target.value; | 337 | const value = e.target.value; |
| 319 | extension_settings.memory.position = value; | 338 | extension_settings.memory.position = value; |
| @@ -515,15 +534,22 @@ async function summarizeCallback(args, text) { | |||
| 515 | 534 | ||
| 516 | const source = args.source || extension_settings.memory.source; | 535 | const source = args.source || extension_settings.memory.source; |
| 517 | const prompt = substituteParamsExtended((args.prompt || extension_settings.memory.prompt), { words: extension_settings.memory.promptWords }); | 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 | try { | 539 | try { |
| 520 | switch (source) { | 540 | switch (source) { |
| 521 | case summary_sources.extras: | 541 | case summary_sources.extras: |
| 522 | return await callExtrasSummarizeAPI(text); | 542 | return await callExtrasSummarizeAPI(text); |
| 523 | case summary_sources.main: | 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 | case summary_sources.webllm: { | 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 | const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; | 553 | const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; |
| 528 | return await generateWebLlmChatPrompt(messages, params); | 554 | return await generateWebLlmChatPrompt(messages, params); |
| 529 | } | 555 | } |
| @@ -646,8 +672,9 @@ async function summarizeChatWebLLM(context, force) { | |||
| 646 | return null; | 672 | return null; |
| 647 | } | 673 | } |
| 648 | 674 | ||
| 675 | const promptRole = getSummaryPromptRole() === extension_prompt_roles.USER ? 'user' : 'system'; | ||
| 649 | const messages = [ | 676 | const messages = [ |
| 650 | { role: 'system', content: prompt }, | 677 | { role: promptRole, content: prompt }, |
| 651 | { role: 'user', content: rawPrompt }, | 678 | { role: 'user', content: rawPrompt }, |
| 652 | ]; | 679 | ]; |
| 653 | 680 | ||
| @@ -692,13 +719,30 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 692 | if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) { | 719 | if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) { |
| 693 | try { | 720 | try { |
| 694 | inApiCall = true; | 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 | } finally { | 746 | } finally { |
| 703 | inApiCall = false; | 747 | inApiCall = false; |
| 704 | } | 748 | } |
| @@ -722,10 +766,14 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 722 | return null; | 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 | /** @type {import('../../../script.js').GenerateRawParams} */ | 773 | /** @type {import('../../../script.js').GenerateRawParams} */ |
| 726 | const params = { | 774 | const params = { |
| 727 | prompt: rawPrompt, | 775 | prompt: useUserRole ? [prompt, rawPrompt].filter(x => x).join('\n\n') : rawPrompt, |
| 728 | systemPrompt: prompt, | 776 | systemPrompt: useUserRole ? '' : prompt, |
| 729 | responseLength: extension_settings.memory.overrideResponseLength, | 777 | responseLength: extension_settings.memory.overrideResponseLength, |
| 730 | }; | 778 | }; |
| 731 | const rawSummary = await generateRaw(params); | 779 | const rawSummary = await generateRaw(params); |
| @@ -1047,6 +1095,7 @@ function setupListeners() { | |||
| 1047 | $('#memory_template').off('input').on('input', onMemoryTemplateInput); | 1095 | $('#memory_template').off('input').on('input', onMemoryTemplateInput); |
| 1048 | $('#memory_depth').off('input').on('input', onMemoryDepthInput); | 1096 | $('#memory_depth').off('input').on('input', onMemoryDepthInput); |
| 1049 | $('#memory_role').off('input').on('input', onMemoryRoleInput); | 1097 | $('#memory_role').off('input').on('input', onMemoryRoleInput); |
| 1098 | $('#memory_summary_prompt_role').off('input').on('input', onMemorySummaryPromptRoleInput); | ||
| 1050 | $('input[name="memory_position"]').off('change').on('change', onMemoryPositionChange); | 1099 | $('input[name="memory_position"]').off('change').on('change', onMemoryPositionChange); |
| 1051 | $('#memory_prompt_words_force').off('input').on('input', onMemoryPromptWordsForceInput); | 1100 | $('#memory_prompt_words_force').off('input').on('input', onMemoryPromptWordsForceInput); |
| 1052 | $('#memory_prompt_builder_default').off('input').on('input', onMemoryPromptBuilderInput); | 1101 | $('#memory_prompt_builder_default').off('input').on('input', onMemoryPromptBuilderInput); |
| @@ -69,6 +69,12 @@ | |||
| 69 | </div> | 69 | </div> |
| 70 | </label> | 70 | </label> |
| 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> | 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 | <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> | 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 | <input id="memory_prompt_words" type="range" value="{{defaultSettings.promptWords}}" min="{{defaultSettings.promptMinWords}}" max="{{defaultSettings.promptMaxWords}}" step="{{defaultSettings.promptWordsStep}}" /> | 79 | <input id="memory_prompt_words" type="range" value="{{defaultSettings.promptWords}}" min="{{defaultSettings.promptMinWords}}" max="{{defaultSettings.promptMaxWords}}" step="{{defaultSettings.promptWordsStep}}" /> |
| 74 | <label for="memory_override_response_length"> | 80 | <label for="memory_override_response_length"> |