@convo-lang/convo-lang
Version:
The language of AI
367 lines (362 loc) • 15.8 kB
JavaScript
;
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