NODEDC_1C/llm_normalizer/backend/src/services/promptBuilder.ts

213 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from "fs";
import path from "path";
import { DEFAULT_PROMPT_VERSION, PROMPTS_DIR } from "../config";
import type { PromptBundle, PromptPreset, PromptVersion } from "../types/preset";
function readPromptFile(relativePath: string): string {
const filePath = path.resolve(PROMPTS_DIR, relativePath);
if (!fs.existsSync(filePath)) {
throw new Error(`Prompt file not found: ${filePath}`);
}
return fs.readFileSync(filePath, "utf-8").trim();
}
interface BuiltinPromptPresetDefinition {
id: string;
name: string;
promptVersion: PromptVersion;
schemaNotes: string;
files: {
system: string;
developer: string;
domain: string;
fewshot: string;
};
}
const BUILTIN_PROMPT_PRESETS: Record<PromptVersion, BuiltinPromptPresetDefinition> = {
normalizer_v1: {
id: "default-normalizer-v1",
name: "Стандартный пресет NDC v1",
promptVersion: "normalizer_v1",
schemaNotes: "Используется схема normalized_query_v1. Строго соблюдать enum/required поля.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "default.txt"),
domain: path.join("domain", "default.txt"),
fewshot: path.join("fewshot", "default.txt")
}
},
normalizer_v1_1: {
id: "default-normalizer-v1_1",
name: "Стандартный пресет NDC v1.1",
promptVersion: "normalizer_v1_1",
schemaNotes:
"v1.1: усиленная taxonomy intent/route и confidence policy. Используется схема normalized_query_v1 без дополнительных полей.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v1_1.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_fewshot_v1_1.txt")
}
},
normalizer_v1_1_1: {
id: "default-normalizer-v1_1_1",
name: "Стандартный пресет NDC v1.1.1",
promptVersion: "normalizer_v1_1_1",
schemaNotes:
"v1.1.1: surgical patch для period_close_risk, exact drilldown requires и anomaly route escalation. Схема normalized_query_v1 без изменений.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v1_1_1.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_fewshot_v1_1_1.txt")
}
},
normalizer_v1_1_2: {
id: "default-normalizer-v1_1_2",
name: "Стандартный пресет NDC v1.1.2",
promptVersion: "normalizer_v1_1_2",
schemaNotes:
"v1.1.2: точечный patch границы heavy_analytical vs period_close_risk + confidence guard на boundary кейсах. Схема normalized_query_v1 без изменений.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v1_1_2.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_fewshot_v1_1_2.txt")
}
},
normalizer_v1_1_2_1: {
id: "default-normalizer-v1_1_2_1",
name: "Стандартный пресет NDC v1.1.2.1",
promptVersion: "normalizer_v1_1_2_1",
schemaNotes:
"v1.1.2.1: stable prompt baseline v1.1.2 + accounting-review phrasing anchors for 30-case validation pack. Схема normalized_query_v1 без изменений.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v1_1_2_1.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_fewshot_v1_1_2_1.txt")
}
},
normalizer_v2: {
id: "default-normalizer-v2",
name: "Стандартный пресет NDC v2",
promptVersion: "normalizer_v2",
schemaNotes:
"v2: decomposition-first pre-router. LLM returns fragments + scope + flags; deterministic routing happens in code. Схема normalized_query_v2.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v2.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_v2.txt")
}
},
normalizer_v2_0_1: {
id: "default-normalizer-v2_0_1",
name: "Стандартный пресет NDC v2.0.1",
promptVersion: "normalizer_v2_0_1",
schemaNotes:
"v2.0.1: clarification-threshold policy. Вопросы в контуре и с понятным route должны исполняться без лишних уточнений. Схема normalized_query_v2_0_1.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v2_0_1.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_v2_0_1.txt")
}
},
normalizer_v2_0_2: {
id: "default-normalizer-v2_0_2",
name: "Стандартный пресет NDC v2.0.2",
promptVersion: "normalizer_v2_0_2",
schemaNotes:
"v2.0.2: execution-state hardening + explicit route_status/no_route_reason. Схема normalized_query_v2_0_2.",
files: {
system: path.join("system", "default.txt"),
developer: path.join("developer", "normalizer_v2_0_2.txt"),
domain: path.join("domain", "normalizer_domain_v1_1.txt"),
fewshot: path.join("fewshot", "normalizer_v2_0_2.txt")
}
}
};
function isPromptVersion(value: string | undefined): value is PromptVersion {
return (
value === "normalizer_v1" ||
value === "normalizer_v1_1" ||
value === "normalizer_v1_1_1" ||
value === "normalizer_v1_1_2" ||
value === "normalizer_v1_1_2_1" ||
value === "normalizer_v2" ||
value === "normalizer_v2_0_1" ||
value === "normalizer_v2_0_2"
);
}
function resolvePromptVersion(requested?: string): PromptVersion {
if (isPromptVersion(requested)) {
return requested;
}
if (isPromptVersion(DEFAULT_PROMPT_VERSION)) {
return DEFAULT_PROMPT_VERSION;
}
return "normalizer_v2_0_2";
}
function loadBuiltinPreset(promptVersion: PromptVersion): PromptPreset {
const now = new Date().toISOString();
const definition = BUILTIN_PROMPT_PRESETS[promptVersion];
return {
id: definition.id,
name: definition.name,
createdAt: now,
updatedAt: now,
prompt_version: definition.promptVersion,
systemPrompt: readPromptFile(definition.files.system),
developerPrompt: readPromptFile(definition.files.developer),
domainPrompt: readPromptFile(definition.files.domain),
schemaNotes: definition.schemaNotes,
fewShotExamples: readPromptFile(definition.files.fewshot)
};
}
export function listBuiltinPromptPresets(): PromptPreset[] {
return (Object.keys(BUILTIN_PROMPT_PRESETS) as PromptVersion[]).map((version) => loadBuiltinPreset(version));
}
export function loadDefaultPrompts(promptVersion?: string): PromptPreset {
return loadBuiltinPreset(resolvePromptVersion(promptVersion));
}
export function buildPromptBundle(input: {
promptVersion?: string;
systemPrompt?: string;
developerPrompt?: string;
domainPrompt?: string;
schemaNotes?: string;
fewShotExamples?: string;
}): PromptBundle {
const selectedPromptVersion = resolvePromptVersion(input.promptVersion);
const defaults = loadDefaultPrompts(selectedPromptVersion);
const systemPrompt = (input.systemPrompt ?? defaults.systemPrompt).trim();
const developerPrompt = (input.developerPrompt ?? defaults.developerPrompt).trim();
const domainPrompt = (input.domainPrompt ?? defaults.domainPrompt).trim();
const schemaNotes = (input.schemaNotes ?? defaults.schemaNotes ?? "").trim();
const fewShotExamples = (input.fewShotExamples ?? defaults.fewShotExamples ?? "").trim();
const prompt_version = (input.promptVersion ?? defaults.prompt_version).trim() || selectedPromptVersion;
const sections = [developerPrompt, `Schema notes:\n${schemaNotes}`];
if (fewShotExamples) {
sections.push(`Few-shot examples:\n${fewShotExamples}`);
}
return {
prompt_version,
systemPrompt,
developerPrompt,
domainPrompt,
schemaNotes,
fewShotExamples,
combinedDeveloperPrompt: sections.join("\n\n")
};
}