feat: Add Oghma RAG Proxy for SkyrimNet lore injection

RAG proxy that intercepts SkyrimNet LLM requests and enriches them
with relevant Tamrielic lore from CHIM's Oghma Infinium database.

Features:
- FastAPI proxy compatible with OpenAI API
- ChromaDB semantic search for lore retrieval
- NPC profile extraction from SkyrimNet prompts
- Google Sheets ingestion for CHIM's Oghma data
- Kubernetes deployment manifests
- Debug endpoint for RAG operation monitoring

Collections ingested to iris-dev ChromaDB:
- oghma_lore: 1951 entries (scholar knowledge)
- oghma_basic: 1949 entries (commoner knowledge)
- oghma_visual: 1151 entries (Omnisight perception)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
dafit
2026-03-30 23:22:46 +02:00
parent 62dcee5fbf
commit 3926ab676f
20 changed files with 2367 additions and 0 deletions

View File

@@ -0,0 +1,497 @@
# Oghma RAG Proxy — Technical Specification
**Project:** SkyrimNet Lore Enhancement via RAG Proxy
**Status:** Design Phase
**Author:** Chrysalis + dafit
**Created:** 2026-03-30
---
## 1. Problem Statement
SkyrimNet currently relies on:
1. LLM's baked-in Skyrim knowledge (incomplete, potentially wrong)
2. Dynamic memories (what the NPC witnessed)
**Missing:** Authoritative lore retrieval filtered by what each NPC *should* know.
**Result:**
- Knowledge bleedover (guard knows Telvanni secrets)
- Lore inaccuracies (mixing up timelines, factions)
- No grounding in canon
---
## 2. Solution: Oghma RAG Proxy
A transparent proxy that sits between SkyrimNet and the LLM inference endpoint.
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ OGHMA RAG PROXY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ SkyrimNet │ │ Oghma Proxy │ │ OpenRouter │ │
│ │ SKSE Plugin │────────▶│ (FastAPI) │────────▶│ / vLLM │ │
│ │ │ │ │ │ │ │
│ │ Port: N/A │ │ Port: 8100 │ │ Upstream │ │
│ └──────────────┘ └────────┬─────────┘ └──────────────┘ │
│ │ │
│ │ Query │
│ ▼ │
│ ┌──────────────────┐ │
│ │ iris-dev │ │
│ │ ChromaDB │ │
│ │ Port: 35000 │ │
│ │ │ │
│ │ Collections: │ │
│ │ - oghma_lore │ │
│ │ - oghma_basic │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 3. Core Principles
1. **Zero SkyrimNet Changes** — Only change the endpoint URL in config
2. **Transparent Passthrough** — Unknown requests forward unchanged
3. **NPC-Aware Filtering** — Lore filtered by extracted NPC profile
4. **Two-Tier Content** — Scholars get deep lore, commoners get basics
5. **Nimmerverse Native** — Runs on existing infrastructure (iris-dev)
---
## 4. Architecture Components
### 4.1 Oghma Proxy Service
**Location:** VM on nimmerverse (could run on phoebe-dev or dedicated)
**Tech Stack:** Python 3.11+, FastAPI, httpx, chromadb-client
**Port:** 8100 (configurable)
**Responsibilities:**
- Intercept OpenRouter-compatible API requests
- Parse prompts to extract NPC context
- Query ChromaDB for relevant lore
- Inject lore into system prompt
- Forward to upstream LLM
- Stream response back to SkyrimNet
### 4.2 Oghma ChromaDB Collection
**Location:** iris-dev.eachpath.local:35000
**Collections:**
| Collection | Content | Use Case |
|------------|---------|----------|
| `oghma_lore` | Full `topic_desc` entries | Scholars, mages, priests |
| `oghma_basic` | Simplified `topic_desc_basic` | Commoners, guards, peasants |
**Metadata Schema:**
```json
{
"topic": "Akatosh",
"category": "Figures/Gods",
"knowledge_classes": ["priest", "scholar", "dragon", "snowelf"],
"knowledge_classes_basic": ["nord", "imperial", "breton"],
"tags": ["divine", "time", "dragon-god"],
"source_sheet": "Figures/Gods"
}
```
### 4.3 NPC Profile Extractor
Parses SkyrimNet prompts to extract:
- NPC name
- Race
- Profession/class (from bio or context)
- Factions
- Location (hold)
- Special traits
**Extraction Patterns:**
```python
# From character bio section
r"## (?P<name>\w+) Bio\n- Gender: (?P<gender>\w+)\n- Race: (?P<race>\w+)"
# From system context
r"You are (?P<name>[^,]+), a (?P<race>\w+) (?P<profession>\w+)"
# From faction mentions
r"member of (?:the )?(?P<faction>[\w\s]+)"
```
---
## 5. Data Flow
### 5.1 Request Interception
```
1. SkyrimNet sends POST /chat/completions to proxy
2. Proxy extracts NPC profile from messages
3. Proxy extracts conversation context/topic
4. Proxy queries ChromaDB:
- Collection: oghma_lore or oghma_basic (based on NPC education)
- Filter: knowledge_classes intersects NPC's classes
- Query: conversation context (semantic search)
- Limit: 3-5 most relevant entries
5. Proxy injects lore block into system message
6. Proxy forwards enriched request to upstream
7. Proxy streams response back to SkyrimNet
```
### 5.2 Lore Injection Format
Injected after character bio, before conversation:
```
## Relevant Lore Knowledge
Based on your background as a Nord priest in Whiterun, you would know:
- **Talos**: [Condensed lore about Talos worship, appropriate to character]
- **Whiterun**: [Local knowledge about the hold]
- **Companions**: [If character has connection]
Remember: This is knowledge your character possesses. Reference it naturally in conversation, don't recite it.
```
---
## 6. NPC Knowledge Classification
### 6.1 Profile → Knowledge Classes Mapping
```python
RACE_CLASSES = {
"Nord": ["nord"],
"Dunmer": ["darkelf", "dunmer"],
"Altmer": ["highelf", "altmer"],
"Bosmer": ["woodelf", "bosmer"],
"Argonian": ["argonian"],
"Khajiit": ["khajiit"],
"Breton": ["breton"],
"Redguard": ["redguard"],
"Orsimer": ["orc", "orsimer"],
"Imperial": ["imperial"],
}
PROFESSION_CLASSES = {
"priest": ["priest"],
"mage": ["mage", "scholar"],
"scholar": ["scholar"],
"blacksmith": ["blacksmith"],
"guard": ["guard", "warrior"],
"thief": ["thief"],
"merchant": ["merchant"],
"innkeeper": ["innkeeper"],
"hunter": ["hunter"],
"farmer": ["peasant"],
"noble": ["noble"],
"bard": ["bard"],
}
LOCATION_CLASSES = {
"Whiterun": ["whiterun"],
"Windhelm": ["eastmarch"],
"Solitude": ["haafingar"],
"Riften": ["rift"],
"Markarth": ["reach"],
"Morthal": ["hjaalmarch"],
"Dawnstar": ["pale"],
"Winterhold": ["winterhold"],
"Falkreath": ["falkreath"],
"Solstheim": ["solstheim"],
}
FACTION_CLASSES = {
"Companions": ["companions"],
"College of Winterhold": ["college", "mage"],
"Thieves Guild": ["thieves"],
"Dark Brotherhood": ["darkbrotherhood"],
"Stormcloaks": ["stormcloak"],
"Imperial Legion": ["imperial"],
"Thalmor": ["thalmor"],
"Dawnguard": ["dawnguard"],
"Volkihar": ["vampire", "volkihar"],
}
```
### 6.2 Education Level Detection
```python
def get_education_level(npc_profile: NPCProfile) -> str:
"""Determine if NPC gets full lore or basic summaries."""
educated_professions = {"mage", "scholar", "priest", "noble", "bard"}
educated_factions = {"College of Winterhold", "Thalmor"}
if npc_profile.profession in educated_professions:
return "scholar"
if any(f in educated_factions for f in npc_profile.factions):
return "scholar"
return "commoner"
```
---
## 7. API Specification
### 7.1 Proxy Endpoints
**POST /v1/chat/completions** (OpenRouter compatible)
- Intercepts, enriches, forwards
- Supports streaming
**POST /v1/completions** (Legacy)
- Same enrichment logic
**GET /health**
- Returns proxy + ChromaDB status
**GET /stats**
- Lore injection statistics
- Cache hit rates
- Average latency added
### 7.2 Configuration
```yaml
# oghma-proxy.yaml
proxy:
host: 0.0.0.0
port: 8100
upstream:
# OpenRouter
url: https://openrouter.ai/api/v1
api_key: ${OPENROUTER_API_KEY}
# Or local vLLM
# url: http://localhost:8000/v1
chromadb:
host: iris-dev.eachpath.local
port: 35000
collection_lore: oghma_lore
collection_basic: oghma_basic
retrieval:
max_results: 5
min_score: 0.6
injection:
enabled: true
position: after_bio # after_bio | before_conversation | system_suffix
logging:
level: INFO
log_injections: true
log_to_phoebe: true # Log to nimmerverse decision table
```
---
## 8. Deployment Architecture
### 8.1 Host Options
**Option A: Dedicated VM (Recommended)**
```
VM ID: 109
Hostname: oghma.eachpath.local
IP: 10.0.40.109
Resources: 2 vCPU, 4GB RAM
OS: Rocky Linux 10
```
**Option B: Co-locate on phoebe-dev**
```
Hostname: phoebe-dev.eachpath.local
Port: 8100 (alongside PostgreSQL 35432)
Pros: No new VM, shared resources
Cons: Resource contention
```
### 8.2 Service Configuration
```ini
# /etc/systemd/system/oghma-proxy.service
[Unit]
Description=Oghma RAG Proxy for SkyrimNet
After=network.target
[Service]
Type=simple
User=chrysalis
WorkingDirectory=/opt/oghma-proxy
ExecStart=/opt/oghma-proxy/venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8100
Restart=always
RestartSec=5
Environment=OPENROUTER_API_KEY=your-key-here
[Install]
WantedBy=multi-user.target
```
### 8.3 SkyrimNet Configuration Change
In `config/OpenRouter.yaml`:
```yaml
# Before
openrouter:
base_url: https://openrouter.ai/api/v1
# After
openrouter:
base_url: http://oghma.eachpath.local:8100/v1
# Or if running locally:
# base_url: http://localhost:8100/v1
```
---
## 9. Data Pipeline: Oghma Ingestion
### 9.1 Google Sheets → ChromaDB Pipeline
```python
# ingest_oghma.py
import pandas as pd
import chromadb
from chromadb.config import Settings
SHEET_ID = "1dcfctU-iOqprwy2BOc7___4Awteczgdlv8886KalPsQ"
SHEETS = [
("Figures/Gods", 0),
("Artifacts", 1),
("Locations - Whiterun", 2),
# ... etc
]
def ingest_sheet(sheet_name: str, gid: int, chroma_client):
url = f"https://docs.google.com/spreadsheets/d/{SHEET_ID}/export?format=csv&gid={gid}"
df = pd.read_csv(url)
collection_lore = chroma_client.get_or_create_collection("oghma_lore")
collection_basic = chroma_client.get_or_create_collection("oghma_basic")
for _, row in df.iterrows():
topic = row['topic']
# Full lore for educated NPCs
if pd.notna(row.get('topic_desc')):
collection_lore.add(
documents=[row['topic_desc']],
ids=[f"{sheet_name}:{topic}"],
metadatas=[{
"topic": topic,
"category": sheet_name,
"knowledge_classes": row.get('knowledge_class', ''),
"tags": row.get('tags', ''),
}]
)
# Basic lore for commoners
if pd.notna(row.get('topic_desc_basic')):
collection_basic.add(
documents=[row['topic_desc_basic']],
ids=[f"{sheet_name}:{topic}:basic"],
metadatas=[{
"topic": topic,
"category": sheet_name,
"knowledge_classes": row.get('knowledge_class_basic', ''),
"tags": row.get('tags', ''),
}]
)
```
### 9.2 Embedding Model
Use same embedding model as SkyrimNet memories for consistency:
- **Model:** `all-MiniLM-L6-v2` (384 dimensions)
- **Or:** Match whatever SkyrimNet uses in Memory.yaml
---
## 10. Implementation Phases
### Phase 1: Foundation (Week 1)
- [ ] Set up oghma-proxy repository
- [ ] Implement basic FastAPI proxy (passthrough mode)
- [ ] Test with SkyrimNet (verify transparent forwarding)
- [ ] Deploy on phoebe-dev for initial testing
### Phase 2: Oghma Ingestion (Week 1-2)
- [ ] Write Google Sheets ingestion script
- [ ] Ingest all Oghma sheets into iris-dev ChromaDB
- [ ] Verify embeddings and metadata
- [ ] Test semantic queries manually
### Phase 3: NPC Profile Extraction (Week 2)
- [ ] Implement prompt parser for NPC context
- [ ] Build knowledge class mapper
- [ ] Test with various NPC types
- [ ] Handle edge cases (unnamed NPCs, generic guards)
### Phase 4: RAG Integration (Week 2-3)
- [ ] Implement ChromaDB query logic
- [ ] Build lore injection formatter
- [ ] Add education-level routing (scholar vs commoner)
- [ ] Test end-to-end with SkyrimNet
### Phase 5: Polish & Deploy (Week 3)
- [ ] Add streaming support
- [ ] Implement caching (avoid re-querying same context)
- [ ] Add metrics/logging to phoebe
- [ ] Create dedicated VM or finalize deployment
- [ ] Write operational runbook
### Phase 6: Iteration (Ongoing)
- [ ] Tune retrieval parameters based on gameplay
- [ ] Add more knowledge sources beyond Oghma
- [ ] Consider contributing upstream to SkyrimNet
---
## 11. Success Metrics
| Metric | Target | How to Measure |
|--------|--------|----------------|
| Lore accuracy | NPCs reference correct lore | Manual spot-checks |
| Knowledge scoping | Guards don't know mage secrets | Test with various NPC types |
| Latency overhead | < 200ms added | Proxy metrics |
| Stability | 99.9% uptime | Service monitoring |
| User experience | Immersion improved | Subjective gameplay testing |
---
## 12. Risks & Mitigations
| Risk | Impact | Mitigation |
|------|--------|------------|
| Latency too high | Breaks dialogue flow | Cache aggressively, async prefetch |
| Wrong lore injected | Immersion broken | Strict knowledge filtering, fallback to no injection |
| ChromaDB down | No lore enrichment | Graceful degradation (passthrough mode) |
| Prompt parsing fails | NPC profile unknown | Default to basic/generic lore |
| Oghma data incomplete | Missing topics | Supplement with UESP scraping |
---
## 13. Future Enhancements
1. **Player profile tracking** — Remember what player has learned, NPCs can reference shared knowledge
2. **Gossip propagation** — Lore spreads through NPC network with degradation
3. **Dynamic lore updates** — Events in-game add to lore corpus
4. **Multi-source RAG** — Combine Oghma + UESP + custom worldbuilding
5. **Upstream contribution** — Propose RAG API to SkyrimNet author
---
**Version:** 1.0 | **Created:** 2026-03-30 | **Updated:** 2026-03-30