462 lines
17 KiB
Markdown
462 lines
17 KiB
Markdown
|
|
# 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.*
|