Files
breakpilot-compliance/admin-compliance/app/api/sdk/compliance-advisor/chat/route.ts
T
Benjamin Admin cd3e0b15ad
CI / detect-changes (push) Successful in 6s
CI / branch-name (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / validate-canonical-controls (push) Successful in 6s
CI / loc-budget (push) Successful in 19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m4s
CI / test-go (push) Successful in 58s
CI / iace-gt-coverage (push) Successful in 16s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
fix(advisor): Compliance-Advisor auf prod reparieren — RAG via ai-sdk (bge-m3) + OVH-LLM
Der Floating-Compliance-Advisor war auf prod kaputt (502): RAG ging ueber
rag-service:8097 (auf prod nicht vorhanden) und der Chat ueber
OLLAMA_URL=ollama-embed (embedding-only, kein qwen2.5vl).

- RAG laeuft jetzt ueber die ai-compliance-sdk /sdk/v1/rag/search (bge-m3,
  prod-erreichbar) statt rag-service -> profitiert vom reicheren Embedding.
  (lib/sdk/agents/advisor-rag.ts)
- LLM-Kaskade: OVH/LiteLLM (gpt-oss-120b) zuerst, Ollama als Dev-Fallback.
  (lib/sdk/agents/advisor-llm.ts; OVH-Env via orca-infra admin-Block)
- ai-sdk: bp_compliance_recht in AllowedCollections ergaenzt (Whitelist war
  inkonsistent — die Fehlermeldung listete es bereits als erlaubt).
- Route auf die Module umgestellt (duenn); Controls-Augmentation unveraendert.
- Tests: advisor-rag + advisor-llm.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-19 09:22:44 +02:00

122 lines
4.3 KiB
TypeScript

/**
* Compliance Advisor Chat API
*
* Verbindet das ComplianceAdvisorWidget mit:
* 1. Multi-Collection-RAG ueber die ai-compliance-sdk (bge-m3) — siehe advisor-rag
* 2. Strukturierten Controls zum erkannten Thema — buildControlsContext
* 3. LLM-Kaskade OVH (prod) -> Ollama (Dev) — siehe advisor-llm
*
* Laenderspezifische Filterung (DE, AT, CH, EU). Streamt die Antwort als Text.
*/
import { NextRequest, NextResponse } from 'next/server'
import { readSoulFile } from '@/lib/sdk/agents/soul-reader'
import { buildControlsContext } from '@/lib/sdk/agents/controls-augmentation'
import { queryAdvisorRAG } from '@/lib/sdk/agents/advisor-rag'
import { streamAdvisorAnswer, type ChatMessage } from '@/lib/sdk/agents/advisor-llm'
type Country = 'DE' | 'AT' | 'CH' | 'EU'
const FALLBACK_SYSTEM_PROMPT = `# Compliance Advisor Agent
## Identitaet
Du bist der BreakPilot Compliance-Berater. Du hilfst Nutzern des AI Compliance SDK,
Datenschutz- und Compliance-Fragen in verstaendlicher Sprache zu beantworten.
## Kernprinzipien
- Quellenbasiert: Verweise auf DSGVO-Artikel, BDSG-Paragraphen
- Verstaendlich: Einfache, praxisnahe Sprache
- Ehrlich: Bei Unsicherheit empfehle Rechtsberatung
- Deutsch als Hauptsprache`
const COUNTRY_LABELS: Record<Country, string> = {
DE: 'Deutschland',
AT: 'Oesterreich',
CH: 'Schweiz',
EU: 'EU-weit',
}
function countryBlock(c: Country): string {
const label = COUNTRY_LABELS[c]
const nationalLaws =
c === 'DE'
? 'BDSG, TDDDG, TKG, UWG'
: c === 'AT'
? 'AT DSG, ECG, TKG, KSchG, MedienG'
: 'CH DSG, DSV, OR, UWG, FMG'
const guidance =
c === 'EU'
? 'EU-weiten Fragen: Beziehe dich auf EU-Verordnungen und -Richtlinien'
: `${label}: Beziehe nationale Gesetze (${nationalLaws}) mit ein`
return `\n\n## Laenderspezifische Auskunft
Der Nutzer hat "${label} (${c})" gewaehlt.
- Beziehe dich AUSSCHLIESSLICH auf ${c}-Recht + anwendbares EU-Recht
- Nenne IMMER explizit das Land in deiner Antwort
- Verwende NIEMALS Gesetze eines anderen Landes
- Bei ${guidance}`
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { message, history = [], currentStep = 'default', country } = body
if (!message || typeof message !== 'string') {
return NextResponse.json({ error: 'Message is required' }, { status: 400 })
}
const validCountry = (['DE', 'AT', 'CH', 'EU'] as const).includes(country)
? (country as Country)
: undefined
// 1. RAG (ai-sdk, bge-m3) + strukturierte Controls zum Thema — beide parallel
const [ragContext, controlsContext] = await Promise.all([
queryAdvisorRAG(message),
buildControlsContext(message),
])
// 2. System-Prompt zusammenbauen
const soulPrompt = await readSoulFile('compliance-advisor')
let systemContent = soulPrompt || FALLBACK_SYSTEM_PROMPT
if (validCountry) systemContent += countryBlock(validCountry)
if (ragContext) {
systemContent += `\n\n## Relevanter Kontext aus dem RAG-System\n\nNutze die folgenden Quellen fuer deine Antwort. Verweise in deiner Antwort auf die jeweilige Quelle:\n\n${ragContext}`
}
if (controlsContext) systemContent += `\n\n${controlsContext}`
systemContent += `\n\n## Aktueller SDK-Schritt\nDer Nutzer befindet sich im SDK-Schritt: ${currentStep}`
// 3. Nachrichten (History auf die letzten 6 begrenzen)
const messages: ChatMessage[] = [
{ role: 'system', content: systemContent },
...history.slice(-6).map((h: { role: string; content: string }) => ({
role: h.role === 'user' ? 'user' : 'assistant',
content: h.content,
})),
{ role: 'user', content: message },
]
// 4. LLM-Kaskade -> Plain-Text-Stream
const stream = await streamAdvisorAnswer(messages)
if (!stream) {
return NextResponse.json(
{ error: 'LLM nicht erreichbar. Weder OVH/LiteLLM noch Ollama haben geantwortet.' },
{ status: 502 },
)
}
return new NextResponse(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
} catch (error) {
console.error('Compliance advisor chat error:', error)
return NextResponse.json(
{ error: 'Verbindung zum LLM fehlgeschlagen.' },
{ status: 503 },
)
}
}