UNPKG

@convo-lang/convo-lang

Version:
367 lines (362 loc) 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.applyConvoModelConfigurationToOutput = exports.applyConvoModelConfigurationToInputAsync = exports.getConvoCompletionServicesForModelAsync = exports.getConvoCompletionServiceAsync = exports.completeConvoUsingCompletionServiceAsync = exports.requireConvertConvoOutput = exports.requireConvertConvoInput = exports.convertConvoOutput = exports.convertConvoInput = void 0; const common_1 = require("@iyio/common"); const json5_1 = require("@iyio/json5"); const ConvoError_1 = require("./ConvoError"); const convo_lib_1 = require("./convo-lib"); const convo_zod_1 = require("./convo-zod"); const convertConvoInput = (flat, inputType, converters) => { for (const converter of converters) { if (converter.supportedInputTypes.includes(inputType)) { return { success: true, converter, result: converter.convertConvoToInput(flat, inputType), }; } } return { success: false }; }; exports.convertConvoInput = convertConvoInput; const convertConvoOutput = (output, outputType, input, inputType, converters, flat) => { for (const converter of converters) { if (converter.supportedOutputTypes.includes(outputType)) { return { success: true, converter, result: converter.convertOutputToConvo(output, outputType, input, inputType, flat), }; } } return { success: false }; }; exports.convertConvoOutput = convertConvoOutput; const requireConvertConvoInput = (flat, inputType, converters) => { const r = (0, exports.convertConvoInput)(flat, inputType, converters); if (!r.success) { throw new Error(`No convo converter found for input type - ${inputType}`); } return r.result; }; exports.requireConvertConvoInput = requireConvertConvoInput; const requireConvertConvoOutput = (output, outputType, input, inputType, converters, flat) => { const r = (0, exports.convertConvoOutput)(output, outputType, input, inputType, converters, flat); if (!r.success) { throw new Error(`No convo converter found for output type - ${outputType}`); } return r.result; }; exports.requireConvertConvoOutput = requireConvertConvoOutput; const completeConvoUsingCompletionServiceAsync = async (flat, service, converters, ctx = {}) => { if (!service) { return []; } const input = (0, exports.requireConvertConvoInput)(flat, service.inputType, converters); const r = await service.completeConvoAsync(input, flat, ctx); await ctx.afterComplete?.(service, r, input, flat); return (0, exports.requireConvertConvoOutput)(r, service.outputType, input, service.inputType, converters, flat); }; exports.completeConvoUsingCompletionServiceAsync = completeConvoUsingCompletionServiceAsync; const getConvoCompletionServiceAsync = async (flat, services, updateTargetModel = false, cache) => { const serviceModels = await (0, exports.getConvoCompletionServicesForModelAsync)(flat.responseModel ?? convo_lib_1.convoAnyModelName, services, cache); for (const s of serviceModels) { if (s.service?.canComplete(s.model?.name ?? flat.responseModel ?? convo_lib_1.convoAnyModelName, flat)) { if (updateTargetModel && s.model) { flat.responseModel = s.model.name; flat.model = s.model; } return s; } } return undefined; }; exports.getConvoCompletionServiceAsync = getConvoCompletionServiceAsync; const getConvoCompletionServicesForModelAsync = async (model, services, cache) => { const cached = cache?.[model]; if (cached) { return cached; } const matches = []; for (const s of services) { const models = await (0, convo_lib_1.getConvoCompletionServiceModelsAsync)(s); if (!models.length) { matches.push({ priority: Number.MIN_SAFE_INTEGER, service: { service: s } }); continue; } let hasMatch = false; for (const m of models) { if (m.name === model) { matches.push({ priority: m.priority ?? 0, service: { service: s, model: m } }); hasMatch = true; } if (m.aliases) { for (const a of m.aliases) { if ((0, convo_lib_1.isConvoModelAliasMatch)(model, a)) { matches.push({ priority: a.priority ?? 0, service: { service: s, model: m } }); hasMatch = true; } } } } if (!hasMatch) { const m = models.find(m => m.isServiceDefault); if (m) { matches.push({ priority: Number.MIN_SAFE_INTEGER, service: { service: s, model: m } }); } } } matches.sort((a, b) => b.priority - a.priority); const service = matches.map(m => m.service); if (cache) { cache[model] = service; } return service; }; exports.getConvoCompletionServicesForModelAsync = getConvoCompletionServicesForModelAsync; const applyConvoModelConfigurationToInputAsync = async (model, flat, convo) => { const lastMsg = (0, convo_lib_1.getLastConvoMessageWithRole)(flat.messages, 'user'); const jsonMode = lastMsg?.responseFormat === 'json'; let hasFunctions = false; let fnSystem; // first role requirement const roleRequireAry = model.requiredFirstMessageRole ? (model.requiredFirstMessageRoleList ?? ['user', 'assistant']) : undefined; let firstRequiredRoleChecked = roleRequireAry === undefined; for (let i = 0; i < flat.messages.length; i++) { const msg = flat.messages[i]; if (!msg) { continue; } if (jsonMode && (model.jsonModeDisableFunctions || model.jsonModeImplementAsFunction) && msg?.fn && !msg.called) { flat.messages.splice(i, 1); i--; continue; } if (msg.responseFormat === 'json') { applyJsonModeToMessage(msg, model, flat); } if (msg.fn && !msg.called) { hasFunctions = true; } if (!model.supportsFunctionCalling) { if (msg.fn && !msg.called) { if (!fnSystem) { fnSystem = []; } fnSystem.push(`<function>\nName: ${msg.fn.name}\nDescription: ${msg.fn.description ?? ''}\nParameters JSON Scheme: ${JSON.stringify((msg._fnParams ?? (msg.fnParams ? ((0, common_1.zodTypeToJsonScheme)(msg.fnParams) ?? {}) : {})))}\n</function>\n`); flat.messages.splice(i, 1); i--; } else if (msg.called) { const updated = { ...msg }; flat.messages[i] = updated; delete updated.called; updated.role = 'assistant'; const fnCall = { functionName: msg.called.name, parameters: msg.calledParams ?? {} }; updated.content = JSON.stringify(fnCall, null, 4); const resultMsg = { role: 'user', content: (`The return value of calling ${msg.called.name} is:\`\`\` json\n${msg.calledReturn === undefined ? 'undefined' : JSON.stringify(msg.calledReturn, null, 4)}\n\`\`\``) }; flat.messages.splice(i + 1, 0, resultMsg); } } if (!firstRequiredRoleChecked && roleRequireAry?.includes(msg.role)) { firstRequiredRoleChecked = true; if (msg.role !== model.requiredFirstMessageRole) { const msg = { role: model.requiredFirstMessageRole ?? 'user', content: model.requiredFirstMessageRoleContent ?? 'You can start the conversation', tags: { [convo_lib_1.convoTags.hidden]: '' } }; if (convo.isUserMessage(msg)) { msg.isUser = true; } else if (convo.isAssistantMessage(msg)) { msg.isAssistant = true; } else if (convo.isSystemMessage(msg)) { msg.isSystem = true; } flat.messages.splice(i, 0, msg); i++; continue; } } } if (fnSystem) { fnSystem.unshift('## Function Calling\nYou can call functions when responding to the user if any of the ' + 'functions relate to the user\'s message.\n\n<callable-functions>\n'); fnSystem.push(`</callable-functions> To call a function respond with a JSON object with 2 properties, "functionName" and "parameters". The value of parameters property should conform to the function parameter JSON scheme. For example to call a function named "openFolder" with a user asks to open the folder named "My Documents" you would respond with the following JSON object. <call-function> { "functionName":"openFolder", "parameters":{ "folderName":"My Documents" } } </call-function> `); (0, convo_lib_1.insertSystemMessageIntoFlatConvo)(fnSystem.join(''), flat); } if (!jsonMode && model.enableRespondWithTextFunction && flat.messages.some(m => m.fn)) { await convo.flattenSourceAsync({ appendTo: flat, passExe: true, cacheName: 'responseWithTextFunction', convo: model.respondWithTextFunctionSource ?? /*convo*/ ` # You can call this function if no other functions match the user's message > respondWithText( # Message to response with text: string ) ` }); } if (jsonMode && model.jsonModeImplementAsFunction) { const isAry = lastMsg?.responseFormatIsArray; const convoType = `${isAry ? 'array(' : ''}${lastMsg?.responseFormatTypeName ?? 'any'}${isAry ? ')' : ''}`; await convo.flattenSourceAsync({ appendTo: flat, passExe: true, cacheName: 'respondWithJSONFunction_' + convoType, convo: model.respondWithJSONFunctionSource?.replace('__TYPE__', convoType) ?? /*convo*/ ` # You can call this function to return JSON values to the user > respondWithJSON( # JSON object. Do not serialize the value. value: ${convoType} ) ` }); } return { lastMsg, jsonMode, hasFunctions }; }; exports.applyConvoModelConfigurationToInputAsync = applyConvoModelConfigurationToInputAsync; const applyConvoModelConfigurationToOutput = (model, flat, output, { lastMsg, jsonMode, hasFunctions, }) => { for (let i = 0; i < output.length; i++) { const msg = output[i]; if (!msg) { continue; } if (hasFunctions && !model.supportsFunctionCalling && msg.content && msg.content.includes('"functionName"')) { try { let content = msg.content; if (content.includes('<call')) { content = content.replace(/<\/?call-?function\/?>/g, ''); } const call = (0, convo_lib_1.parseConvoJsonMessage)(content); if (call.functionName && call.parameters) { output[i] = (0, convo_lib_1.createFunctionCallConvoCompletionMessage)({ flat, callFn: call.functionName, callParams: call.parameters, toolId: (0, common_1.uuid)(), model: model.name, inputTokens: msg?.inputTokens, outputTokens: msg?.outputTokens, tokenPrice: msg.tokenPrice, }); } } catch { } } if (model.enableRespondWithTextFunction && msg.callFn === 'respondWithText') { output[i] = (0, convo_lib_1.createTextConvoCompletionMessage)({ flat, role: msg.role ?? 'assistant', content: msg.callParams?.text, model: msg.model ?? convo_lib_1.convoAnyModelName, inputTokens: msg.inputTokens, outputTokens: msg.outputTokens, tokenPrice: msg.tokenPrice, }); } if (model.jsonModeImplementAsFunction && jsonMode) { if (msg.callFn === 'respondWithJSON') { let paramValue = msg.callParams?.value ?? null; if (lastMsg?.responseFormatTypeName && lastMsg?.responseFormatTypeName !== 'string' && (typeof paramValue === 'string')) { paramValue = (0, json5_1.parseJson5)(paramValue); } output[i] = (0, convo_lib_1.createTextConvoCompletionMessage)({ flat, role: msg.role ?? 'assistant', content: JSON.stringify(paramValue), model: msg.model ?? convo_lib_1.convoAnyModelName, inputTokens: msg.inputTokens, outputTokens: msg.outputTokens, tokenPrice: msg.tokenPrice, defaults: { format: 'json', formatTypeName: lastMsg?.responseFormatTypeName, formatIsArray: lastMsg?.responseFormatIsArray, } }); } else { output.splice(i, 1); i--; } } } }; exports.applyConvoModelConfigurationToOutput = applyConvoModelConfigurationToOutput; const applyJsonModeToMessage = (msg, model, flat) => { if (msg.responseFormat !== 'json' || model.jsonModeInstructions) { return; } if (model.jsonModeInstructions) { (0, convo_lib_1.appendFlatConvoMessageSuffix)(msg, model.jsonModeInstructions); } else if (model.jsonModeImplementAsFunction) { (0, convo_lib_1.appendFlatConvoMessageSuffix)(msg, 'Call the respondWithJSON function'); } else if (msg.responseFormatTypeName) { const type = flat.exe.getVar(msg.responseFormatTypeName); let scheme = (0, convo_zod_1.convoTypeToJsonScheme)(type); if (!scheme) { throw new ConvoError_1.ConvoError('invalid-message-response-scheme', {}, `${msg.responseFormatTypeName} does not point to a convo type object `); } if (msg.responseFormatIsArray) { scheme = { type: 'object', required: ['values'], properties: { values: { type: 'array', items: scheme } } }; } (0, convo_lib_1.appendFlatConvoMessageSuffix)(msg, `Return a well formatted JSON ${msg.responseFormatIsArray ? 'array' : 'object'} that conforms to the following JSON Schema:\n${JSON.stringify(scheme)}`); } else { (0, convo_lib_1.appendFlatConvoMessageSuffix)(msg, `Return a well formatted JSON ${msg.responseFormatIsArray ? 'array' : 'object'}.`); } if (model.jsonModeInstructWrapInCodeBlock) { (0, convo_lib_1.appendFlatConvoMessageSuffix)(msg, 'Wrap the generated JSON in a markdown json code fence and do not include any pre or post-amble.'); } if (model.jsonModeInstructionsPrefix) { msg.suffix = `${model.jsonModeInstructionsPrefix}\n\n${msg.suffix}`; } if (model.jsonModeInstructionsSuffix) { msg.suffix = `${msg.suffix}\n\n${model.jsonModeInstructionsSuffix}`; } }; //# sourceMappingURL=convo-completion-lib.js.map