site-library/src/base/kbot-templates.ts

262 lines
9.5 KiB
TypeScript

import { IKBotTask } from "@polymech/kbot-d";
import { sync as read } from "@polymech/fs/read";
import { sync as exists } from "@polymech/fs/exists";
import { z } from "zod";
// Type definitions
const InstructionSchema = z.object({
flag: z.string(),
text: z.string()
});
const InstructionSetSchema = z.record(z.array(InstructionSchema));
export interface TemplateProps extends IKBotTask {
language?: string;
clazz?: string;
cache?: boolean;
disabled?: boolean;
template?: string;
renderer?: string;
}
const TemplateConfigSchema = z.object({
router: z.string().optional(),
_router: z.string().optional(),
model: z.string(),
preferences: z.string(),
mode: z.string(),
filters: z.string().optional(),
overrides: z.record(z.array(z.string())).optional(),
variables: z.record(z.string()).optional()
});
type TemplateConfig = z.infer<typeof TemplateConfigSchema>;
interface TemplateFunction {
(opts?: Partial<TemplateConfig>): TemplateConfig;
}
interface TemplateMap {
[key: string]: TemplateFunction | TemplateConfig;
}
const LLMConfigSchema = z.object({
templates: z.record(TemplateConfigSchema),
instructions: InstructionSetSchema.optional(),
defaults: z.record(z.array(z.string())).optional()
});
type LLMConfig = z.infer<typeof LLMConfigSchema>;
// Context enum for template switching
export const enum TemplateContext {
COMMONS = 'commons',
HOWTO = 'howto',
MARKETPLACE = 'marketplace'
}
// Default configuration
export const DEFAULT_CONFIG: LLMConfig = {
templates: {},
instructions: {},
defaults: {
tone: ["formal"],
content: ["spellCheck", "removeEmojis", "removePersonalPrefs", "shorten"],
moderation: ["mafiaFilter", "deprogramming"],
context: ["makerTutorials", "units"],
format: ["markdown"]
}
};
// Default template options
const DEFAULT_TEMPLATE_OPTIONS = {
mode: "completion",
preferences: "none",
filters: "code"
} as const;
// Load configuration from JSON file
const loadConfig = (context: TemplateContext = TemplateContext.COMMONS): LLMConfig => {
const configPath = `./src/config/templates/${context}.json`;
if (exists(configPath)) {
try {
const content = read(configPath);
if (typeof content === 'string') {
const parsed = JSON.parse(content);
return LLMConfigSchema.parse(parsed);
}
} catch (error) {
console.error(`Error loading ${context} config:`, error);
}
}
return DEFAULT_CONFIG;
};
// Helper function to build prompt from instruction sets
export const buildPrompt = (
instructions: z.infer<typeof InstructionSetSchema>,
defaults: Record<string, string[]>,
overrides?: Record<string, string[]>
): string => {
const getInstructions = (category: string, flags: string[]) => {
const set = instructions[category] || [];
return set.filter(x => flags.includes(x.flag)).map(x => x.text);
};
const merged = Object.keys(instructions).reduce((acc, category) => ({
...acc,
[category]: (overrides?.[category] ?? defaults[category] ?? [])
}), {} as Record<string, string[]>);
return Object.entries(merged)
.flatMap(([category, flags]) => getInstructions(category, flags))
.join("\n");
};
// Prompt system
const PromptSchema = z.object({
template: z.string(),
variables: z.record(z.string()).optional(),
format: z.enum(['text', 'json', 'markdown']).default('text')
});
type Prompt = z.infer<typeof PromptSchema>;
const PromptRegistrySchema = z.record(PromptSchema);
type PromptRegistry = z.infer<typeof PromptRegistrySchema>;
const DEFAULT_PROMPTS: PromptRegistry = {
keywords: {
template: "Return a list of max. 10 keywords that can be used for SEO purposes, separated by commas (dont comment, just the list) : ",
format: 'text'
},
references: {
template: "Return a list of useful references (only with links), as Markdown, grouped : Articles, Books, Papers, Youtube, Opensource Designs, ... Dont comment !",
format: 'markdown'
},
toolsAndHardware: {
template: "Extract the required tools, software hardware from the following tutorial. Return as Markdown chapters (H3) with very short bullet points (not bold), with links, max. 5.",
format: 'markdown'
},
learnedSkills: {
template: "Analyze the following tutorial and identify all the skills that a person would learn or improve by completing this project, modest, humble, simple - dont enflate (sustainable, recylcing, ...), Return as Markdown chapter (H2) with very short bullet points (not bold), max. 5.",
format: 'markdown'
},
local: {
template: "Markdown chapter (h4) with a list of local resources, services & suppliers, max. 5, with links, group by category. dont comment, just the list",
format: 'markdown'
}
};
// Helper function to render prompt
const renderPrompt = (prompt: Prompt, variables: Record<string, string> = {}): string => {
let result = prompt.template;
for (const [key, value] of Object.entries(variables)) {
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
}
return result;
};
// Template definitions
const createTemplate = (config: LLMConfig, name: string, defaults: Partial<TemplateConfig>, promptKey?: keyof PromptRegistry) => {
return (opts: Partial<TemplateConfig> = {}) => {
const template = config.templates[name] || defaults;
const prompt = promptKey ? renderPrompt(DEFAULT_PROMPTS[promptKey], opts.variables) : buildPrompt(
config.instructions || {},
config.defaults || {},
opts.overrides || template.overrides
);
const merged = {
...template,
...opts,
prompt
};
return merged;
};
};
export const createTemplates = (context: TemplateContext = TemplateContext.COMMONS) => {
const config = loadConfig(context);
switch (context) {
case TemplateContext.COMMONS:
return {
simple: createTemplate(config, 'simple', {
router: "openai",
model: "gpt-4o",
...DEFAULT_TEMPLATE_OPTIONS
}),
codeSimple: createTemplate(config, 'codeSimple', {
model: "gpt-4o",
...DEFAULT_TEMPLATE_OPTIONS
}),
research: createTemplate(config, 'research', {
router: "openai",
model: "gpt-4.5-preview",
...DEFAULT_TEMPLATE_OPTIONS
})
};
case TemplateContext.HOWTO:
return {
keywords: createTemplate(config, 'keywords', {
_router: "openai",
model: "google/gemini-exp-1206:free",
...DEFAULT_TEMPLATE_OPTIONS
}, 'keywords'),
references: createTemplate(config, 'references', {
_router: "openai",
model: "google/gemini-exp-1206:free",
...DEFAULT_TEMPLATE_OPTIONS
}, 'references'),
toolsAndHardware: createTemplate(config, 'toolsAndHardware', {
router: "openai",
model: "perplexity/sonar-deep-research",
...DEFAULT_TEMPLATE_OPTIONS
}, 'toolsAndHardware'),
requiredSkills: createTemplate(config, 'requiredSkills', {
router: "openai",
model: "gpt-4o",
...DEFAULT_TEMPLATE_OPTIONS
}),
learnedSkills: createTemplate(config, 'learnedSkills', {
router: "openai",
model: "gpt-4o",
...DEFAULT_TEMPLATE_OPTIONS
}, 'learnedSkills'),
local: createTemplate(config, 'local', {
model: "perplexity/sonar-deep-research",
...DEFAULT_TEMPLATE_OPTIONS
}, 'local')
};
case TemplateContext.MARKETPLACE:
return {
productDescription: createTemplate(config, 'productDescription', {
router: "openai",
model: "gpt-4o",
...DEFAULT_TEMPLATE_OPTIONS
}),
pricingAnalysis: createTemplate(config, 'pricingAnalysis', {
router: "openai",
model: "gpt-4o",
...DEFAULT_TEMPLATE_OPTIONS
}),
marketResearch: createTemplate(config, 'marketResearch', {
router: "openai",
model: "perplexity/sonar-deep-research",
...DEFAULT_TEMPLATE_OPTIONS
})
};
default:
return {};
}
};
// Export default templates with default config (commons context)
export const templates = createTemplates(TemplateContext.COMMONS);
// Export types
export type { TemplateConfig, LLMConfig, Prompt, PromptRegistry };
export { InstructionSchema, InstructionSetSchema, TemplateConfigSchema, LLMConfigSchema, PromptSchema, PromptRegistrySchema };