mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38: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, }) => {
|
||||
this._addMessageToThread(threadId, { role: 'assistant', content: fullText, reasoning: fullReasoning, anthropicReasoning })
|
||||
this._setStreamState(threadId, { messageSoFar: undefined, reasoningSoFar: undefined, streamingToken: undefined, toolCallSoFar: undefined }, 'merge')
|
||||
console.log('tool call!!', toolCall)
|
||||
resMessageIsDonePromise(toolCall) // resolve with tool calls
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { createSaxParser } from './sax.js'
|
|||
// =============== 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
|
||||
export const extractReasoningOnTextWrapper = (
|
||||
export const extractReasoningWrapper = (
|
||||
onText: OnText, onFinalMessage: OnFinalMessage, thinkTags: [string, string]
|
||||
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
||||
let latestAddIdx = 0 // exclusive index in fullText_
|
||||
|
|
@ -122,6 +122,10 @@ export const extractReasoningOnTextWrapper = (
|
|||
}
|
||||
|
||||
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()
|
||||
onFinalMessage({ ...params, fullText, fullReasoning })
|
||||
}
|
||||
|
|
@ -145,7 +149,7 @@ type ToolsState = {
|
|||
currentToolCall: RawToolCallObj,
|
||||
}
|
||||
|
||||
export const extractToolsOnTextWrapper = (
|
||||
export const extractToolsWrapper = (
|
||||
onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode
|
||||
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {
|
||||
const tools = availableTools(chatMode)
|
||||
|
|
@ -288,16 +292,44 @@ export const extractToolsOnTextWrapper = (
|
|||
|
||||
|
||||
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('----- returning ----\n', fullText)
|
||||
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 };
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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 { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js';
|
||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js';
|
||||
import { extractReasoningOnTextWrapper, extractToolsOnTextWrapper } from './extractGrammar.js';
|
||||
import { extractReasoningWrapper, extractToolsWrapper } from './extractGrammar.js';
|
||||
|
||||
|
||||
type InternalCommonMessageParams = {
|
||||
|
|
@ -156,14 +156,14 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage
|
|||
const { needsManualParse: needsManualReasoningParse, nameOfFieldInDelta: nameOfReasoningFieldInDelta } = providerReasoningIOSettings?.output ?? {}
|
||||
const manuallyParseReasoning = needsManualReasoningParse && canIOReasoning && openSourceThinkTags
|
||||
if (manuallyParseReasoning) {
|
||||
const { newOnText, newOnFinalMessage } = extractReasoningOnTextWrapper(onText, onFinalMessage, openSourceThinkTags)
|
||||
const { newOnText, newOnFinalMessage } = extractReasoningWrapper(onText, onFinalMessage, openSourceThinkTags)
|
||||
onText = newOnText
|
||||
onFinalMessage = newOnFinalMessage
|
||||
}
|
||||
|
||||
// manually parse out tool results
|
||||
if (chatMode) {
|
||||
const { newOnText, newOnFinalMessage } = extractToolsOnTextWrapper(onText, onFinalMessage, chatMode)
|
||||
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
|
||||
onText = newOnText
|
||||
onFinalMessage = newOnFinalMessage
|
||||
}
|
||||
|
|
@ -281,7 +281,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
|
||||
// manually parse out tool results
|
||||
if (chatMode) {
|
||||
const { newOnText, newOnFinalMessage } = extractToolsOnTextWrapper(onText, onFinalMessage, chatMode)
|
||||
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
|
||||
onText = newOnText
|
||||
onFinalMessage = newOnFinalMessage
|
||||
}
|
||||
|
|
@ -290,8 +290,8 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
let fullText = ''
|
||||
let fullReasoning = ''
|
||||
|
||||
let fullToolName = ''
|
||||
let fullToolParams = ''
|
||||
// let fullToolName = ''
|
||||
// let fullToolParams = ''
|
||||
|
||||
// there are no events for tool_use, it comes in at the end
|
||||
stream.on('streamEvent', e => {
|
||||
|
|
@ -313,10 +313,10 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
fullReasoning += '[redacted_thinking]'
|
||||
onText({ fullText, fullReasoning, })
|
||||
}
|
||||
else if (e.content_block.type === 'tool_use') {
|
||||
fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
|
||||
onText({ fullText, fullReasoning, })
|
||||
}
|
||||
// else if (e.content_block.type === 'tool_use') {
|
||||
// fullToolName += e.content_block.name ?? '' // anthropic gives us the tool name in the start block
|
||||
// onText({ fullText, fullReasoning, })
|
||||
// }
|
||||
}
|
||||
|
||||
// delta
|
||||
|
|
@ -329,10 +329,10 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM
|
|||
fullReasoning += e.delta.thinking
|
||||
onText({ fullText, fullReasoning, })
|
||||
}
|
||||
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
|
||||
onText({ fullText, fullReasoning, })
|
||||
}
|
||||
// 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
|
||||
// onText({ fullText, fullReasoning, })
|
||||
// }
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue