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:
169
oghma-proxy/src/oghma_proxy/models.py
Normal file
169
oghma-proxy/src/oghma_proxy/models.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""Data models for Oghma RAG Proxy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class EducationLevel(str, Enum):
|
||||
"""NPC education level determines lore depth."""
|
||||
|
||||
SCHOLAR = "scholar" # Full lore access
|
||||
COMMONER = "commoner" # Basic summaries only
|
||||
|
||||
|
||||
class NPCProfile(BaseModel):
|
||||
"""Extracted NPC profile from SkyrimNet prompts."""
|
||||
|
||||
name: str = "Unknown"
|
||||
race: str = "Unknown"
|
||||
gender: str = "Unknown"
|
||||
profession: str | None = None
|
||||
factions: list[str] = Field(default_factory=list)
|
||||
location: str | None = None
|
||||
traits: list[str] = Field(default_factory=list)
|
||||
|
||||
# Computed
|
||||
knowledge_classes: list[str] = Field(default_factory=list)
|
||||
education_level: EducationLevel = EducationLevel.COMMONER
|
||||
|
||||
def compute_knowledge_classes(self) -> None:
|
||||
"""Compute knowledge classes from profile attributes."""
|
||||
classes = set()
|
||||
|
||||
# Race-based knowledge
|
||||
race_map = {
|
||||
"nord": ["nord"],
|
||||
"dunmer": ["darkelf", "dunmer"],
|
||||
"altmer": ["highelf", "altmer"],
|
||||
"bosmer": ["woodelf", "bosmer"],
|
||||
"argonian": ["argonian"],
|
||||
"khajiit": ["khajiit"],
|
||||
"breton": ["breton"],
|
||||
"redguard": ["redguard"],
|
||||
"orsimer": ["orc", "orsimer"],
|
||||
"orc": ["orc", "orsimer"],
|
||||
"imperial": ["imperial"],
|
||||
}
|
||||
race_lower = self.race.lower()
|
||||
if race_lower in race_map:
|
||||
classes.update(race_map[race_lower])
|
||||
|
||||
# Profession-based knowledge
|
||||
profession_map = {
|
||||
"priest": ["priest"],
|
||||
"mage": ["mage", "scholar"],
|
||||
"wizard": ["mage", "scholar"],
|
||||
"scholar": ["scholar"],
|
||||
"blacksmith": ["blacksmith"],
|
||||
"guard": ["guard", "warrior"],
|
||||
"soldier": ["warrior", "guard"],
|
||||
"warrior": ["warrior"],
|
||||
"thief": ["thief"],
|
||||
"merchant": ["merchant"],
|
||||
"innkeeper": ["innkeeper"],
|
||||
"hunter": ["hunter"],
|
||||
"farmer": ["peasant"],
|
||||
"peasant": ["peasant"],
|
||||
"noble": ["noble"],
|
||||
"jarl": ["noble"],
|
||||
"bard": ["bard"],
|
||||
"alchemist": ["alchemist"],
|
||||
}
|
||||
if self.profession:
|
||||
prof_lower = self.profession.lower()
|
||||
if prof_lower in profession_map:
|
||||
classes.update(profession_map[prof_lower])
|
||||
|
||||
# Location-based knowledge
|
||||
location_map = {
|
||||
"whiterun": ["whiterun"],
|
||||
"windhelm": ["eastmarch"],
|
||||
"solitude": ["haafingar"],
|
||||
"riften": ["rift"],
|
||||
"markarth": ["reach"],
|
||||
"morthal": ["hjaalmarch"],
|
||||
"dawnstar": ["pale"],
|
||||
"winterhold": ["winterhold"],
|
||||
"falkreath": ["falkreath"],
|
||||
"solstheim": ["solstheim"],
|
||||
}
|
||||
if self.location:
|
||||
loc_lower = self.location.lower()
|
||||
if loc_lower in location_map:
|
||||
classes.update(location_map[loc_lower])
|
||||
|
||||
# Faction-based knowledge
|
||||
faction_map = {
|
||||
"companions": ["companions"],
|
||||
"college of winterhold": ["college", "mage"],
|
||||
"college": ["college", "mage"],
|
||||
"thieves guild": ["thieves"],
|
||||
"dark brotherhood": ["darkbrotherhood"],
|
||||
"stormcloaks": ["stormcloak"],
|
||||
"stormcloak": ["stormcloak"],
|
||||
"imperial legion": ["imperial"],
|
||||
"legion": ["imperial"],
|
||||
"thalmor": ["thalmor"],
|
||||
"dawnguard": ["dawnguard"],
|
||||
"volkihar": ["vampire", "volkihar"],
|
||||
}
|
||||
for faction in self.factions:
|
||||
faction_lower = faction.lower()
|
||||
if faction_lower in faction_map:
|
||||
classes.update(faction_map[faction_lower])
|
||||
|
||||
self.knowledge_classes = list(classes)
|
||||
|
||||
# Determine education level
|
||||
educated_professions = {"mage", "wizard", "scholar", "priest", "noble", "bard"}
|
||||
educated_factions = {"college of winterhold", "thalmor", "college"}
|
||||
|
||||
if self.profession and self.profession.lower() in educated_professions:
|
||||
self.education_level = EducationLevel.SCHOLAR
|
||||
elif any(f.lower() in educated_factions for f in self.factions):
|
||||
self.education_level = EducationLevel.SCHOLAR
|
||||
else:
|
||||
self.education_level = EducationLevel.COMMONER
|
||||
|
||||
|
||||
class LoreEntry(BaseModel):
|
||||
"""A retrieved lore entry from Oghma."""
|
||||
|
||||
topic: str
|
||||
content: str
|
||||
category: str
|
||||
score: float
|
||||
knowledge_classes: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
"""OpenRouter-compatible chat message."""
|
||||
|
||||
role: str
|
||||
content: str
|
||||
name: str | None = None
|
||||
|
||||
|
||||
class ChatCompletionRequest(BaseModel):
|
||||
"""OpenRouter-compatible chat completion request."""
|
||||
|
||||
model: str
|
||||
messages: list[ChatMessage]
|
||||
temperature: float | None = None
|
||||
max_tokens: int | None = None
|
||||
stream: bool = False
|
||||
# Allow additional fields to pass through
|
||||
model_config = {"extra": "allow"}
|
||||
|
||||
|
||||
class InjectionResult(BaseModel):
|
||||
"""Result of lore injection."""
|
||||
|
||||
npc_profile: NPCProfile
|
||||
lore_entries: list[LoreEntry]
|
||||
injection_text: str
|
||||
query_time_ms: float
|
||||
Reference in New Issue
Block a user