@convo-lang/convo-lang
Version:
The language of AI
249 lines • 10.5 kB
JavaScript
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 => ``);
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