/** * Drafting Engine - Draft Helper Functions (v1 pipeline + shared constants) * * Shared state, v1 legacy pipeline helpers. * v2 pipeline lives in draft-helpers-v2.ts. */ import { NextResponse } from 'next/server' import { buildVVTDraftPrompt } from '@/lib/sdk/drafting-engine/prompts/draft-vvt' import { buildTOMDraftPrompt } from '@/lib/sdk/drafting-engine/prompts/draft-tom' import { buildDSFADraftPrompt } from '@/lib/sdk/drafting-engine/prompts/draft-dsfa' import { buildPrivacyPolicyDraftPrompt } from '@/lib/sdk/drafting-engine/prompts/draft-privacy-policy' import { buildLoeschfristenDraftPrompt } from '@/lib/sdk/drafting-engine/prompts/draft-loeschfristen' import type { DraftContext, DraftResponse, DraftRevision, DraftSection } from '@/lib/sdk/drafting-engine/types' import type { ScopeDocumentType } from '@/lib/sdk/compliance-scope-types' import { ConstraintEnforcer } from '@/lib/sdk/drafting-engine/constraint-enforcer' import { ProseCacheManager } from '@/lib/sdk/drafting-engine/cache' import { queryRAG } from '@/lib/sdk/drafting-engine/rag-query' import { DOCUMENT_RAG_CONFIG } from '@/lib/sdk/drafting-engine/rag-config' const OLLAMA_URL = process.env.OLLAMA_URL || 'http://host.docker.internal:11434' const LLM_MODEL = process.env.COMPLIANCE_LLM_MODEL || 'qwen2.5vl:32b' export const constraintEnforcer = new ConstraintEnforcer() export const proseCache = new ProseCacheManager({ maxEntries: 200, ttlHours: 24 }) export const TEMPLATE_VERSION = '2.0.0' export const TERMINOLOGY_VERSION = '1.0.0' export const VALIDATOR_VERSION = '1.0.0' // ============================================================================ // v1 Legacy Pipeline // ============================================================================ export const V1_SYSTEM_PROMPT = `Du bist ein DSGVO-Compliance-Experte und erstellst strukturierte Dokument-Entwuerfe. Du MUSST immer im JSON-Format antworten mit einem "sections" Array. Jede Section hat: id, title, content, schemaField. Halte die Tiefe strikt am vorgegebenen Level. Markiere fehlende Informationen mit [PLATZHALTER: Beschreibung]. Sprache: Deutsch.` export function buildPromptForDocumentType( documentType: ScopeDocumentType, context: DraftContext, instructions?: string ): string { switch (documentType) { case 'vvt': return buildVVTDraftPrompt({ context, instructions }) case 'tom': return buildTOMDraftPrompt({ context, instructions }) case 'dsfa': return buildDSFADraftPrompt({ context, instructions }) case 'dsi': return buildPrivacyPolicyDraftPrompt({ context, instructions }) case 'lf': return buildLoeschfristenDraftPrompt({ context, instructions }) default: return `## Aufgabe: Entwurf fuer ${documentType} ### Level: ${context.decisions.level} ### Tiefe: ${context.constraints.depthRequirements.depth} ### Erforderliche Inhalte: ${context.constraints.depthRequirements.detailItems.map((item, i) => `${i + 1}. ${item}`).join('\n')} ${instructions ? `### Anweisungen: ${instructions}` : ''} Antworte als JSON mit "sections" Array.` } } export async function handleV1Draft(body: Record): Promise { const { documentType, draftContext, instructions, existingDraft } = body as { documentType: ScopeDocumentType draftContext: DraftContext instructions?: string existingDraft?: DraftRevision } const constraintCheck = constraintEnforcer.checkFromContext(documentType, draftContext) if (!constraintCheck.allowed) { return NextResponse.json({ draft: null, constraintCheck, tokensUsed: 0, error: 'Constraint-Verletzung: ' + constraintCheck.violations.join('; '), }, { status: 403 }) } const ragCfg = DOCUMENT_RAG_CONFIG[documentType] const ragContext = await queryRAG(ragCfg.query, 3, ragCfg.collection) let v1SystemPrompt = V1_SYSTEM_PROMPT if (ragContext) { v1SystemPrompt += `\n\n## Relevanter Rechtskontext\n${ragContext}` } const draftPrompt = buildPromptForDocumentType(documentType, draftContext, instructions) const messages = [ { role: 'system', content: v1SystemPrompt }, ...(existingDraft ? [{ role: 'assistant', content: `Bisheriger Entwurf:\n${JSON.stringify(existingDraft.sections, null, 2)}`, }] : []), { role: 'user', content: draftPrompt }, ] const ollamaResponse = await fetch(`${OLLAMA_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: LLM_MODEL, messages, stream: false, think: false, options: { temperature: 0.15, num_predict: 16384, num_ctx: 8192 }, format: 'json', }), signal: AbortSignal.timeout(180000), }) if (!ollamaResponse.ok) { return NextResponse.json( { error: `LLM nicht erreichbar (Status ${ollamaResponse.status})` }, { status: 502 } ) } const result = await ollamaResponse.json() const content = result.message?.content || '' let sections: DraftSection[] = [] try { const parsed = JSON.parse(content) sections = (parsed.sections || []).map((s: Record, i: number) => ({ id: String(s.id || `section-${i}`), title: String(s.title || ''), content: String(s.content || ''), schemaField: s.schemaField ? String(s.schemaField) : undefined, })) } catch { sections = [{ id: 'raw', title: 'Entwurf', content }] } const draft: DraftRevision = { id: `draft-${Date.now()}`, content: sections.map(s => `## ${s.title}\n\n${s.content}`).join('\n\n'), sections, createdAt: new Date().toISOString(), instruction: instructions as string | undefined, } return NextResponse.json({ draft, constraintCheck, tokensUsed: result.eval_count || 0, } satisfies DraftResponse) } // Re-export v2 handler for route.ts (backward compat — single import point) export { handleV2Draft } from './draft-helpers-v2'