Files
breakpilot-compliance/admin-compliance/lib/sdk/agents/advisor-rag.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

92 lines
2.7 KiB
TypeScript

/**
* 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<ScoredPassage[]> {
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<string> {
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')
}