mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
trim whitespace + fix onFinalMessage last chunk error
This commit is contained in:
parent
4e197f4659
commit
e331fb4eed
3 changed files with 51 additions and 18 deletions
|
|
@ -721,6 +721,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
||||||
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
onFinalMessage: async ({ fullText, fullReasoning, toolCall, anthropicReasoning, }) => {
|
||||||
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning, anthropicReasoning })
|
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
||||||
|
console.log('tool call!!', toolCall)
|
||||||
resMessageIsDonePromise(toolCall) // resolve with tool calls
|
resMessageIsDonePromise(toolCall) // resolve with tool calls
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { createSaxParser } from './sax.js'
|
||||||
// =============== reasoning ===============
|
// =============== reasoning ===============
|
||||||
|
|
||||||
// could simplify this - this assumes we can never add a tag without committing it to the user's screen, but that's not true
|
// could simplify this - this assumes we can never add a tag without committing it to the user's screen, but that's not true
|
||||||
export const extractReasoningOnTextWrapper = (
|
export const extractReasoningWrapper = (
|
||||||
onText: OnText, onFinalMessage: OnFinalMessage, thinkTags: [string, string]
|
onText: OnText, onFinalMessage: OnFinalMessage, thinkTags: [string, string]
|
||||||
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
||||||
let latestAddIdx = 0 // exclusive index in fullText_
|
let latestAddIdx = 0 // exclusive index in fullText_
|
||||||
|
|
@ -122,6 +122,10 @@ export const extractReasoningOnTextWrapper = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const newOnFinalMessage: OnFinalMessage = (params) => {
|
const newOnFinalMessage: OnFinalMessage = (params) => {
|
||||||
|
|
||||||
|
// treat like just got text before calling onFinalMessage (or else we sometimes miss the final chunk that's new to finalMessage)
|
||||||
|
newOnText({ ...params })
|
||||||
|
|
||||||
const { fullText, fullReasoning } = getOnFinalMessageParams()
|
const { fullText, fullReasoning } = getOnFinalMessageParams()
|
||||||
onFinalMessage({ ...params, fullText, fullReasoning })
|
onFinalMessage({ ...params, fullText, fullReasoning })
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +149,7 @@ type ToolsState = {
|
||||||
currentToolCall: RawToolCallObj,
|
currentToolCall: RawToolCallObj,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractToolsOnTextWrapper = (
|
export const extractToolsWrapper = (
|
||||||
onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode
|
onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode
|
||||||
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
||||||
const tools = availableTools(chatMode)
|
const tools = availableTools(chatMode)
|
||||||
|
|
@ -288,16 +292,44 @@ export const extractToolsOnTextWrapper = (
|
||||||
|
|
||||||
|
|
||||||
const newOnFinalMessage: OnFinalMessage = (params) => {
|
const newOnFinalMessage: OnFinalMessage = (params) => {
|
||||||
|
// treat like just got text before calling onFinalMessage (or else we sometimes miss the final chunk that's new to finalMessage)
|
||||||
|
newOnText({ ...params })
|
||||||
|
|
||||||
console.log('final message!!!', trueFullText)
|
console.log('final message!!!', trueFullText)
|
||||||
console.log('----- returning ----\n', fullText)
|
console.log('----- returning ----\n', fullText)
|
||||||
console.log('----- tools ----\n', JSON.stringify(currentToolCalls, null, 2))
|
console.log('----- tools ----\n', JSON.stringify(currentToolCalls, null, 2))
|
||||||
onFinalMessage({ ...params, fullText, toolCall: currentToolCalls[0] })
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fullText = fullText.trimEnd()
|
||||||
|
const toolCall = currentToolCalls[0]
|
||||||
|
if (toolCall) {
|
||||||
|
// trim off all whitespace at and before first \n and after last \n for each param
|
||||||
|
for (const paramName in toolCall.rawParams) {
|
||||||
|
const orig = toolCall.rawParams[paramName]
|
||||||
|
if (orig === undefined) continue
|
||||||
|
toolCall.rawParams[paramName] = trimBeforeAndAfterNewLines(orig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onFinalMessage({ ...params, fullText, toolCall: currentToolCalls.length > 0 ? currentToolCalls[0] : undefined })
|
||||||
|
}
|
||||||
return { newOnText, newOnFinalMessage };
|
return { newOnText, newOnFinalMessage };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// trim all whitespace up until the first newline, and all whitespace after the last newline
|
||||||
|
const trimBeforeAndAfterNewLines = (s: string) => {
|
||||||
|
if (!s) return s;
|
||||||
|
|
||||||
|
const firstNewLineIndex = s.indexOf('\n');
|
||||||
|
|
||||||
|
if (firstNewLineIndex !== -1 && s.substring(0, firstNewLineIndex).trim() === '') {
|
||||||
|
s = s.substring(firstNewLineIndex + 1, Infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastNewLineIndex = s.lastIndexOf('\n');
|
||||||
|
if (lastNewLineIndex !== -1 && s.substring(lastNewLineIndex + 1, Infinity).trim() === '') {
|
||||||
|
s = s.substring(0, lastNewLineIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, On
|
||||||
import { ChatMode, defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
import { ChatMode, defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||||
import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js';
|
import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js';
|
||||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js';
|
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js';
|
||||||
import { extractReasoningOnTextWrapper, extractToolsOnTextWrapper } from './extractGrammar.js';
|
import { extractReasoningWrapper, extractToolsWrapper } from './extractGrammar.js';
|
||||||
|
|
||||||
|
|
||||||
type InternalCommonMessageParams = {
|
type InternalCommonMessageParams = {
|
||||||
|
|
@ -156,14 +156,14 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
||||||
const { needsManualParse: needsManualReasoningParse, nameOfFieldInDelta: nameOfReasoningFieldInDelta } = providerReasoningIOSettings?.output ?? {}
|
const { needsManualParse: needsManualReasoningParse, nameOfFieldInDelta: nameOfReasoningFieldInDelta } = providerReasoningIOSettings?.output ?? {}
|
||||||
const manuallyParseReasoning = needsManualReasoningParse && canIOReasoning && openSourceThinkTags
|
const manuallyParseReasoning = needsManualReasoningParse && canIOReasoning && openSourceThinkTags
|
||||||
if (manuallyParseReasoning) {
|
if (manuallyParseReasoning) {
|
||||||
const { newOnText, newOnFinalMessage } = extractReasoningOnTextWrapper(onText, onFinalMessage, openSourceThinkTags)
|
const { newOnText, newOnFinalMessage } = extractReasoningWrapper(onText, onFinalMessage, openSourceThinkTags)
|
||||||
onText = newOnText
|
onText = newOnText
|
||||||
onFinalMessage = newOnFinalMessage
|
onFinalMessage = newOnFinalMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// manually parse out tool results
|
// manually parse out tool results
|
||||||
if (chatMode) {
|
if (chatMode) {
|
||||||
const { newOnText, newOnFinalMessage } = extractToolsOnTextWrapper(onText, onFinalMessage, chatMode)
|
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
|
||||||
onText = newOnText
|
onText = newOnText
|
||||||
onFinalMessage = newOnFinalMessage
|
onFinalMessage = newOnFinalMessage
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +281,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
|
|
||||||
// manually parse out tool results
|
// manually parse out tool results
|
||||||
if (chatMode) {
|
if (chatMode) {
|
||||||
const { newOnText, newOnFinalMessage } = extractToolsOnTextWrapper(onText, onFinalMessage, chatMode)
|
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
|
||||||
onText = newOnText
|
onText = newOnText
|
||||||
onFinalMessage = newOnFinalMessage
|
onFinalMessage = newOnFinalMessage
|
||||||
}
|
}
|
||||||
|
|
@ -290,8 +290,8 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
let fullText = ''
|
let fullText = ''
|
||||||
let fullReasoning = ''
|
let fullReasoning = ''
|
||||||
|
|
||||||
let fullToolName = ''
|
// let fullToolName = ''
|
||||||
let fullToolParams = ''
|
// let fullToolParams = ''
|
||||||
|
|
||||||
// there are no events for tool_use, it comes in at the end
|
// there are no events for tool_use, it comes in at the end
|
||||||
stream.on('streamEvent', e => {
|
stream.on('streamEvent', e => {
|
||||||
|
|
@ -313,10 +313,10 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
fullReasoning += '[redacted_thinking]'
|
fullReasoning += '[redacted_thinking]'
|
||||||
onText({ fullText, fullReasoning, })
|
onText({ fullText, fullReasoning, })
|
||||||
}
|
}
|
||||||
else if (e.content_block.type === 'tool_use') {
|
// else if (e.content_block.type === 'tool_use') {
|
||||||
fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
|
// fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
|
||||||
onText({ fullText, fullReasoning, })
|
// onText({ fullText, fullReasoning, })
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// delta
|
// delta
|
||||||
|
|
@ -329,10 +329,10 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
||||||
fullReasoning += e.delta.thinking
|
fullReasoning += e.delta.thinking
|
||||||
onText({ fullText, fullReasoning, })
|
onText({ fullText, fullReasoning, })
|
||||||
}
|
}
|
||||||
else if (e.delta.type === 'input_json_delta') { // tool use
|
// else if (e.delta.type === 'input_json_delta') { // tool use
|
||||||
fullToolParams += e.delta.partial_json ?? '' // anthropic gives us the partial delta (string) here - https://docs.anthropic.com/en/api/messages-streaming
|
// fullToolParams += e.delta.partial_json ?? '' // anthropic gives us the partial delta (string) here - https://docs.anthropic.com/en/api/messages-streaming
|
||||||
onText({ fullText, fullReasoning, })
|
// onText({ fullText, fullReasoning, })
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue