Files
boc/intelligence/eslm-b-rag-injektion-design.md
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

462 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ESLM-B: RAG/CANON Runtime-Fakta-Injektion — Design
**Datum:** 2026-06-06
**Status:** Design + stub (EJ deployad)
**Utfört av:** Bernt (subagent ESLM-B)
**Kod-stub:** `/home/bernt/.openclaw/workspace/build/eslm-rag-injection.mjs`
---
## 0. Verifierat vs Antagande
| Symbol | Betydelse |
|--------|-----------|
| ✅ VERIFIERAT | Läst ur faktisk körande kod/fil |
| ⚠️ ANTAGANDE | Logisk slutledning, ej källverifierad |
---
## 1. NULÄGE — Exakt karta (verifierad mot körande kod)
### 1.1 Produktionsserver — var den kör
**Tjänst:** `amos-core.service` via systemd
**Fil:** `/opt/amos/services/amos-core/server.mjs`
**Port:** 3100 (bind 0.0.0.0)
**AMOS_ROOT:** `/opt/amos/` (resolve av `services/amos-core/../..`)
**Node.js:** via `ExecStart=/usr/bin/node --max-old-space-size=2048`
### 1.2 Hur systemprompten byggs — chat-flödet
Det finns **två chat-endpoints** som båda använder samma systemprompt-logik:
#### `/api/aamos/chat` (huvud-AAMOS-chat)
```
[request] POST /api/aamos/chat
[routes.mjs] /opt/amos/api/aamos/routes.mjs:77
↓ import getEnrichedPrompt
[system-prompt.mjs] /opt/amos/api/dynasty-counsel/system-prompt.mjs
├── getFactsContext() ← HÅRDKODAD fil: wavult-facts.mjs
└── getToolsContextBlock() + buildPersonaBlock()
systemPrompt = getEnrichedPrompt(baseContext) + aamosContext [rad 109]
[model-router.mjs] selectModelCompliant() → Qwen3 via AWS Bedrock
[qwenStream()] SSE-svar till klient
```
**Verifierat:** `api/aamos/routes.mjs` importerar `getEnrichedPrompt` från `api/dynasty-counsel/system-prompt.mjs`
**Verifierat:** `getEnrichedPrompt` anropar `getFactsContext()` som läser `wavult-facts.mjs`
#### `/api/dynasty-counsel/chat` (juridisk assistent)
```
[request] POST /api/dynasty-counsel/chat
[routes.mjs] /opt/amos/api/dynasty-counsel/routes.mjs
↓ const systemPrompt = getEnrichedPrompt(enrichedContext) [rad ~240]
[system-prompt.mjs] → getFactsContext() → wavult-facts.mjs
[runToolLoop()] med tool-loop.mjs
SSE-stream till klient
```
**Verifierat:** Samma `getEnrichedPrompt` används
**Verifierat:** `WAVULT_FACTS` importeras direkt i routes.mjs rad ~20
#### `/api/chat` (generisk Qwen3-endpoint)
**Verifierat:** `server.mjs` rad 1389 — `app.post('/api/chat', ...)` använder **INTE** `getEnrichedPrompt`. Minimal hårdkodad prompt: `"Du är AAMOS — Wavult Groups AI-plattform (Qwen3 via AWS Bedrock). Svara precist."`**ingen faktainjektion alls**.
### 1.3 Var injiceras getFactsContext()
```
getEnrichedPrompt(context) [system-prompt.mjs]
├─ BASE_SYSTEM_PROMPT (statisk, ~4KB)
├─ getFactsContext() ← HÅRDKODAD import: wavult-facts.mjs
│ └─ formaterar WAVULT_FACTS till ~1KB kontextblock
├─ getToolsContextBlock() (tool-katalog)
├─ buildPersonaBlock() (user-specifik persona)
└─ session context (mode, tenant, user_email...)
```
**Verifierat:** `system-prompt.mjs` rad ~1: `import { getFactsContext } from './knowledge/wavult-facts.mjs';`
### 1.4 wavult-facts.mjs — nuläge
**Fil:** `/opt/amos/api/dynasty-counsel/knowledge/wavult-facts.mjs`
**Version:** 1.1.0, uppdaterad 2026-06-06
**Innehåll:** Hardkodad JavaScript-modul med WAVULT_FACTS objekt
**Storlek:** ~5KB av produkt/entitets-/teamfakta
**Backup-filer:** `.bak-canon-20260606-161310`, `.bak-cleanup-20260606-163506`, `.bak-purge` — indikerar nyliga ändringar
**Obs:** `wavult-facts.mjs` innehåller AAMOS_CANON-data som har duplicerats manuellt hit. Den är *inte* automatgenererad från CANON.
### 1.5 rag.mjs — nuläge
**Fil:** `/opt/amos/api/rag.mjs`
**RAG-index:** `/opt/amos/data/rag_index.json` — byggt 2026-06-06T15:57:39Z
**Embeddings:** `/opt/amos/data/rag_embeddings.json`**94/94 chunks har faktiska embeddings**
**Embedding-modell:** OpenAI `text-embedding-3-small`
**Retrieval:** Primär semantisk (cosine similarity), fallback BM25
**Export:** `retrieveAsync()`, `retrieve()`, `formatContext()`, `buildIndex()`
**AAMOS_CANON.md är tier-1 i rag.mjs:**
```javascript
// /opt/amos/api/rag.mjs — KNOWLEDGE_SOURCES
{
id: 'aamos_canon',
label: 'AAMOS_CANON.md',
tier: 1, // ENDA KANONISKA SANNINGSKÄLLAN — Erik-låst 2026-06-06
path: join(DATA_DIR, 'AAMOS_CANON.md'),
}
```
**Verifierat:** AAMOS_CANON är tier-1 med 11 chunks i nuvarande index
**Kritisk observation:**
**VERIFIERAT:** `rag.mjs` används **INTE** av `/api/aamos/chat` eller `/api/dynasty-counsel/chat`.
Varken `api/aamos/routes.mjs` eller `api/dynasty-counsel/routes.mjs` importerar `rag.mjs` eller anropar `retrieveAsync()`.
RAG-systemet existerar och är fullt byggt med embeddings, men är **kopplat bort** från huvud-chat-flödet.
### 1.6 Index/embeddings-status per källa
| Källa | Tier | Chunks | Embeddings |
|-------|------|--------|-----------|
| AAMOS_CANON.md | 1 | 11 | 11/11 ✅ |
| WAVULT_TRUTH.md | 1 | 17 | 17/17 ✅ |
| AMOS_KNOWLEDGE.md | 1 | 56 | 56/56 ✅ |
| AMOS_CONSTITUTION.md | 1 | 10 | 10/10 ✅ |
| **Totalt** | | **94** | **94/94** |
**RAG-indexet är byggt och komplett.**
### 1.7 ESLM-modellen
⚠️ **ANTAGANDE:** ESLM (v5) = `aamos-eslm` modell på vLLM-endpoint `172.31.36.61:8000`.
**Verifierat:** `eslm-judge.mjs` refererar till `ESLM_ENDPOINT = 'http://172.31.36.61:8000/v1'` och `ESLM_MODEL = 'aamos-eslm'`.
⚠️ **Okänt:** Om ESLM-endpointen är live (curl timeout). ESLM kan vara under driftsättning.
---
## 2. PROBLEMANALYS — Gap mellan nuläge och målarkitektur
```
NULÄGE:
/api/aamos/chat → getEnrichedPrompt() → wavult-facts.mjs (hårdkodad JS)
/api/dynasty-counsel/chat → samma
rag.mjs (med CANON tier-1) → ANVÄNDS INTE av chatten
ESLM-modell (172.31.36.61:8000) → ingen faktainjektion alls
PROBLEM:
1. wavult-facts.mjs är en kopia av CANON-data, ej härledd från CANON
2. RAG/CANON-systemet existerar men är frånkopplat
3. Generell ESLM saknar ALL faktainjektion
4. /api/chat (generisk) = ingen injektion alls
```
---
## 3. INJEKTIONSDESIGN
### 3.1 Principer (Claude×Siemens-robust)
1. **En källa (CANON):** AAMOS_CANON.md är enda auktoritativa källan. wavult-facts.mjs deprecas eller autogenereras från CANON.
2. **Intelligent routing:** Klassificera frågan → injicera bara vid behov → generella frågor = ren generell modell.
3. **Ingen single point of failure:** RAG → statisk CANON-fallback → minimal hardkodad fallback.
4. **Latency-gräns:** RAG-anrop max 2s (timeout) → chat-svar ska ej fördröjas märkbart.
5. **Sekretess:** CANON-data injiceras bara i system-rollen, aldrig tillbaka till klienten.
### 3.2 Flödesdiagram
```
[User query]
classifyQuery(query)
├─ 'generic' → systemPrompt = basePrompt (ingen injektion)
└─ 'company'
retrieveAsync(query, {topK:5, minScore:0.15})
├─ OK (chunks > 0) → formatContext() → RAG-block
└─ FAIL/timeout → getStaticCanonFallback()
systemPrompt = CANON-block + '---' + basePrompt
[LLM anrop: ESLM eller Qwen3]
[Svar till klient]
```
### 3.3 Klassificering — COMPANY vs GENERIC
**Company-keywords (komplett lista i kod-stub):**
- Bolagsnamn: `landvex`, `quixzoom`, `aamos`, `wavult`, `ouroboros`, `vyra`
- Teamnamn: `erik`, `winston`, `dennis`, `johan`, `bernt`, `sven`, `kjell`, `rufus`
- Produktspecifikt: `zoomer`, `kontrollintelligens`, `mission`, `uppdrag`, `gecl`, `stammregister`
- Org-nr: `559141-7042`, `dmcc`
**Generic-mönster (ingen injektion):**
- Frågor om GDPR/MOMS/bokföring som inte rör Wavult specifikt
- Kodfrågor (JavaScript, Python, SQL)
- Allmänna "explain X"-frågor
**Heuristik:** Svenska frågor med "vi/vår/våra/bolaget" → company
### 3.4 buildContextForQuery() — Huvud-API
Se kod-stub för komplett implementation. Signatur:
```javascript
/**
* @param {string} query - Användarens fråga
* @param {object} opts
* @returns {Promise<{
* contextBlock: string, // Injicera i systemPrompt ('' om generic)
* source: 'rag'|'static'|'none',
* classification: 'company'|'generic'
* }>}
*/
async function buildContextForQuery(query, { timeoutMs=2000, topK=5, minScore=0.15 } = {})
```
### 3.5 Var hookar den in i chat-flödet
**Alternativ A — Express Middleware (rekommenderat):**
```javascript
// I api/aamos/routes.mjs:
import { ragInjectionMiddleware } from '../aamos/rag-injection.mjs';
router.post('/chat', requireAuth, ragInjectionMiddleware, async (req, res) => {
// req.ragContextBlock sätts av middleware
const systemPrompt = req.ragContextBlock
? req.ragContextBlock + '\n\n---\n\n' + getEnrichedPrompt(baseContext) + aamosContext
: getEnrichedPrompt(baseContext) + aamosContext;
// ...resten oförändrat...
});
```
**Alternativ B — Inline i chat-handler:**
```javascript
// I api/aamos/routes.mjs, inuti POST /chat:
const { buildSystemPromptWithRAG } = await import('../aamos/rag-injection.mjs');
const { systemPrompt } = await buildSystemPromptWithRAG(
getEnrichedPrompt(baseContext) + aamosContext,
lastUserMsg
);
```
**Alternativ C — I buildSystemPrompt (direkt ersätt getEnrichedPrompt):**
```javascript
// system-prompt.mjs — modifiering av getEnrichedPrompt:
export async function getEnrichedPromptWithRAG(context = {}, query = '') {
const { buildContextForQuery } = await import('./rag-injection.mjs');
const { contextBlock } = await buildContextForQuery(query);
let out = contextBlock ? contextBlock + '\n\n---\n\n' : '';
out += BASE_SYSTEM_PROMPT + '\n\n';
out += getFactsContext({ verbose: false }) + '\n\n'; // behåll som fallback tills CANON är enda källa
// ...resten...
return out;
}
```
**Rekommendation:** Alternativ A (middleware) ger minst kod-ändring och isolerar logiken.
### 3.6 ESLM-specifik injektion
ESLM-endpointen (`172.31.36.61:8000`) har ingen injektion idag. För ESLM gäller:
```javascript
// Vid anrop till ESLM-endpointen (vLLM OpenAI API):
const { contextBlock } = await buildContextForQuery(userQuery);
const systemMessage = contextBlock
? contextBlock + '\n\nDu är en AI-assistent för Wavult/AAMOS. Svara precist.'
: 'Du är en AI-assistent. Svara precist.';
const response = await fetch('http://172.31.36.61:8000/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'aamos-eslm',
messages: [
{ role: 'system', content: systemMessage },
...userMessages
],
temperature: 0.1,
max_tokens: 2048,
}),
});
```
---
## 4. RISKER + FALLBACK
### 4.1 RAG är nere (OpenAI-api, disk, process-crash)
**Fallback-kedja (3 nivåer):**
1. **RAG OK** → semantisk retrieval, CANON tier-1 chunks → bäst
2. **RAG timeout/fel**`getStaticCanonFallback()` — läser CANON.md direkt och extraherar bolagsstruktur + produktsektioner + förbjudna fakta (~600 tokens)
3. **CANON.md oläsbar** → minimal hardkodad sträng i minnet (bolag/produkt/team/regler)
**Timeout:** 2000ms (konfigurerbart via `opts.timeoutMs`). RAG-fel är aldrig fatala — chatten fortsätter.
### 4.2 Fakta hamnar i fel kontext (säkerhet)
**Risk:** CANON-data läcker till ej autentiserade endpoints.
**Mitigation:**
- Injektionen sker alltid **server-side**, i system-rollen, aldrig i user/assistant-rollen
- Klassificeringen sker per fråga — generella frågor får ingen injektion
- CANON innehåller inga hemligheter (org-nr, namn, produktfakta är semi-publik affärssanning)
- `/api/chat` (generisk, publik) ska **INTE** injiceras — skapa separat endpoint för ESLM om nödvändigt
- Auth krävs redan på `/api/aamos/chat` och `/api/dynasty-counsel/chat`
**CANON innehåller ALDRIG:**
- API-nycklar, lösenord, tokens
- Personuppgifter (personnummer, bankuppgifter)
- Intern infrastruktur-IP/portar (WAVULT_TRUTH.md hanteras separat)
### 4.3 Injektion av felaktig data (hallucination via RAG)
**Risk:** RAG hämtar irrelevant chunk och "hittar på" koppling.
**Mitigation:**
- `minScore = 0.15` (semantisk) eller BM25 threshold — låga scores ignoreras
- CANON är tier-1 (E4 evidens) — data är verifierad av Erik
- `formatContext()` märker varje chunk med källa + radnummer → auditerbart
- CANON uppdateras via kontrollerad process (Erik-godkännande), inte automatiskt
### 4.4 Latency-påverkan
**Risk:** RAG-anrop (OpenAI embedding) tar 200-800ms och fördröjer chat-svar.
**Mitigation:**
- Timeout 2000ms — timeout → statisk fallback (0ms extra)
- Embeddings cachas i minnet (`_embeddingsCache`, 1h TTL)
- Index cachas i minnet (5 min TTL), disk-cache (1h TTL)
- Klassificering är synkron (0ms) — generella frågor skippar RAG helt
### 4.5 wavult-facts.mjs — dupliceringsrisk
**Risk:** wavult-facts.mjs och AAMOS_CANON.md divergerar (uppdatering i en men inte båda).
**Mitigation (kort sikt):** behåll båda under migrationsfasen — RAG-CANON ersätter gradvis.
**Mitigation (lång sikt):** generera wavult-facts.mjs automatiskt från CANON:
```bash
# CI-script: auto-generera wavult-facts.mjs från CANON
node scripts/canon-to-wavult-facts.mjs \
--input /opt/amos/data/AAMOS_CANON.md \
--output /opt/amos/api/dynasty-counsel/knowledge/wavult-facts.mjs
```
---
## 5. DEPLOY-PLAN (vilka filer ändras, i vilken ordning)
> **OBS:** Inget i denna plan muterar produktionskod idag. Alla steg kräver explicit godkännande.
### Steg 1 — Placera injektionsfilen (låg risk)
```
KÄLLA: /home/bernt/.openclaw/workspace/build/eslm-rag-injection.mjs
MÅL: /opt/amos/api/aamos/rag-injection.mjs
ÄNDRAR: Inget befintligt
RISK: Noll (ny fil)
```
### Steg 2 — Verifiera RAG-hälsa (ingen mutation)
```bash
node --input-type=module << 'EOF'
import rag from '/opt/amos/api/aamos/rag-injection.mjs';
console.log(await rag.ragHealthCheck());
// Förväntat: canonExists: true, ragModuleAccessible: true, indexLoaded: true
EOF
```
### Steg 3 — Integrera i /api/aamos/chat (middleware-approach)
**Fil att ändra:** `/opt/amos/api/aamos/routes.mjs`
```diff
import { getEnrichedPrompt } from '../dynasty-counsel/system-prompt.mjs';
+import { ragInjectionMiddleware } from './rag-injection.mjs';
-router.post('/chat', requireAuth, aamosRateLimit, express.json({ limit: '256kb' }), async (req, res) => {
+router.post('/chat', requireAuth, aamosRateLimit, express.json({ limit: '256kb' }), ragInjectionMiddleware, async (req, res) => {
// ... (existerande kod) ...
- const systemPrompt = getEnrichedPrompt(baseContext) + aamosContext;
+ const ragBlock = req.ragContextBlock || '';
+ const systemPrompt = ragBlock
+ ? ragBlock + '\n\n---\n\n' + getEnrichedPrompt(baseContext) + aamosContext
+ : getEnrichedPrompt(baseContext) + aamosContext;
```
### Steg 4 — Integrera i /api/dynasty-counsel/chat
**Fil att ändra:** `/opt/amos/api/dynasty-counsel/routes.mjs`
Samma pattern som steg 3 — lägg till `ragInjectionMiddleware` och modifiera `systemPrompt`-konstruktionen.
### Steg 5 — ESLM-endpoint (framtida)
**Fil att skapa/ändra:** `/opt/amos/api/aamos/eslm-proxy.mjs` (ny fil)
Wrappa ESLM-anropet med `buildContextForQuery()` innan forward till `172.31.36.61:8000`.
### Steg 6 — Rebuild RAG-index med CANON som primär
```bash
# Trigga rebuild av index (kör mot körande server)
curl -X POST http://localhost:3100/api/rag/rebuild 2>/dev/null || \
node -e "import('/opt/amos/api/rag.mjs').then(m => m.buildIndex(true))"
```
### Steg 7 — (Lång sikt) Deprecera wavult-facts.mjs
1. Skapa `scripts/canon-to-wavult-facts.mjs` som autogenererar filen från CANON
2. Kör scriptet i CI vid varje CANON-ändring
3. `wavult-facts.mjs` blir en genererad fil, ej manuellt underhållen
### Steg 8 — Monitoring
Logga per request:
```json
{
"ts": "2026-06-06T23:00:00Z",
"component": "rag-injection",
"query_classification": "company",
"rag_source": "rag", // "rag" | "static" | "none"
"rag_chunks": 4,
"rag_latency_ms": 312,
"rag_coverage": "E4"
}
```
---
## 6. SAMMANFATTNING
| Aspekt | Nuläge | Målarkitektur |
|--------|--------|---------------|
| Faktakälla | `wavult-facts.mjs` (hårdkodad JS-fil) | `AAMOS_CANON.md` via RAG |
| Injektionspunkt | Alltid, alla frågor | Intelligent klassificering (company vs generic) |
| RAG-status | Byggt + embeddings komplett — men frånkopplat | Kopplat till chat-flödet |
| ESLM-injektion | Ingen | Via `buildContextForQuery()` |
| Fallback | Ingen (kraschar tyst) | 3-nivå: RAG → CANON statisk → hardkodad |
| Duplikering | CANON + wavult-facts divergerar | CANON = enda källa, wavult-facts autogenereras |
**Kod-stub:** `/home/bernt/.openclaw/workspace/build/eslm-rag-injection.mjs`
**Exporterar:** `classifyQuery`, `buildContextForQuery`, `buildSystemPromptWithRAG`, `ragInjectionMiddleware`, `ragHealthCheck`
---
*Producerat av agent Bernt (ESLM-B subagent) 2026-06-06. Mutera ingenting i prod utan Erik-godkännande.*