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

17 KiB
Raw Blame History

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.json94/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:

// /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:

/**
 * @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):

// 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:

// 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):

// 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:

// 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/felgetStaticCanonFallback() — 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:

# 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)

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

 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

# 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:

{
  "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.