add ai instructions, fix prompts

This commit is contained in:
Andrew Pareles 2025-01-15 05:25:43 -08:00
parent 9ba70cdc4a
commit d4fc59034a
9 changed files with 91 additions and 124 deletions

View file

@ -69,14 +69,14 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
this.onErrorHooks_llm[e.requestId]?.(e)
this._onRequestIdDone(e.requestId)
}))
// ollama
// ollama .list()
this._register((this.channel.listen('onSuccess_ollama') satisfies Event<EventModelListOnSuccessParams<OllamaModelResponse>>)(e => {
this.onSuccess_ollama[e.requestId]?.(e)
}))
this._register((this.channel.listen('onError_ollama') satisfies Event<EventModelListOnErrorParams<OllamaModelResponse>>)(e => {
this.onError_ollama[e.requestId]?.(e)
}))
// openaiCompatible
// openaiCompatible .list()
this._register((this.channel.listen('onSuccess_openAICompatible') satisfies Event<EventModelListOnSuccessParams<OpenaiCompatibleModelResponse>>)(e => {
this.onSuccess_openAICompatible[e.requestId]?.(e)
}))
@ -98,6 +98,10 @@ export class LLMMessageService extends Disposable implements ILLMMessageService
}
const { providerName, modelName } = modelSelection
const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions
if (aiInstructions)
proxyParams.messages.unshift({ role: 'system', content: aiInstructions })
// add state for request id
const requestId_ = generateUuid();
this.onTextHooks_llm[requestId_] = onText

View file

