4.1 and 3.7 work!

This commit is contained in:
Andrew Pareles 2025-04-14 22:57:27 -07:00
parent 499a47904e
commit ee959ded08
4 changed files with 138 additions and 54 deletions

View file

@ -1437,14 +1437,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
const start = toolMessage.params.startLine === null ? `start` : `${toolMessage.params.startLine}`
const end = toolMessage.params.endLine === null ? `end` : `${toolMessage.params.endLine}`
const addStr = `(${start}-${end})`
componentParams.title += ` ${addStr}`
componentParams.desc1 += ` ${addStr}`
}
if (toolMessage.type === 'success') {
const { params, result } = toolMessage
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
if (result.hasNextPage && params.pageNumber === 1) // first page
componentParams.desc2 = '(more content available)'
componentParams.desc2 = '(truncated)'
else if (params.pageNumber > 1) // subsequent pages
componentParams.desc2 = `(part ${params.pageNumber})`
}

View file

@ -629,39 +629,6 @@ ${tripleTick[1]}).`
// const toAnthropicTool = (toolInfo: InternalToolInfo) => {
// const { name, description, params } = toolInfo
// return {
// name: name,
// description: description,
// input_schema: {
// type: 'object',
// properties: params,
// // required: Object.keys(params),
// },
// } satisfies Anthropic.Messages.Tool
// }
// const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => {
// const { name, description, params } = toolInfo
// return {
// type: 'function',
// function: {
// name: name,
// // strict: true, // strict mode - https://platform.openai.com/docs/guides/function-calling?api-mode=chat
// description: description,
// parameters: {
// type: 'object',
// properties: params,
// // required: Object.keys(params), // in strict mode, all params are required and additionalProperties is false
// // additionalProperties: false,
// },
// }
// } satisfies OpenAI.Chat.Completions.ChatCompletionTool
// }
/*
// ======================================================== ai search/replace ========================================================

View file

@ -135,7 +135,7 @@ export const extractReasoningWrapper = (
}
// =============== tools ===============
// =============== tools (XML) ===============
@ -256,7 +256,7 @@ const parseXMLPrefixToToolCall = (toolName: ToolName, toolId: string, str: strin
}
}
export const extractToolsWrapper = (
export const extractXMLToolsWrapper = (
onText: OnText, onFinalMessage: OnFinalMessage, chatMode: ChatMode | null
): { newOnText: OnText, newOnFinalMessage: OnFinalMessage } => {

View file

@ -10,10 +10,11 @@ import { MistralCore } from '@mistralai/mistralai/core.js';
import { fimComplete } from '@mistralai/mistralai/funcs/fimComplete.js';
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js';
import { AnthropicLLMChatMessage, LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js';
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getMaxOutputTokens } from '../../common/modelCapabilities.js';
import { extractReasoningWrapper, extractToolsWrapper } from './extractGrammar.js';
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js';
type InternalCommonMessageParams = {
@ -124,6 +125,55 @@ const _sendOpenAICompatibleFIM = ({ messages: { prefix, suffix, stopTokens }, on
}
const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => {
const { name, description, params } = toolInfo
return {
type: 'function',
function: {
name: name,
// strict: true, // strict mode - https://platform.openai.com/docs/guides/function-calling?api-mode=chat
description: description,
parameters: {
type: 'object',
properties: params,
// required: Object.keys(params), // in strict mode, all params are required and additionalProperties is false
// additionalProperties: false,
},
}
} satisfies OpenAI.Chat.Completions.ChatCompletionTool
}
const openAITools = (chatMode: ChatMode) => {
const allowedTools = availableTools(chatMode)
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
const openAITools: OpenAI.Chat.Completions.ChatCompletionTool[] = []
for (const t in allowedTools ?? {}) {
openAITools.push(toOpenAICompatibleTool(allowedTools[t]))
}
return openAITools
}
const openAIToolToRawToolCallObj = (name: string, toolParamsStr: string, id: string): RawToolCallObj | null => {
if (!isAToolName(name)) return null
const rawParams: RawToolParamsObj = {}
let input: unknown
try {
input = JSON.parse(toolParamsStr)
}
catch (e) {
return null
}
if (input === null) return null
if (typeof input !== 'object') return null
for (const paramName in voidTools[name].params) {
rawParams[paramName as ToolParamName] = (input as any)[paramName]
}
return { id, name, rawParams, doneParams: Object.keys(rawParams) as ToolParamName[], isDone: true }
}
// ------------ OPENAI-COMPATIBLE ------------
const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, chatMode, separateSystemMessage }: SendChatParams_Internal) => {
@ -140,12 +190,17 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions) // user's modelName_ here
const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {}
// tools
const potentialTools = chatMode !== null ? openAITools(chatMode) : null
const nativeToolsObj = potentialTools ? { tools: potentialTools } as const : {}
// instance
const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload })
const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelName,
messages: messages as any,
stream: true,
...nativeToolsObj,
// max_completion_tokens: maxTokens,
}
@ -160,7 +215,7 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
// manually parse out tool results if XML
if (!specialToolFormat) {
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode)
onText = newOnText
onFinalMessage = newOnFinalMessage
}
@ -168,6 +223,10 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
let fullReasoningSoFar = ''
let fullTextSoFar = ''
let toolName = ''
let toolId = ''
let toolParamsStr = ''
openai.chat.completions
.create(options)
.then(async response => {
@ -178,6 +237,17 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
const newText = chunk.choices[0]?.delta?.content ?? ''
fullTextSoFar += newText
// tool call
for (const tool of chunk.choices[0]?.delta?.tool_calls ?? []) {
const index = tool.index
if (index !== 0) continue
toolName += tool.function?.name ?? ''
toolParamsStr += tool.function?.arguments ?? '';
toolId += tool.id ?? ''
}
// reasoning
let newReasoning = ''
if (nameOfReasoningFieldInDelta) {
@ -189,11 +259,12 @@ const _sendOpenAICompatibleChat = ({ messages, onText, onFinalMessage, onError,
onText({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar })
}
// on final
if (!fullTextSoFar && !fullReasoningSoFar) {
if (!fullTextSoFar && !fullReasoningSoFar && !toolName) {
onError({ message: 'Void: Response from model was empty.', fullError: null })
}
else {
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null });
const toolCall = openAIToolToRawToolCallObj(toolName, toolParamsStr, toolId) ?? undefined
onFinalMessage({ fullText: fullTextSoFar, fullReasoning: fullReasoningSoFar, anthropicReasoning: null, toolCall: toolCall });
}
})
// when error/fail - this catches errors of both .create() and .then(for await)
@ -241,6 +312,43 @@ const _openaiCompatibleList = async ({ onSuccess: onSuccess_, onError: onError_,
// ------------ ANTHROPIC (HELPERS) ------------
const toAnthropicTool = (toolInfo: InternalToolInfo) => {
const { name, description, params } = toolInfo
return {
name: name,
description: description,
input_schema: {
type: 'object',
properties: params,
// required: Object.keys(params),
},
} satisfies Anthropic.Messages.Tool
}
const anthropicTools = (chatMode: ChatMode) => {
const allowedTools = availableTools(chatMode)
if (!allowedTools || Object.keys(allowedTools).length === 0) return null
const anthropicTools: Anthropic.Messages.ToolUnion[] = []
for (const t in allowedTools ?? {}) {
anthropicTools.push(toAnthropicTool(allowedTools[t]))
}
return anthropicTools
}
const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBlock): RawToolCallObj | null => {
const { id, name, input } = toolBlock
if (!isAToolName(name)) return null
const rawParams: RawToolParamsObj = {}
if (input === null) return null
if (typeof input !== 'object') return null
for (const paramName in voidTools[name].params) {
rawParams[paramName as ToolParamName] = (input as any)[paramName]
}
return { id, name, rawParams, doneParams: Object.keys(rawParams) as ToolParamName[], isDone: true }
}
// ------------ ANTHROPIC ------------
const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, separateSystemMessage, chatMode }: SendChatParams_Internal) => {
const {
@ -258,6 +366,11 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
// anthropic-specific - max tokens
const maxTokens = getMaxOutputTokens(providerName, modelName_, { isReasoningEnabled: !!reasoningInfo?.isReasoningEnabled })
// tools
const potentialTools = chatMode !== null ? anthropicTools(chatMode) : null
const nativeToolsObj = potentialTools ? { tools: potentialTools, tool_choice: { type: 'auto' } } as const : {}
// instance
const anthropic = new Anthropic({
apiKey: thisConfig.apiKey,
@ -270,11 +383,13 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
model: modelName,
max_tokens: maxTokens ?? 4_096, // anthropic requires this
...includeInPayload,
...nativeToolsObj,
})
// manually parse out tool results
if (!specialToolFormat) {
const { newOnText, newOnFinalMessage } = extractToolsWrapper(onText, onFinalMessage, chatMode)
const { newOnText, newOnFinalMessage } = extractXMLToolsWrapper(onText, onFinalMessage, chatMode)
onText = newOnText
onFinalMessage = newOnFinalMessage
}
@ -283,8 +398,8 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
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 => {
@ -306,10 +421,10 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
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
@ -322,17 +437,19 @@ const sendAnthropicChat = ({ messages, providerName, onText, onFinalMessage, onE
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, })
}
}
})
// on done - (or when error/fail) - this is called AFTER last streamEvent
stream.on('finalMessage', (response) => {
const anthropicReasoning = response.content.filter(c => c.type === 'thinking' || c.type === 'redacted_thinking')
onFinalMessage({ fullText, fullReasoning, anthropicReasoning })
const tools = response.content.filter(c => c.type === 'tool_use')
const toolCall = tools[0] ? anthropicToolToRawToolCallObj(tools[0]) ?? undefined : undefined
onFinalMessage({ fullText, fullReasoning, anthropicReasoning, toolCall, })
})
// on error
stream.on('error', (error) => {