UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

106 lines (105 loc) 3.82 kB
import { boolVal, listValOrUndef, strVal, strValOrUndef, } from "@paroi/data-formatters-lib"; import { insertIssueEvent } from "../../db/db-write.queries.js"; import { invokeClaude } from "../lib/calling-llm-anthropic.js"; import { createPromptTemplate } from "../lib/create-prompt.js"; import { debugLlmOutput } from "../lib/debug-utils.js"; import { parseLlmResponseAsProperties } from "../lib/parse-llm-response.js"; const guardPrompt = await createPromptTemplate({ filename: "message-guard.md", }); const invalidCauses = new Set(["rude", "malicious", "outOfScope", "technicalLimits", "noSense"]); export async function invokeMessageGuard(ctx, input, { skipOutOfScopeCheck = false } = {}) { const llmTaskName = "guard"; const llmInput = { message: input.prompt, }; const debug = await debugLlmOutput(ctx, llmTaskName, ctx.anthropicModelName, undefined, { message: llmInput.message, }); let llmOutput = debug.stored; if (!llmOutput) { const { messageContent, report } = await invokeClaude(ctx, { llmTaskName, prompt: guardPrompt(llmInput), maxTokens: 200, temperature: 0.2, systemInstruction: "beFast", }); llmOutput = await debug.getMessageContent(messageContent, report); } const rawReport = parseLlmResponseAsProperties(llmOutput.output, [ { tagName: "guard_yaml", key: "guard", format: "yaml", }, ]); const report = formatMessageGuardReport(rawReport.guard); if (report.causes && skipOutOfScopeCheck) { report.causes = report.causes.filter((cause) => cause !== "outOfScope"); if (report.causes.length === 0) { report.valid = true; } } if (report.valid) return; await insertIssueEvent(ctx, { eventType: "blockedByGuard", llmTaskName: "guard", issueMessage: report.explanation, }); return { success: false, language: report.language, userMessage: report.explanation ?? toUserMessage(report.causes, report.language), }; } function toUserMessage(causes, language) { if (!causes || causes.length === 0) return "Invalid message"; return causes.map((cause) => getCauseLabel(cause, language)).join(", "); } function getCauseLabel(cause, language) { if (language === "fr") { switch (cause) { case "rude": case "malicious": return "Nous ne pouvons pas répondre à cette demande"; case "outOfScope": return "Hors sujet"; case "technicalLimits": return "Limites techniques"; case "noSense": return "Nous ne comprenons pas la demande"; default: return cause; } } switch (cause) { case "rude": case "malicious": return "We cannot answer to this request"; case "outOfScope": return "Out of scope"; case "technicalLimits": return "Technical limits"; case "noSense": return "We do not understand the request"; default: return cause; } } function formatMessageGuardReport(rawReport) { return { language: strValOrUndef(rawReport.language, { varName: "language" }), valid: boolVal(rawReport.valid, { varName: "valid" }), causes: listValOrUndef(rawReport.causes, formatGuardInvalidCause), explanation: strValOrUndef(rawReport.explanation, { varName: "explanation" }), }; } function formatGuardInvalidCause(cause) { const v = strVal(cause, { varName: "cause" }); if (!invalidCauses.has(v)) throw new Error(`Invalid cause: "${v}"`); return v; }