From 13020bc72ffcb9f2ae0db2c621de10e13d3ba10a Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 10 Apr 2026 01:16:55 -0700 Subject: [PATCH] feat(palace): add LLM memory extractor with regex fallback --- backend/src/modules/palace/extractor.ts | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 backend/src/modules/palace/extractor.ts diff --git a/backend/src/modules/palace/extractor.ts b/backend/src/modules/palace/extractor.ts new file mode 100644 index 0000000..15f3b31 --- /dev/null +++ b/backend/src/modules/palace/extractor.ts @@ -0,0 +1,73 @@ +import { + buildExtractionPrompt, + parseExtractionResponse, + regexFallbackExtraction, + hallFromLabel, + HALL_PRESETS, +} from '@bytelyst/palace'; +import type { ExtractedMemory } from '@bytelyst/palace'; +import { llm } from '../../lib/llm.js'; +import { config } from '../../lib/config.js'; +import { HALL_TYPES } from './types.js'; +import type { HallType } from './types.js'; + +export type { ExtractedMemory }; + +const NOTELETT_HALLS = HALL_PRESETS.notelett?.halls ?? HALL_TYPES; + +export interface NoteLettExtractedMemory { + hall: HallType; + content: string; + roomSlug: string; + entities: string[]; +} + +/** + * Extract memories from note content using LLM with regex fallback. + * + * Uses @bytelyst/palace shared extraction prompt builder + parser. + * Falls back to regex extraction if LLM is unavailable or fails. + */ +export async function extractMemories( + noteBody: string, + noteTitle: string, + workspaceName: string, +): Promise { + if (!config.PALACE_EXTRACTION_ENABLED) return []; + if (!noteBody || noteBody.trim().length === 0) return []; + + const content = noteBody.slice(0, 6000); + const prompt = buildExtractionPrompt(content, { + title: noteTitle, + context: workspaceName, + hallTypes: NOTELETT_HALLS, + }); + + let rawMemories: ExtractedMemory[]; + + try { + const provider = llm(); + const result = await provider.chatCompletion({ + model: config.LLM_DEFAULT_MODEL, + messages: [{ role: 'user', content: prompt }], + temperature: 0.1, + }); + rawMemories = parseExtractionResponse(result.content); + } catch { + rawMemories = regexFallbackExtraction(content); + } + + // Map to NoteLett halls, filtering out any that don't match + return rawMemories + .map(mem => { + const mappedHall = hallFromLabel(mem.hall, NOTELETT_HALLS as unknown as HallType[]) as HallType | undefined; + if (!mappedHall) return null; + return { + hall: mappedHall, + content: mem.content, + roomSlug: mem.roomSlug || 'general', + entities: mem.entities || [], + }; + }) + .filter((m): m is NoteLettExtractedMemory => m !== null); +}