learning_ai_notes/backend/src/modules/note-prompts/runner.ts
saravanakumardb1 9e3a7206b9 feat(backend): add note-prompts module with Smart Actions LLM integration
- Add @bytelyst/llm dependency (file: ref) + llm.ts singleton wrapper
- Add LLM env vars to config (LLM_PROVIDER, LLM_DEFAULT_MODEL, LLM_VISION_MODEL, LLM_EMBEDDING_MODEL)
- Create note-prompts module: types, repository, runner, routes, seed (20 built-in templates)
- Built-in templates: 8 transform, 3 extract, 3 generate, 2 analyze, 2 vision, 2 export
- Prompt runner supports text, image, and text+image inputs via @bytelyst/llm vision
- Upgrade copilot-transform.ts to use @bytelyst/llm directly (with local heuristic fallback)
- Add reading-time endpoint (GET /api/notes/:id/reading-time)
- Extend agent-action types with smart_action and auto_enrich
- Add note_prompts Cosmos container to cosmos-init
- Register notePromptRoutes in server.ts
- 15 new tests (CRUD, run, slug resolution, seed validation, reading-time)
2026-04-06 08:01:42 -07:00

76 lines
2.1 KiB
TypeScript

/**
* Prompt runner — executes a PromptTemplate against note content via @bytelyst/llm.
*/
import { llm } from '../../lib/llm.js';
import { config } from '../../lib/config.js';
import {
buildVisionMessage,
hasVisionContent,
type ChatMessage,
} from '@bytelyst/llm';
import type { PromptTemplateDoc, RunPromptInput, RunPromptOutput } from './types.js';
/**
* Interpolate {{variable}} placeholders in a template string.
*/
function interpolate(template: string, vars: Record<string, string>): string {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
}
/**
* Run a prompt template against provided input.
* Handles text-only, image-only, and text+image inputs.
*/
export async function executePrompt(
template: PromptTemplateDoc,
input: RunPromptInput,
noteBody: string,
): Promise<RunPromptOutput> {
const provider = llm();
// Build variables map
const vars: Record<string, string> = {
...input.variables,
noteBody,
noteId: input.noteId,
workspaceId: input.workspaceId,
};
if (input.inputText) vars.inputText = input.inputText;
const userPrompt = interpolate(template.userPromptTemplate, vars);
// Build messages
const messages: ChatMessage[] = [
{ role: 'system', content: template.systemPrompt },
];
if (input.imageUrl && (template.inputType === 'image' || template.inputType === 'text+image')) {
messages.push(buildVisionMessage(userPrompt, input.imageUrl));
} else {
messages.push({ role: 'user', content: userPrompt });
}
// Select model: vision model for image content, custom model, or default
const req = { messages };
let model = template.model || config.LLM_DEFAULT_MODEL;
if (hasVisionContent(req)) {
model = config.LLM_VISION_MODEL;
}
const result = await provider.chatCompletion({
messages,
model,
temperature: template.temperature ?? 0.7,
maxTokens: template.maxTokens ?? 4096,
});
return {
content: result.content,
model: result.model,
usage: result.usage,
templateSlug: template.slug,
outputType: template.outputType,
};
}