/** * Drafting Engine - Draft API * * Erstellt strukturierte Compliance-Dokument-Entwuerfe. * Baut dokument-spezifische Prompts aus SOUL-Template + State-Projection. * Gibt strukturiertes JSON zurueck. */ import { NextRequest, NextResponse } from 'next/server' const OLLAMA_URL = process.env.OLLAMA_URL || 'http://host.docker.internal:11434' const LLM_MODEL = process.env.COMPLIANCE_LLM_MODEL || 'qwen2.5vl:32b' // Import prompt builders 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' const constraintEnforcer = new ConstraintEnforcer() const DRAFTING_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.` 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 POST(request: NextRequest) { try { const body = await request.json() const { documentType, draftContext, instructions, existingDraft } = body if (!documentType || !draftContext) { return NextResponse.json( { error: 'documentType und draftContext sind erforderlich' }, { status: 400 } ) } // 1. Constraint Check (Hard Gate) 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 }) } // 2. Build document-specific prompt const draftPrompt = buildPromptForDocumentType(documentType, draftContext, instructions) // 3. Build messages const messages = [ { role: 'system', content: DRAFTING_SYSTEM_PROMPT }, ...(existingDraft ? [{ role: 'assistant', content: `Bisheriger Entwurf:\n${JSON.stringify(existingDraft.sections, null, 2)}`, }] : []), { role: 'user', content: draftPrompt }, ] // 4. Call LLM (non-streaming for structured output) const ollamaResponse = await fetch(`${OLLAMA_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: LLM_MODEL, messages, stream: false, options: { temperature: 0.15, num_predict: 16384, }, 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 || '' // 5. Parse JSON response 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 { // If not JSON, wrap raw content as single section sections = [{ id: 'raw', title: 'Entwurf', content: 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, } const response: DraftResponse = { draft, constraintCheck, tokensUsed: result.eval_count || 0, } return NextResponse.json(response) } catch (error) { console.error('Draft generation error:', error) return NextResponse.json( { error: 'Draft-Generierung fehlgeschlagen.' }, { status: 503 } ) } }