Sortana/modules/backends/chatCompletions.js

81 lines
2.5 KiB
JavaScript

const SYSTEM_PREFIX = `You are an email-classification assistant.
Read the email below and the classification criterion provided by the user.
`;
const DEFAULT_CUSTOM_SYSTEM_PROMPT = "Determine whether the email satisfies the user's criterion.";
const SYSTEM_SUFFIX = `
Return ONLY a JSON object on a single line of the form:
{"match": true, "reason": "<short explanation>"} - if the email satisfies the criterion
{"match": false, "reason": "<short explanation>"} - otherwise
Do not add any other keys, text, or formatting.`;
function extractLastJsonObject(text) {
let last = null;
let start = -1;
let depth = 0;
let inString = false;
let escape = false;
for (let i = 0; i < text.length; i += 1) {
const ch = text[i];
if (inString) {
if (escape) {
escape = false;
continue;
}
if (ch === "\\") {
escape = true;
continue;
}
if (ch === "\"") {
inString = false;
}
continue;
}
if (ch === "\"") {
inString = true;
continue;
}
if (ch === "{") {
if (depth === 0) {
start = i;
}
depth += 1;
continue;
}
if (ch === "}" && depth > 0) {
depth -= 1;
if (depth === 0 && start !== -1) {
last = text.slice(start, i + 1);
start = -1;
}
}
}
return last;
}
export const path = '/v1/chat/completions';
export function buildRequest(text, criterion, config) {
const payload = {
messages: [
{ role: 'system', content: SYSTEM_PREFIX + (config.customSystemPrompt || DEFAULT_CUSTOM_SYSTEM_PROMPT) + SYSTEM_SUFFIX },
{ role: 'user', content: `**Email Contents**\n\`\`\`\n${text}\n\`\`\`\nClassification Criterion: ${criterion}` }
], ...config.aiParams
};
if (config.model) payload.model = config.model;
return payload;
}
export function parseResponse(result) {
const content = result?.choices?.[0]?.message?.content;
if (typeof content !== 'string') throw new Error('Chat response missing text content.');
const candidate = extractLastJsonObject(content);
if (!candidate) throw new Error('No JSON object found in AI response.');
const obj = JSON.parse(candidate);
const matched = Object.hasOwn(obj, 'match') ? obj.match : obj.matched;
if (typeof matched !== 'boolean') throw new Error('AI response missing valid match boolean.');
const reason = obj.reason ?? obj.reasoning ?? obj.explaination;
return { matched, reason: typeof reason === 'string' ? reason : null, probability: null };
}