feat(ai): resolve Steam app IDs from the library, don't make the model guess — 0.29.0

The model guessed "Rainbow Six Siege" for appID 2694490 (Path of Exile 2). We
already know the names locally, so ground it: steam.appid_names() maps appid→name
from the scanned library, and ai.build_prompt scans the text for app IDs and
injects a resolved glossary. Only locally-known IDs are listed; no network, no
fine-tuning. Tests + verified live (2694490 = Path of Exile 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 13:40:34 +02:00
parent c7e50ba4cb
commit 12339c3282
6 changed files with 62 additions and 4 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
"""RigDoctor — modular hardware monitoring & crash diagnostics for Linux gamers."""
__version__ = "0.28.1"
__version__ = "0.29.0"
+30 -2
View File
@@ -16,12 +16,15 @@ Answers are *grounded*: we pass the actual findings plus matched reference facts
from __future__ import annotations
import json
import re
import urllib.error
import urllib.request
from .. import config
from . import ai_knowledge
_APPID_RE = re.compile(r"\b\d{5,7}\b") # Steam app IDs are 57 digits
PROVIDERS = ("ollama", "claude")
OLLAMA_DEFAULT_ENDPOINT = "http://localhost:11434"
# Suggested Ollama model — strong instruction-following that fits an 8 GB GPU at Q4. Because we
@@ -89,10 +92,35 @@ def provider_label() -> str:
return "not configured"
def appid_glossary(text: str) -> str:
"""Resolve Steam app IDs that appear in `text` against the user's scanned library.
We don't teach the model app IDs — we look them up locally and hand it the mapping, so it
names games correctly instead of guessing. Only IDs we can resolve are listed.
"""
candidates = set(_APPID_RE.findall(text))
if not candidates:
return ""
try:
from . import steam
names = steam.appid_names()
except Exception: # never let a glossary lookup break an explanation
return ""
known = sorted((i, names[i]) for i in candidates if i in names)
if not known:
return ""
return "App IDs (resolved from your installed games):\n" + "\n".join(
f"- {appid} = {name}" for appid, name in known)
def build_prompt(findings_text: str) -> str:
"""The user-message content: matched reference facts + the collected findings."""
facts = ai_knowledge.relevant(findings_text)
"""The user-message content: app-ID glossary + matched reference facts + the findings."""
parts = []
glossary = appid_glossary(findings_text)
if glossary:
parts.append(glossary)
parts.append("")
facts = ai_knowledge.relevant(findings_text)
if facts:
parts.append("Reference facts (use these to interpret the findings):")
parts += [f"- {f}" for f in facts]
+5
View File
@@ -318,6 +318,11 @@ def cached_games() -> list[Game]:
return [Game(**{k: g[k] for k in Game.__dataclass_fields__ if k in g}) for g in cache.get("games", [])]
def appid_names() -> dict[str, str]:
"""{appid: name} for the user's scanned games — lets us resolve IDs seen in logs (M14)."""
return {g.appid: g.name for g in cached_games() if g.appid and g.name}
def rescan(cfg: dict | None = None) -> ScanResult:
"""Scan the selected libraries, diff against the cache, and persist the result.