/** * Compliance-Advisor RAG-Suche. * * Fragt die ai-compliance-sdk (`/sdk/v1/rag/search`) ab statt des frueheren * `rag-service:8097` (auf prod nicht erreichbar). Die ai-sdk embeddet die Query * mit bge-m3 (prod: ollama-embed) und sucht in den Qdrant-Compliance-Collections * — damit profitiert der Advisor vom reicheren Embedding. * * Fehler je Collection werden geschluckt (graceful: Antwort ohne diesen Treffer). */ const SDK_URL = process.env.SDK_API_URL || process.env.SDK_URL || 'http://ai-compliance-sdk:8090' const DEFAULT_USER = '00000000-0000-0000-0000-000000000001' const DEFAULT_TENANT = process.env.DEFAULT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' // Compliance-relevante Collections (ai-sdk-Whitelist `AllowedCollections`). export const COMPLIANCE_COLLECTIONS = [ 'bp_compliance_gesetze', 'bp_compliance_ce', 'bp_compliance_datenschutz', 'bp_dsfa_corpus', 'bp_compliance_recht', 'bp_legal_templates', ] as const interface SdkRagResult { text?: string regulation_code?: string regulation_name?: string regulation_short?: string category?: string source_url?: string score?: number } interface ScoredPassage { content: string source: string score: number } /** Normalisiert eine ai-sdk-RAG-Antwort auf {content, source, score}. */ export function mapSdkResults(results: SdkRagResult[] | undefined): ScoredPassage[] { return (results || []) .map((r) => ({ content: r.text || '', source: r.regulation_short || r.regulation_name || r.regulation_code || 'Unbekannt', score: typeof r.score === 'number' ? r.score : 0, })) .filter((p) => p.content) } async function searchCollection(collection: string, query: string): Promise { try { const res = await fetch(`${SDK_URL}/sdk/v1/rag/search`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-User-ID': DEFAULT_USER, 'X-Tenant-ID': DEFAULT_TENANT, }, body: JSON.stringify({ query, collection, top_k: 3 }), signal: AbortSignal.timeout(10000), }) if (!res.ok) return [] const data = await res.json() return mapSdkResults(data.results) } catch { return [] } } /** * Fragt alle Compliance-Collections parallel ab und liefert die Top-8-Passagen * als formatierten Kontextblock (oder '' wenn nichts erreichbar/gefunden). */ export async function queryAdvisorRAG(query: string): Promise { const settled = await Promise.all( COMPLIANCE_COLLECTIONS.map((c) => searchCollection(c, query)), ) const all = settled.flat() if (all.length === 0) return '' all.sort((a, b) => b.score - a.score) return all .slice(0, 8) .map((r, i) => `[Quelle ${i + 1}: ${r.source}]\n${r.content}`) .join('\n\n---\n\n') }