diff --git a/src/vs/platform/void/common/llmMessageService.ts b/src/vs/platform/void/common/llmMessageService.ts index cd2b4de4..40855c8d 100644 --- a/src/vs/platform/void/common/llmMessageService.ts +++ b/src/vs/platform/void/common/llmMessageService.ts @@ -98,24 +98,10 @@ export class LLMMessageService extends Disposable implements ILLMMessageService } const { providerName, modelName } = modelSelection - // end early if there are no messages - if (proxyParams.messages.length === 0) { - onError({ message: 'Please send a message first.', fullError: null }) - return null - } - - // make first message contain system message for compatibility - // this is needed because o1 and other models do not accept a system prompt + // add ai instructions here because we don't have access to voidSettingsService on the other side of the proxy const aiInstructions = this.voidSettingsService.state.globalSettings.aiInstructions - const systemMessageIdx = proxyParams.messages.findIndex(m => m.role === 'system') - const systemMessage = systemMessageIdx > -1 ? proxyParams.messages[systemMessageIdx].content : undefined - proxyParams.messages[0].content = `` - + (systemMessage ? `\n\n${systemMessage}` : '') - + (aiInstructions ? `\n\n${aiInstructions}` : '') - + `\n\nHere are the user's instructions:\n\n${proxyParams.messages[0].content}` - if (systemMessageIdx > -1) { // remove role='system' messages - proxyParams.messages.splice(systemMessageIdx, 1) - } + if (aiInstructions) + proxyParams.messages.unshift({ role: 'system', content: aiInstructions }) // add state for request id const requestId_ = generateUuid(); diff --git a/src/vs/platform/void/common/llmMessageTypes.ts b/src/vs/platform/void/common/llmMessageTypes.ts index f14e82a6..77b31c07 100644 --- a/src/vs/platform/void/common/llmMessageTypes.ts +++ b/src/vs/platform/void/common/llmMessageTypes.ts @@ -30,6 +30,12 @@ export type LLMMessage = { content: string; } +export type _InternalLLMMessage = { + role: 'user' | 'assistant'; + content: string; +} + + export type ServiceSendLLMFeatureParams = { useProviderFor: 'Ctrl+K'; range: IRange; @@ -80,7 +86,7 @@ export type EventLLMMessageOnFinalMessageParams = Parameters[0] export type EventLLMMessageOnErrorParams = Parameters[0] & { requestId: string } export type _InternalSendLLMMessageFnType = (params: { - messages: LLMMessage[]; + messages: _InternalLLMMessage[]; onText: OnText; onFinalMessage: OnFinalMessage; onError: OnError; diff --git a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts index 04dcaa21..59d4c04a 100644 --- a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts +++ b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts @@ -7,11 +7,6 @@ import Anthropic from '@anthropic-ai/sdk'; import { _InternalSendLLMMessageFnType } from '../../common/llmMessageTypes.js'; import { anthropicMaxPossibleTokens } from '../../common/voidSettingsTypes.js'; -// Anthropic -type LLMMessageAnthropic = { - role: 'user' | 'assistant'; - content: string; -} export const sendAnthropicMsg: _InternalSendLLMMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => { const thisConfig = settingsOfProvider.anthropic @@ -24,20 +19,9 @@ export const sendAnthropicMsg: _InternalSendLLMMessageFnType = ({ messages, onTe const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); - // find system messages and concatenate them - const systemMessage = messages - .filter(msg => msg.role === 'system') - .map(msg => msg.content) - .join('\n'); - - // remove system messages for Anthropic - const anthropicMessages = messages.filter(msg => msg.role !== 'system') as LLMMessageAnthropic[] - - - const stream = anthropic.messages.stream({ - system: systemMessage, - messages: anthropicMessages, + // system: systemMessage, + messages: messages, model: modelName, max_tokens: maxTokens, }); diff --git a/src/vs/platform/void/electron-main/llmMessage/gemini.ts b/src/vs/platform/void/electron-main/llmMessage/gemini.ts index ee267e30..936a68f0 100644 --- a/src/vs/platform/void/electron-main/llmMessage/gemini.ts +++ b/src/vs/platform/void/electron-main/llmMessage/gemini.ts @@ -16,22 +16,17 @@ export const sendGeminiMsg: _InternalSendLLMMessageFnType = async ({ messages, o const genAI = new GoogleGenerativeAI(thisConfig.apiKey); const model = genAI.getGenerativeModel({ model: modelName }); - // remove system messages that get sent to Gemini - // str of all system messages - const systemMessage = messages - .filter(msg => msg.role === 'system') - .map(msg => msg.content) - .join('\n'); - // Convert messages to Gemini format const geminiMessages: Content[] = messages - .filter(msg => msg.role !== 'system') .map((msg, i) => ({ parts: [{ text: msg.content }], role: msg.role === 'assistant' ? 'model' : 'user' })) - model.generateContentStream({ contents: geminiMessages, systemInstruction: systemMessage, }) + model.generateContentStream({ + // systemInstruction: systemMessage, + contents: geminiMessages, + }) .then(async response => { _setAborter(() => response.stream.return(fullText)) diff --git a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts index 5d1866db..ebe6083c 100644 --- a/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/platform/void/electron-main/llmMessage/sendLLMMessage.ts @@ -3,7 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { LLMMMessageParams, OnText, OnFinalMessage, OnError } from '../../common/llmMessageTypes.js'; +import { LLMMMessageParams, OnText, OnFinalMessage, OnError, LLMMessage, _InternalLLMMessage } from '../../common/llmMessageTypes.js'; import { IMetricsService } from '../../common/metricsService.js'; import { sendAnthropicMsg } from './anthropic.js'; @@ -12,8 +12,43 @@ import { sendOpenAIMsg } from './openai.js'; import { sendGeminiMsg } from './gemini.js'; import { sendGroqMsg } from './groq.js'; + +const cleanMessages = (messages: LLMMessage[]): _InternalLLMMessage[] => { + // trim message content (Anthropic and other providers give an error if there is trailing whitespace) + messages = messages.map(m => ({ ...m, content: m.content.trim() })) + + // find system messages and concatenate them + const systemMessage = messages + .filter(msg => msg.role === 'system') + .map(msg => msg.content) + .join('\n') || undefined; + + // remove all system messages + const noSystemMessages = messages + .filter(msg => msg.role !== 'system') as _InternalLLMMessage[] + + // add system mesasges to first message (should be a user message) + if (systemMessage && (noSystemMessages.length !== 0)) { + const newFirstMessage = { + role: noSystemMessages[0].role, + content: ('' + + '\n' + + systemMessage + + '\n' + + '\n' + + noSystemMessages[0].content + ) + } + noSystemMessages.splice(0, 1) // delete first message + noSystemMessages.unshift(newFirstMessage) // add new first message + } + + return noSystemMessages +} + + export const sendLLMMessage = ({ - messages, + messages: messages_, onText: onText_, onFinalMessage: onFinalMessage_, onError: onError_, @@ -26,9 +61,7 @@ export const sendLLMMessage = ({ metricsService: IMetricsService ) => { - - // trim message content (Anthropic and other providers give an error if there is trailing whitespace) - messages = messages.map(m => ({ ...m, content: m.content.trim() })) + const messages = cleanMessages(messages_) // only captures number of messages and message "shape", no actual code, instructions, prompts, etc const captureChatEvent = (eventId: string, extras?: object) => { @@ -37,6 +70,9 @@ export const sendLLMMessage = ({ modelName, numMessages: messages?.length, messagesShape: messages?.map(msg => ({ role: msg.role, length: msg.content.length })), + origNumMessages: messages_?.length, + origMessagesShape: messages_?.map(msg => ({ role: msg.role, length: msg.content.length })), + ...extras, }) }