Extends the Compliance Advisor from a Q&A chatbot into a full drafting engine that can generate, validate, and refine compliance documents within Scope Engine constraints. Includes intent classifier, state projector, constraint enforcer, SOUL templates, Go backend endpoints, and React UI components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
169 lines
5.6 KiB
TypeScript
169 lines
5.6 KiB
TypeScript
/**
|
|
* 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<string, unknown>, 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 }
|
|
)
|
|
}
|
|
}
|