@ -80,7 +80,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
disposables.forEach(d => d.dispose())
disposables.clear()
if (!voidSettingsService.state.featureFlagSettings.autoRefreshModels) return
if (!voidSettingsService.state.globalSettings['autoRefreshModels']) return
for (const providerName of refreshableProviderNames) {
@ -117,11 +117,11 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
}
}
// on mount (when get init settings state), and if a relevant feature flag changes (detected natively right now by refreshing if any flag changes), start refreshing models
// on mount (when get init settings state), and if a relevant feature flag changes, start refreshing models
voidSettingsService.waitForInitState.then(() => {
initializePollingAndOnChange()
this._register(
voidSettingsService.onDidChangeState((type) => { if (type === 'featureFlagSettings') initializePollingAndOnChange() })
voidSettingsService.onDidChangeState((type) => { if (typeof type === 'object' && type[1] === 'autoRefreshModels') initializePollingAndOnChange() })
)
})
@ -184,7 +184,7 @@ export class RefreshModelService extends Disposable implements IRefreshModelServ
// check if we should poll
// if it was originally called as `isPolling` and if the `autoRefreshModels` flag is enabled
if (isPolling && this.voidSettingsService.state.featureFlagSettings.autoRefreshModels) {
if (isPolling && this.voidSettingsService.state.globalSettings['autoRefreshModels']) {
const timeoutId = setTimeout(() => this.refreshModels(providerName, enableProviderOnSuccess, options), REFRESH_INTERVAL)
this._setTimeoutId(providerName, timeoutId)
}

View file

@ -11,10 +11,10 @@ import { registerSingleton, InstantiationType } from '../../instantiation/common
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
import { IMetricsService } from './metricsService.js';
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, VoidModelInfo, FeatureFlagSettings, FeatureFlagName, defaultFeatureFlagSettings } from './voidSettingsTypes.js';
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, modelInfoOfDefaultNames, VoidModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings } from './voidSettingsTypes.js';
const STORAGE_KEY = 'void.voidSettingsStorage'
const STORAGE_KEY = 'void.settingsServiceStorage'
type SetSettingOfProviderFn = <S extends SettingName>(
providerName: ProviderName,
@ -28,7 +28,7 @@ type SetModelSelectionOfFeatureFn = <K extends FeatureName>(
options?: { doNotApplyEffects?: true }
) => Promise<void>;
type SetFeatureFlagFn = (flagName: FeatureFlagName, newVal: boolean) => void;
type SetGlobalSettingFn = <T extends GlobalSettingName, >(settingName: T, newVal: GlobalSettings[T]) => void;
export type ModelOption = { name: string, selection: ModelSelection }
@ -37,12 +37,13 @@ export type ModelOption = { name: string, selection: ModelSelection }
export type VoidSettingsState = {
readonly settingsOfProvider: SettingsOfProvider; // optionsOfProvider
readonly modelSelectionOfFeature: ModelSelectionOfFeature; // stateOfFeature
readonly featureFlagSettings: FeatureFlagSettings;
readonly globalSettings: GlobalSettings;
readonly _modelOptions: ModelOption[] // computed based on the two above items
}
type EventProp = Exclude<keyof VoidSettingsState, '_modelOptions'> | 'all'
type RealVoidSettings = Exclude<keyof VoidSettingsState, '_modelOptions'>
type EventProp<T extends RealVoidSettings = RealVoidSettings> = T extends 'globalSettings' ? [T, keyof VoidSettingsState[T]] : T | 'all'
export interface IVoidSettingsService {
@ -54,7 +55,7 @@ export interface IVoidSettingsService {
setSettingOfProvider: SetSettingOfProviderFn;
setModelSelectionOfFeature: SetModelSelectionOfFeatureFn;
setFeatureFlag: SetFeatureFlagFn;
setGlobalSetting: SetGlobalSettingFn;
setAutodetectedModels(providerName: ProviderName, modelNames: string[], logging: { enableProviderOnSuccess?: boolean, isPolling?: boolean, isInvisible?: boolean }): void;
toggleModelHidden(providerName: ProviderName, modelName: string): void;
@ -81,7 +82,7 @@ const defaultState = () => {
const d: VoidSettingsState = {
settingsOfProvider: deepClone(defaultSettingsOfProvider),
modelSelectionOfFeature: { 'Ctrl+L': null, 'Ctrl+K': null, 'Autocomplete': null },
featureFlagSettings: deepClone(defaultFeatureFlagSettings),
globalSettings: deepClone(defaultGlobalSettings),
_modelOptions: _computeModelOptions(defaultSettingsOfProvider), // computed
}
return d
@ -150,7 +151,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
}
}
const newFeatureFlags = this.state.featureFlagSettings
const newGlobalSettings = this.state.globalSettings
// if changed models or enabled a provider, recompute models list
const modelsListChanged = settingName === 'models' || settingName === '_enabled'
@ -159,7 +160,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
const newState: VoidSettingsState = {
modelSelectionOfFeature: newModelSelectionOfFeature,
settingsOfProvider: newSettingsOfProvider,
featureFlagSettings: newFeatureFlags,
globalSettings: newGlobalSettings,
_modelOptions: newModelsList,
}
@ -187,17 +188,17 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
}
setFeatureFlag: SetFeatureFlagFn = async (flagName, newVal) => {
const newState = {
setGlobalSetting: SetGlobalSettingFn = async (settingName, newVal) => {
const newState: VoidSettingsState = {
...this.state,
featureFlagSettings: {
...this.state.featureFlagSettings,
[flagName]: newVal
globalSettings: {
...this.state.globalSettings,
[settingName]: newVal
}
}
this.state = newState
await this._storeState()
this._onDidChangeState.fire('featureFlagSettings')
this._onDidChangeState.fire(['globalSettings', settingName])
}

View file

@ -397,26 +397,16 @@ export type RefreshableProviderName = typeof refreshableProviderNames[number]
export type FeatureFlagSettings = {
export type GlobalSettings = {
autoRefreshModels: boolean;
aiInstructions: string;
}
export const defaultFeatureFlagSettings: FeatureFlagSettings = {
export const defaultGlobalSettings: GlobalSettings = {
autoRefreshModels: true,
aiInstructions: '',
}
export type FeatureFlagName = keyof FeatureFlagSettings
export const featureFlagNames = Object.keys(defaultFeatureFlagSettings) as FeatureFlagName[]
type FeatureFlagDisplayInfo = {
description: string,
}
export const displayInfoOfFeatureFlag = (featureFlag: FeatureFlagName): FeatureFlagDisplayInfo => {
if (featureFlag === 'autoRefreshModels') {
return {
description: `Automatically detect local providers and models (${refreshableProviderNames.map(providerName => displayInfoOfProviderName(providerName).title).join(', ')}).`,
}
}
throw new Error(`featureFlagInfo: Unknown feature flag: "${featureFlag}"`)
}
export type GlobalSettingName = keyof GlobalSettings
export const globalSettingNames = Object.keys(defaultGlobalSettings) as GlobalSettingName[]

View file

@ -37,7 +37,6 @@ export const sendLLMMessage = ({
modelName,
numMessages: messages?.length,
messagesShape: messages?.map(msg => ({ role: msg.role, length: msg.content.length })),
version: '2024-11-14',
...extras,
})
}
@ -62,7 +61,6 @@ export const sendLLMMessage = ({
const onError: OnError = ({ message: error, fullError }) => {
if (_didAbort) return
console.log("ERROR!!!!!", error)
console.error('sendLLMMessage onError:', error)
captureChatEvent(`${loggingName} - Error`, { error })
onError_({ message: error, fullError })

View file

@ -1203,9 +1203,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// add to history
const { onFinishEdit } = this._addToHistory(uri)
// __TODO__ ctrl+K should use Ollama's FIM method.
const ollamaStyleFIM = false
// __TODO__ let users customize modelFimTags
const isOllamaFIM = false // this._voidSettingsService.state.modelSelectionOfFeature['Ctrl+K']?.providerName === 'ollama'
const modelFimTags = defaultFimTags
const adding: Omit<DiffZone, 'diffareaid'> = {
@ -1240,20 +1239,21 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
if (featureName === 'Ctrl+L') {
const userContent = ctrlLStream_prompt({ originalCode, userMessage, uri })
messages = [
// TODO include more context too
{ role: 'system', content: ctrlLStream_systemMessage, },
{ role: 'user', content: userContent, }
]
}
else if (featureName === 'Ctrl+K') {
const { prefix, suffix } = ctrlKStream_prefixAndSuffix({ fullFileStr: currentFileStr, startLine, endLine })
const language = filenameToVscodeLanguage(uri.fsPath) ?? ''
const userContent = ctrlKStream_prompt({ selection: originalCode, userMessage, prefix, suffix, ollamaStyleFIM, fimTags: modelFimTags, language })
// console.log('PREFIX:\n', prefix)
// console.log('SUFFIX:\n', suffix)
// console.log('USER CONTENT:\n', userContent)
// __TODO__ use Ollama's FIM api
// if (isOllamaFIM) {...} else:
const language = filenameToVscodeLanguage(uri.fsPath) ?? ''
const userContent = ctrlKStream_prompt({ selection: originalCode, userMessage, prefix, suffix, isOllamaFIM: false, fimTags: modelFimTags, language })
messages = [
// TODO include more context too (LSP, file history, etc)
{ role: 'system', content: ctrlKStream_systemMessage, },
{ role: 'user', content: userContent, }
]
@ -1286,7 +1286,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const extractText = (fullText: string, recentlyAddedTextLen: number) => {
if (featureName === 'Ctrl+K') {
if (ollamaStyleFIM) return fullText
if (isOllamaFIM) return fullText
return extractCodeFromFIM({ text: fullText, recentlyAddedTextLen, midTag: modelFimTags.midTag })
}
else if (featureName === 'Ctrl+L') {

View file

@ -341,32 +341,18 @@ export const defaultFimTags: FimTagsType = {
midTag: 'SELECTION',
}
export const ctrlKStream_prompt = ({ selection, prefix, suffix, userMessage, fimTags, ollamaStyleFIM, language }:
{ selection: string, prefix: string, suffix: string, userMessage: string, ollamaStyleFIM: boolean, fimTags: FimTagsType, language: string }) => {
export const ctrlKStream_prompt = ({ selection, prefix, suffix, userMessage, fimTags, isOllamaFIM, language }:
{
selection: string, prefix: string, suffix: string, userMessage: string, fimTags: FimTagsType, language: string,
isOllamaFIM: false, // we require this be false for clarity
}) => {
const { preTag, sufTag, midTag } = fimTags
if (ollamaStyleFIM) {
// const preTag = 'PRE'
// const sufTag = 'SUF'
// const midTag = 'MID'
return `\
<${preTag}>
/* Original Selection:
${selection}*/
/* Instructions:
${userMessage}*/
${prefix}</${preTag}>
<${sufTag}>${suffix}</${sufTag}>
<${midTag}>`
}
// prompt the model artifically on how to do FIM
else {
// const preTag = 'BEFORE'
// const sufTag = 'AFTER'
// const midTag = 'SELECTION'
return `\
// const preTag = 'BEFORE'
// const sufTag = 'AFTER'
// const midTag = 'SELECTION'
return `\
The user is selecting this code as their SELECTION:
\`\`\` ${language}
<${midTag}>${selection}</${midTag}>
@ -377,12 +363,12 @@ ${userMessage}
Please edit the SELECTION following the user's INSTRUCTIONS, and return the edited selection.
Note that the SELECTION has code that comes before it. This code is indicated with <${preTag}>...before<${preTag}/>.
Note also that the SELECTION has code that comes after it. This code is indicated with <${sufTag}>...after<${sufTag}/>.
Note that the SELECTION has code that comes before it. This code is indicated with <${preTag}>...before</${preTag}>.
Note also that the SELECTION has code that comes after it. This code is indicated with <${sufTag}>...after</${sufTag}>.
Instructions:
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_selection<${midTag}/>. Do NOT output any text or explanations before or after this.
2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>...<${preTag}/> or <${sufTag}>...<${sufTag}/> tags.
1. Your OUTPUT should be a SINGLE PIECE OF CODE of the form <${midTag}>...new_selection</${midTag}>. Do NOT output any text or explanations before or after this.
2. You may ONLY CHANGE the original SELECTION, and NOT the content in the <${preTag}>...</${preTag}> or <${sufTag}>...</${sufTag}> tags.
3. Make sure all brackets in the new selection are balanced the same as in the original selection.
4. Be careful not to duplicate or remove variables, comments, or other syntax by mistake.
@ -390,8 +376,7 @@ Given the code:
<${preTag}>${prefix}</${preTag}>
<${sufTag}>${suffix}</${sufTag}>
Return only the completion block of code (of the form \`\`\` ${language}\n <${midTag}>...new_selection<${midTag}/>\`\`\`):`
}
Return only the completion block of code (of the form \`\`\` ${language}\n <${midTag}>...new_selection</${midTag}>\`\`\`):`
};

View file

@ -73,7 +73,7 @@ export const VoidInputBox2 = forwardRef<HTMLTextAreaElement, InputBox2Props>(fun
if (r.scrollHeight === 0) return requestAnimationFrame(adjustHeight)
const h = r.scrollHeight
const newHeight = Math.min(h, 500)
const newHeight = Math.min(h + 1, 500) // plus one to avoid scrollbar appearing when it shouldn't
r.style.height = `${newHeight}px`
}, []);

View file

@ -5,7 +5,7 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, featureFlagNames, displayInfoOfFeatureFlag, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidModelInfo, globalSettingNames, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, defaultProviderSettings, nonlocalProviderNames, localProviderNames, GlobalSettingName } from '../../../../../../../platform/void/common/voidSettingsTypes.js'
import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js'
import { VoidButton, VoidCheckBox, VoidCustomSelectBox, VoidInputBox, VoidInputBox2, VoidSwitch } from '../util/inputs.js'
import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js'
@ -355,31 +355,10 @@ export const VoidProviderSettings = ({ providerNames }: { providerNames: Provide
</>
}
// export const VoidFeatureFlagSettings = () => {
// const accessor = useAccessor()
// const voidSettingsService = accessor.get('IVoidSettingsService')
// const voidSettingsState = useSettingsState()
// return <>
// {featureFlagNames.map((flagName) => {
// const value = voidSettingsState.featureFlagSettings[flagName]
// const { description } = displayInfoOfFeatureFlag(flagName)
// return <div key={flagName} className='hover:bg-black/10 hover:dark:bg-gray-200/10 rounded-sm overflow-hidden py-1 px-3 my-1'>
// <div className='flex items-center'>
// <VoidCheckBox
// label=''
// value={value}
// onClick={() => { voidSettingsService.setFeatureFlag(flagName, !value) }}
// />
// <h4 className='text-sm'>{description}</h4>
// </div>
// </div>
// })}
// </>
// }
type TabName = 'models' | 'general'
export const VoidFeatureFlagSettings = () => {
export const AutoRefreshToggle = () => {
const settingName: GlobalSettingName = 'autoRefreshModels'
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
@ -387,22 +366,33 @@ export const VoidFeatureFlagSettings = () => {
const voidSettingsState = useSettingsState()
return featureFlagNames.map((flagName) => {
// right now this is just `enabled_autoRefreshModels`
const enabled = voidSettingsState.globalSettings[settingName]
// right now this is just `enabled_autoRefreshModels`
const enabled = voidSettingsState.featureFlagSettings[flagName]
const { description } = displayInfoOfFeatureFlag(flagName)
return <SubtleButton
onClick={() => {
voidSettingsService.setGlobalSetting(settingName, !enabled)
metricsService.capture('Click', { action: 'Autorefresh Toggle', settingName, enabled: !enabled })
}}
text={`Automatically detect local providers and models (${refreshableProviderNames.map(providerName => displayInfoOfProviderName(providerName).title).join(', ')}).`}
icon={enabled ? <Check className='stroke-green-500 size-3' /> : <X className='stroke-red-500 size-3' />}
disabled={false}
/>
}
return <SubtleButton key={flagName}
onClick={() => {
voidSettingsService.setFeatureFlag(flagName, !enabled)
metricsService.capture('Click', { action: 'Autorefresh Toggle', flagName, enabled: !enabled })
}}
text={description}
icon={enabled ? <Check className='stroke-green-500 size-3' /> : <X className='stroke-red-500 size-3' />}
disabled={false}
/>
})
export const AIInstructionsBox = () => {
const accessor = useAccessor()
const voidSettingsService = accessor.get('IVoidSettingsService')
const voidSettingsState = useSettingsState()
return <VoidInputBox2
className='min-h-[81px] p-3 rounded-sm'
initValue={voidSettingsState.globalSettings.aiInstructions}
placeholder={`Do not change my indentation or delete my comments. When writing TS or JS, do not add ;'s. Respond to all queries in French. `}
multiline
onChangeText={(newText) => {
voidSettingsService.setGlobalSetting('aiInstructions', newText)
}}
/>
}
export const FeaturesTab = () => {
@ -434,7 +424,7 @@ export const FeaturesTab = () => {
<h2 className={`text-3xl mb-2 mt-24`}>Models</h2>
<ErrorBoundary>
<VoidFeatureFlagSettings />
<AutoRefreshToggle />
<RefreshableModels />
<ModelDump />
<AddModelMenuFull />
@ -571,13 +561,6 @@ const GeneralTab = () => {
</div>
{/* <div className='my-4'>
<h3 className={`text-xl mb-2 mt-4`}>Rules for AI</h3>
{`placeholder: "Do not add ;'s. Do not change or delete spacing, formatting, or comments. Respond to queries in French when applicable. "`}
</div> */}
<div className='mt-24'>
<h2 className={`text-3xl mb-2`}>Built-in Settings</h2>
@ -600,7 +583,13 @@ const GeneralTab = () => {
</div>
</div>
{/* <VoidFeatureFlagSettings /> */}
<div className='mt-24'>
<h2 className={`text-3xl mb-2`}>AI Instructions</h2>
<h4 className={`text-void-fg-3 mb-2`}>{`Instructions to include on all AI requests.`}</h4>
<AIInstructionsBox />
</div>
</>
}