Allow automatic extension of truncated summary

This commit is contained in:
Webifi 2023-06-12 16:52:02 -05:00
parent 28d68b3f55
commit a9a49f490a
5 changed files with 116 additions and 83 deletions

View File

@ -59,49 +59,47 @@ export class ChatRequest {
const promptTokenCount = countPromptTokens(messagePayload, model) const promptTokenCount = countPromptTokens(messagePayload, model)
const maxAllowed = maxTokens - (promptTokenCount + 1) const maxAllowed = maxTokens - (promptTokenCount + 1)
// Build and make the request // Build the API request body
try { const request: Request = {
// Build the API request body model: chatSettings.model,
const request: Request = { messages: messagePayload,
model: chatSettings.model, // Provide the settings by mapping the settingsMap to key/value pairs
messages: messagePayload, ...getRequestSettingList().reduce((acc, setting) => {
// Provide the settings by mapping the settingsMap to key/value pairs const key = setting.key
...getRequestSettingList().reduce((acc, setting) => { let value = getChatSettingValueNullDefault(chatId, setting)
const key = setting.key if (key in overrides) value = overrides[key]
let value = getChatSettingValueNullDefault(chatId, setting) if (typeof setting.apiTransform === 'function') {
if (key in overrides) value = overrides[key] value = setting.apiTransform(chatId, setting, value)
if (typeof setting.apiTransform === 'function') { }
value = setting.apiTransform(chatId, setting, value) if (key === 'max_tokens') {
} if (opts.maxTokens) value = opts.maxTokens // only as large as requested
if (key === 'max_tokens') { if (value > maxAllowed || value < 1) value = null // if over max model, do not define max
if (opts.maxTokens) value = opts.maxTokens // only as large as requested }
if (value > maxAllowed || value < 1) value = null // if over max model, do not define max if (key === 'n') {
} if (opts.streaming || opts.summaryRequest) {
if (key === 'n') {
if (opts.streaming || opts.summaryRequest) {
/* /*
Streaming goes insane with more than one completion. Streaming goes insane with more than one completion.
Doesn't seem like there's any way to separate the jumbled mess of deltas for the Doesn't seem like there's any way to separate the jumbled mess of deltas for the
different completions. different completions.
Summary should only have one completion Summary should only have one completion
*/ */
value = 1 value = 1
}
} }
if (value !== null) acc[key] = value }
return acc if (value !== null) acc[key] = value
}, {}), return acc
stream: opts.streaming }, {}),
} stream: opts.streaming
}
// Set-up and make the request
try {
// Add out token count to the response handler // Add out token count to the response handler
// (streaming doesn't return counts, so we need to do it client side) // (streaming doesn't return counts, so we need to do it client side)
chatResponse.setPromptTokenCount(promptTokenCount) chatResponse.setPromptTokenCount(promptTokenCount)
const signal = _this.controller.signal const signal = _this.controller.signal
// console.log('apikey', $apiKeyStorage)
const fetchOptions = { const fetchOptions = {
method: 'POST', method: 'POST',
headers: { headers: {
@ -297,6 +295,7 @@ export class ChatRequest {
*/ */
const bottom = rw.slice(0 - pinBottom) const bottom = rw.slice(0 - pinBottom)
let continueCounter = chatSettings.summaryExtend + 1
rw = rw.slice(0, 0 - pinBottom) rw = rw.slice(0, 0 - pinBottom)
let reductionPoolSize = countPromptTokens(rw, model) let reductionPoolSize = countPromptTokens(rw, model)
const ss = chatSettings.summarySize const ss = chatSettings.summarySize
@ -340,53 +339,67 @@ export class ChatRequest {
// Request and load the summarization prompt // Request and load the summarization prompt
_this.updatingMessage = 'Summarizing...' _this.updatingMessage = 'Summarizing...'
try { const summarizedIds = rw.map(m => m.uuid)
const summary = await _this.sendRequest(top.concat(rw).concat([summaryRequest]), { const summaryIds = [summaryResponse.uuid]
summaryRequest: true, while (continueCounter-- > 0) {
streaming: opts.streaming, try {
maxTokens: maxSummaryTokens, const summary = await _this.sendRequest(top.concat(rw).concat([summaryRequest]), {
fillMessage: summaryResponse, summaryRequest: true,
autoAddMessages: true, streaming: opts.streaming,
onMessageChange: (m) => { maxTokens: maxSummaryTokens,
if (opts.streaming) scrollToMessage(summaryResponse.uuid, 150, true, true) fillMessage: summaryResponse,
autoAddMessages: true,
onMessageChange: (m) => {
if (opts.streaming) scrollToMessage(summaryResponse.uuid, 150, true, true)
}
} as ChatCompletionOpts, {
temperature: 0.1, // make summary more deterministic
top_p: 1,
presence_penalty: 0,
frequency_penalty: 0,
...overrides
} as ChatSettings)
// Wait for the response to complete
if (!summary.hasFinished()) await summary.promiseToFinish()
if (summary.hasError()) {
// Failed for some API issue. let the original caller handle it.
_this.updating = false
_this.updatingMessage = ''
deleteMessage(chatId, srid)
return summary
} }
} as ChatCompletionOpts, { // Looks like we got our summarized messages.
temperature: 0, // make summary more deterministic // Mark the new summaries as such
top_p: 0.5, // Need more?
presence_penalty: 0, if (summaryResponse.finish_reason === 'length' && continueCounter > 0) {
frequency_penalty: 0, // Our summary was truncated
...overrides // Try to get more of it
} as ChatSettings) delete summaryResponse.finish_reason
// Wait for the response to complete _this.updatingMessage = 'Summarizing more...'
if (!summary.hasFinished()) await summary.promiseToFinish() continue
if (summary.hasError()) { } else {
// Failed to some API issue. let the original caller handle it. // We're done
deleteMessage(chatId, summaryResponse.uuid) continueCounter = 0
return summary }
} else { } catch (e) {
// Looks like we got our summarized messages. _this.updating = false
// Mark the new summaries as such _this.updatingMessage = ''
summaryResponse.summary = rw.map(m => m.uuid) deleteMessage(chatId, srid)
const summaryIds = [summaryResponse.uuid] throw e
// Disable the messages we summarized so they still show in history
rw.forEach((m, i) => { m.summarized = summaryIds })
saveChatStore()
// Re-run request with summarized prompts
// return { error: { message: "End for now" } } as Response
_this.updatingMessage = 'Continuing...'
scrollToBottom(true)
return await _this.sendRequest(chat.messages, {
...opts,
didSummary: true
},
overrides)
} }
} catch (e) {
_this.updating = false
_this.updatingMessage = ''
deleteMessage(chatId, srid)
throw e
} }
summaryResponse.summary = summarizedIds
// Disable the messages we summarized so they still show in history
rw.forEach((m, i) => { m.summarized = summaryIds })
saveChatStore()
// Re-run request with summarized prompts
_this.updatingMessage = 'Continuing...'
scrollToBottom(true)
return await _this.sendRequest(chat.messages, {
...opts,
didSummary: true
},
overrides)
} else { } else {
/*************** /***************
* Unknown mode. * Unknown mode.

View File

@ -177,7 +177,7 @@
placeholder={String(setting.placeholder || chatDefaults[setting.key])} placeholder={String(setting.placeholder || chatDefaults[setting.key])}
on:change={e => queueSettingValueChange(e, setting)} on:change={e => queueSettingValueChange(e, setting)}
/> />
{:else if setting.type === 'select'} {:else if setting.type === 'select' || setting.type === 'select-number'}
<!-- <div class="select"> --> <!-- <div class="select"> -->
<div class="select" class:control={fieldControls.length}> <div class="select" class:control={fieldControls.length}>
<select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } > <select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } >

View File

@ -60,7 +60,7 @@ const gptDefaults = {
n: 1, n: 1,
stream: true, stream: true,
stop: null, stop: null,
max_tokens: 500, max_tokens: 512,
presence_penalty: 0, presence_penalty: 0,
frequency_penalty: 0, frequency_penalty: 0,
logit_bias: null, logit_bias: null,
@ -77,6 +77,7 @@ const defaults:ChatSettings = {
continuousChat: 'fifo', continuousChat: 'fifo',
summaryThreshold: 3000, summaryThreshold: 3000,
summarySize: 1000, summarySize: 1000,
summaryExtend: 0,
pinTop: 0, pinTop: 0,
pinBottom: 6, pinBottom: 6,
summaryPrompt: '', summaryPrompt: '',
@ -222,11 +223,23 @@ const summarySettings: ChatSetting[] = [
name: 'Max Summary Size', name: 'Max Summary Size',
title: 'Maximum number of tokens allowed for summary response.', title: 'Maximum number of tokens allowed for summary response.',
min: 128, min: 128,
max: 512, max: 1024,
step: 1, step: 1,
type: 'number', type: 'number',
hide: (chatId) => getChatSettings(chatId).continuousChat !== 'summary' hide: (chatId) => getChatSettings(chatId).continuousChat !== 'summary'
}, },
{
key: 'summaryExtend',
name: 'Summary Extend',
title: 'Number of times a truncated summary can be extended.',
type: 'select-number',
options: [
{ value: 0, text: '0 - Summary must fit in first call.' },
{ value: 1, text: '1 - Allow one extra API call to extend.' },
{ value: 2, text: '2 - Allow two extra API calls to extend.' }
],
hide: (chatId) => getChatSettings(chatId).continuousChat !== 'summary'
},
{ {
key: 'pinTop', key: 'pinTop',
name: 'Keep First Prompts', name: 'Keep First Prompts',

View File

@ -333,6 +333,7 @@
export const cleanSettingValue = (type:string, value: any) => { export const cleanSettingValue = (type:string, value: any) => {
switch (type) { switch (type) {
case 'number': case 'number':
case 'select-number':
value = parseFloat(value) value = parseFloat(value)
if (isNaN(value)) { value = null } if (isNaN(value)) { value = null }
return value return value

View File

@ -60,6 +60,7 @@
continuousChat: (''|'fifo'|'summary'); continuousChat: (''|'fifo'|'summary');
summaryThreshold: number; summaryThreshold: number;
summarySize: number; summarySize: number;
summaryExtend: number;
pinTop: number; pinTop: number;
pinBottom: number; pinBottom: number;
summaryPrompt: string; summaryPrompt: string;
@ -141,19 +142,24 @@
}; };
export type SelectOption = { export type SelectOption = {
value: string; value: string|number;
text: string; text: string;
}; };
type SettingBoolean = { type SettingBoolean = {
type: 'boolean'; type: 'boolean';
}; };
export type SettingSelect = { export type SettingSelect = {
type: 'select'; type: 'select';
options: SelectOption[]; options: SelectOption[];
}; };
export type SettingSelectNumber = {
type: 'select-number';
options: SelectOption[];
};
export type SettingText = { export type SettingText = {
type: 'text'; type: 'text';
}; };
@ -199,7 +205,7 @@ type SettingBoolean = {
fieldControls?: FieldControl[]; fieldControls?: FieldControl[];
beforeChange?: (chatId:number, setting:ChatSetting, value:any) => boolean; beforeChange?: (chatId:number, setting:ChatSetting, value:any) => boolean;
afterChange?: (chatId:number, setting:ChatSetting, value:any) => boolean; afterChange?: (chatId:number, setting:ChatSetting, value:any) => boolean;
} & (SettingNumber | SettingSelect | SettingBoolean | SettingText | SettingTextArea | SettingOther | SubSetting); } & (SettingNumber | SettingSelect | SettingSelectNumber | SettingBoolean | SettingText | SettingTextArea | SettingOther | SubSetting);
export type GlobalSetting = { export type GlobalSetting = {