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>
15 KiB
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:
- LLM's baked-in Skyrim knowledge (incomplete, potentially wrong)
- 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
- Zero SkyrimNet Changes — Only change the endpoint URL in config
- Transparent Passthrough — Unknown requests forward unchanged
- NPC-Aware Filtering — Lore filtered by extracted NPC profile
- Two-Tier Content — Scholars get deep lore, commoners get basics
- 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:
{
"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:
# 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
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
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
# 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
# /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:
# 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
# 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
- Player profile tracking — Remember what player has learned, NPCs can reference shared knowledge
- Gossip propagation — Lore spreads through NPC network with degradation
- Dynamic lore updates — Events in-game add to lore corpus
- Multi-source RAG — Combine Oghma + UESP + custom worldbuilding
- Upstream contribution — Propose RAG API to SkyrimNet author
Version: 1.0 | Created: 2026-03-30 | Updated: 2026-03-30