UNPKG

@convo-lang/convo-lang

Version:
249 lines 10.5 kB
import { asType, deleteUndefined, getErrorMessage, parseMarkdownImages, zodTypeToJsonScheme } from "@iyio/common"; import { parseJson5 } from '@iyio/json5'; import { convoTags, createFunctionCallConvoCompletionMessage, createTextConvoCompletionMessage, getLastConvoContentMessage, getNormalizedFlatMessageList } from "./convo-lib.js"; /** * A conversation converter for OpenAI like APIs */ export class BaseOpenAiConvoConverter { constructor({ chatModel, visionModel, supportedInputTypes, supportedOutputTypes, userRoles, assistantRoles, systemRoles, functionRoles, models, hasVision, transformInput, transformOutput, includeModalities, }) { this.chatModel = chatModel; this.visionModel = visionModel; this.supportedInputTypes = supportedInputTypes ? [...supportedInputTypes] : []; Object.freeze(this.supportedInputTypes); this.supportedOutputTypes = supportedOutputTypes ? [...supportedOutputTypes] : []; Object.freeze(this.supportedOutputTypes); this.userRoles = userRoles ? [...userRoles] : ['user']; Object.freeze(this.userRoles); this.assistantRoles = assistantRoles ? [...assistantRoles] : ['assistant']; Object.freeze(this.assistantRoles); this.systemRoles = systemRoles ? [...systemRoles] : ['system']; Object.freeze(this.systemRoles); this.functionRoles = functionRoles ? [...functionRoles] : ['function']; Object.freeze(this.functionRoles); this.models = models ? [...models] : undefined; Object.freeze(this.models); this.hasVision = hasVision; this.transformInput = transformInput; this.transformOutput = transformOutput; this.includeModalities = includeModalities; } convertOutputToConvo(output, outputType, input, inputType, flat) { if (this.transformOutput) { output = this.transformOutput(output); } const msg = output.choices[0]; if (!msg) { return []; } let params; let callError; ; const tool = msg.message.tool_calls?.find(t => t.function); const toolFn = tool?.function; let fnName = undefined; const toolId = (tool && toolFn) ? tool.id : undefined; if (toolFn) { try { fnName = toolFn.name; params = parseJson5(toolFn.arguments ?? '{}'); } catch (ex) { callError = `Unable to parse arguments for ${toolFn.name} - ${getErrorMessage(ex)}\n${toolFn.arguments}`; } } if (fnName) { if (callError) { return [createTextConvoCompletionMessage({ flat, role: msg.message.role, content: callError, model: output.model, models: this.models, inputTokens: output.usage?.prompt_tokens, outputTokens: output.usage?.completion_tokens, })]; } return [createFunctionCallConvoCompletionMessage({ flat, callFn: fnName, callParams: params, toolId, model: output.model, models: this.models, inputTokens: output.usage?.prompt_tokens, outputTokens: output.usage?.completion_tokens, })]; } else { let content = msg.message.content; let vision = false; if (msg.message.images?.length) { const urls = msg.message.images.filter(i => i?.image_url?.url && i?.type === 'image_url').map(i => `![image](${i.image_url.url})`); if (urls.length) { content += '\n\n' + urls.join('\n\n'); vision = true; } } return [createTextConvoCompletionMessage({ flat, role: msg.message.role, content, model: output.model, models: this.models, inputTokens: output.usage?.prompt_tokens, outputTokens: output.usage?.completion_tokens, tags: vision ? { [convoTags.vision]: '' } : undefined })]; } } convertConvoToInput(flat, inputType) { const messages = getNormalizedFlatMessageList(flat); let visionCapable = flat.capabilities?.includes('vision'); const lastContentMessage = getLastConvoContentMessage(messages); const model = flat?.responseModel ?? (visionCapable ? this.visionModel : this.chatModel); if (!model) { throw new Error('Chat AI model not defined'); } const info = this.models?.find(m => m.name === model); if (info && info.inputCapabilities?.includes('image') || this.hasVision?.(model)) { visionCapable = true; } const oMsgs = []; const oFns = []; for (const m of messages) { if (m.fn) { oFns.push({ type: "function", function: deleteUndefined({ name: m.fn.name, description: m.fn.description, parameters: (m._fnParams ?? (m.fnParams ? (zodTypeToJsonScheme(m.fnParams) ?? {}) : {})) }) }); } else if (m.content !== undefined) { let content; const vc = (visionCapable || m.vision) && m.vision !== false && m.role !== 'system'; if (vc) { const items = parseMarkdownImages(m.content ?? '', { requireImgProtocol: true }); if (items.length === 1 && (typeof items[0]?.text === 'string')) { content = items[0]?.text ?? ''; } else { content = items.map(i => i.image ? { type: 'image_url', image_url: { url: i.image.url } } : { type: 'text', text: i.text ?? '' }); } } else { content = m.content ?? ''; } oMsgs.push(deleteUndefined(asType({ role: this.isKnownRole(m.role) ? m.role : 'user', content }))); } else if (m.called) { const toolId = m.tags?.['toolId'] ?? m.called.name; oMsgs.push({ role: 'assistant', content: null, tool_calls: [{ id: toolId, type: 'function', function: { name: m.called.name, arguments: JSON.stringify(m.calledParams), } }] }); oMsgs.push({ role: 'tool', tool_call_id: toolId, content: m.calledReturn === undefined ? 'function-called' : JSON.stringify(m.calledReturn), }); } } const jsonMode = lastContentMessage?.responseFormat === 'json'; const cParams = { model, response_format: jsonMode ? { type: 'json_object' } : undefined, stream: false, messages: oMsgs, tools: oFns?.length ? oFns : undefined, user: flat?.userId, tool_choice: flat.toolChoice ? ((typeof flat.toolChoice === 'string') ? flat.toolChoice : { type: "function", "function": flat.toolChoice }) : undefined }; if (this.includeModalities && flat.model?.outputCapabilities?.length) { cParams.modalities = flat.model.outputCapabilities; } if (flat.temperature !== undefined) { cParams.temperature = flat.temperature; } if (flat.topP !== undefined) { cParams.top_p = flat.topP; } if (flat.frequencyPenalty !== undefined) { cParams.frequency_penalty = flat.frequencyPenalty; } if (flat.presencePenalty !== undefined) { cParams.presence_penalty = flat.presencePenalty; } if (flat.logprobs !== undefined) { cParams.logprobs = flat.logprobs; } if (flat.reasoningEffort !== undefined) { cParams.reasoning_effort = (flat.reasoningEffort === 'min' ? 'minimal' : flat.reasoningEffort === 'md' ? 'medium' : flat.reasoningEffort); } if (flat.seed !== undefined) { cParams.seed = flat.seed; } if (flat.serviceTier !== undefined && (flat.serviceTier === 'auto' || flat.serviceTier === 'flex' || flat.serviceTier === 'priority' || flat.serviceTier == 'default')) { cParams.service_tier = flat.serviceTier; } if (flat.topLogprobs !== undefined) { cParams.top_logprobs = flat.topLogprobs; } if (flat.maxTokens !== undefined) { cParams.max_completion_tokens = flat.maxTokens; } if (flat.responseVerbosity !== undefined) { cParams.verbosity = flat.responseVerbosity === 'md' ? 'medium' : flat.responseVerbosity; } if (flat.logitBias !== undefined) { cParams.logit_bias = flat.logitBias; } if (flat.modelParams) { for (const e in flat.modelParams) { cParams[e] = flat.modelParams[e]; } } if (this.transformInput) { return this.transformInput(cParams); } else { return cParams; } } isKnownRole(role) { return (this.userRoles.includes(role) || this.assistantRoles.includes(role) || this.systemRoles.includes(role) || this.functionRoles.includes(role)); } } //# sourceMappingURL=BaseOpenAiConvoConverter.js.map