refactor: remediation plan Fase 0-4 completata

- Fase 0: env var allineate, SYSTEM_PROMPT deduplicato, .gitignore pulito
- Fase 1: GameContext isolato, ciclo DM unificato, provider thread-safe (contextvars)
- Fase 2: DB schema esteso (current_area_id, combat_state, character_items)
- Fase 3: thread-local connection pool + WAL + busy_timeout
- Fase 4: test suite (32 test, 100% pass)
- heuristic_fallback come parser di riserva (non secondo pilota)
This commit is contained in:
truetype committed 2026-06-27 18:00:03 +02:00
1 parent 90f823fdb9
commit cb45172717
16 files changed
+906 -221

No files matched your search

+12 -44
View File
@@ -1,62 +1,29 @@
from __future__ import annotations
import contextvars
import json
import re
from src.engine.config import load_from_env
from src.engine.providers import LLMProvider, create_provider
DEFAULT_MODEL = "gemma2:9b"
_KNOWN_TOOLS = {
"roll_dice": {"die": "str", "count": "int (default 1)", "modifier": "int (default 0)"},
"dice_chain": {"die": "str", "steps": "int"},
"combat": {"character_id": "int", "target_ac": "int", "attack_bonus": "int (default 0)",
"damage_die": "str (default d8)", "damage_bonus": "int (default 0)",
"mighty_deed_die": "str (default '')", "luck_mod": "int (default 0)",
"armor_type": "str (none/light/medium/heavy)"},
"ability_check": {"character_id": "int", "dc": "int", "modifier": "int (default 0)"},
"saving_throw": {"character_id": "int", "save_type": "str (Tempra/Riflessi/Volontà)", "dc": "int", "modifier": "int (default 0)"},
"movement": {"distance_m": "int", "terrain": "str (normale/difficile/molto difficile)", "stealth": "bool (default false)"},
"spellcasting": {"character_id": "int", "spell_name": "str", "spell_check": "int", "dc": "int (default 12)"},
"update_inventory": {"character_id": "int", "item": "str", "action": "str (add/remove)"},
"award_xp": {"character_id": "int", "xp": "int"},
"dm_override": {"command": "str", "params": "dict (opzionale)"},
}
SYSTEM_PROMPT = f"""Sei il Dungeon Master di una partita di DCC RPG (Dungeon Crawl Classics).
Il giocatore scrive un'azione e TU decidi quale strumento chiamare.
Strumenti disponibili:
{json.dumps(_KNOWN_TOOLS, indent=2)}
Regole:
1. Scegli SEMPRE UNO strumento per rispondere all'azione del giocatore.
2. I parametri DEVONO essere realistici per il contesto.
3. Se il giocatore fa qualcosa di narrativo, usa dm_override con command="narrate".
4. Tira sempre i dadi quando il giocatore tenta un'azione rischiosa.
5. Rispondi SOLO con JSON in questo formato:
{{"tool": "nome_tool", "params": {{...}}, "narration": "breve descrizione di cosa succede"}}
"""
_provider: LLMProvider | None = None
_provider_var: contextvars.ContextVar[LLMProvider | None] = contextvars.ContextVar("provider", default=None)
def get_provider() -> LLMProvider:
global _provider
if _provider is None:
_provider = create_provider(load_from_env())
return _provider
provider = _provider_var.get()
if provider is None:
from src.engine.config import load_from_env
provider = create_provider(load_from_env())
_provider_var.set(provider)
return provider
def set_provider(provider: LLMProvider) -> None:
global _provider
_provider = provider
_provider_var.set(provider)
def ask(prompt: str, system: str = SYSTEM_PROMPT) -> str:
def ask(prompt: str, system: str = "") -> str:
return get_provider().ask(prompt, system=system)
@@ -100,10 +67,11 @@ if __name__ == "__main__":
"Attacco l'orco con la mia ascia!",
"Esploro il corridoio buio",
]
from src.engine.prompts import SYSTEM_PROMPT as _DM_PROMPT
for a in test_actions:
print(f"\n>>> Giocatore: {a}")
try:
resp = ask(a)
resp = ask(a, system=_DM_PROMPT)
print(f"LLM: {resp[:300]}")
parsed = parse_tool_call(resp)
print(f"Parser: {json.dumps(parsed, indent=2)}")