- 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
17 KiB
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:
// /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)
- En källa (CANON): AAMOS_CANON.md är enda auktoritativa källan. wavult-facts.mjs deprecas eller autogenereras från CANON.
- Intelligent routing: Klassificera frågan → injicera bara vid behov → generella frågor = ren generell modell.
- Ingen single point of failure: RAG → statisk CANON-fallback → minimal hardkodad fallback.
- Latency-gräns: RAG-anrop max 2s (timeout) → chat-svar ska ej fördröjas märkbart.
- 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):
- RAG OK → semantisk retrieval, CANON tier-1 chunks → bäst
- RAG timeout/fel →
getStaticCanonFallback()— läser CANON.md direkt och extraherar bolagsstruktur + produktsektioner + förbjudna fakta (~600 tokens) - 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/chatoch/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
- Skapa
scripts/canon-to-wavult-facts.mjssom autogenererar filen från CANON - Kör scriptet i CI vid varje CANON-ändring
wavult-facts.mjsblir 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.