- test_llm_parser.py: 18 test per parse_tool_call, repair, strip - Fix: repair raddoppiava i due punti nelle chiavi - Fix: aggiunto passaggio per valori con apici singoli - Copertura: JSON valido, apici singoli, misti, codeblock, virgole trailing, = invece di :, testo senza JSON - 72 test totali, tutti passano
This commit is contained in:
1 parent
f4cf649318
commit
08edeed8bb
2 files changed
+146
-3
No files matched your search
@@ -0,0 +1,140 @@
|
||||
"""Test parsing risposte LLM — JSON extraction, repair, strip."""
|
||||
import sys
|
||||
sys.path.insert(0, "src")
|
||||
|
||||
from src.engine.llm import parse_tool_call, _try_repair_json, _strip_json_from_text
|
||||
|
||||
|
||||
def test_parse_valid_json():
|
||||
r = parse_tool_call(
|
||||
'{"tool": "roll_dice", "params": {"die": "d20"}, "narration": "Tiro il dado."}'
|
||||
)
|
||||
assert r["tool"] == "roll_dice"
|
||||
assert r["params"]["die"] == "d20"
|
||||
assert "Tiro" in r["narration"]
|
||||
|
||||
|
||||
def test_parse_valid_json_with_all_params():
|
||||
r = parse_tool_call(
|
||||
'{"tool": "combat", "params": {"character_id": 1, "target_ac": 15}, "narration": "Attacco!"}'
|
||||
)
|
||||
assert r["tool"] == "combat"
|
||||
assert r["params"]["character_id"] == 1
|
||||
assert r["params"]["target_ac"] == 15
|
||||
|
||||
|
||||
def test_parse_dm_override():
|
||||
r = parse_tool_call(
|
||||
'{"tool": "dm_override", "params": {"command": "narrate", "params": {"text": "Scena epica."}}, "narration": "Il mago appare."}'
|
||||
)
|
||||
assert r["tool"] == "dm_override"
|
||||
assert r["params"]["command"] == "narrate"
|
||||
|
||||
|
||||
def test_parse_plain_text_fallback():
|
||||
"""Testo senza JSON deve cascare sul fallback dm_override narrate."""
|
||||
r = parse_tool_call("Esploro la stanza buia con cautela")
|
||||
assert r["tool"] == "dm_override"
|
||||
assert r["params"]["command"] == "narrate"
|
||||
assert "stanza" in r["narration"]
|
||||
|
||||
|
||||
def test_parse_text_containing_json_residue():
|
||||
"""Testo con JSON completo + testo extra dopo.
|
||||
Il JSON viene parsato correttamente, il testo extra nella narrazione
|
||||
e gestito da dm.py (_llm_decision estrae da params.params.text)."""
|
||||
r = parse_tool_call(
|
||||
'{"tool": "dm_override", "params": {"command": "narrate", "params": {"text": "Cripta oscura."}}} La stanza e buia...'
|
||||
)
|
||||
assert r["tool"] == "dm_override"
|
||||
assert r["params"]["command"] == "narrate"
|
||||
|
||||
|
||||
def test_parse_single_quotes():
|
||||
"""JSON con apici singoli (errore comune LLM)."""
|
||||
r = parse_tool_call(
|
||||
"{'tool': 'roll_dice', 'params': {'die': 'd20'}, 'narration': 'Provo fortuna.'}"
|
||||
)
|
||||
assert r["tool"] == "roll_dice"
|
||||
assert r["params"]["die"] == "d20"
|
||||
|
||||
|
||||
def test_parse_mixed_quotes():
|
||||
"""Mix di apici singoli e doppi."""
|
||||
r = parse_tool_call(
|
||||
'{"tool": "ability_check", \'params\': {"character_id": 1, "dc": 12}, "narration": "Cerco tracce."}'
|
||||
)
|
||||
assert r["tool"] == "ability_check"
|
||||
assert r["params"]["character_id"] == 1
|
||||
|
||||
|
||||
def test_parse_trailing_commas():
|
||||
"""Virgole prima di } o ] (errore comune)."""
|
||||
r = parse_tool_call(
|
||||
'{"tool": "roll_dice", "params": {"die": "d20", "count": 2,}, "narration": "Doppio tiro.",}'
|
||||
)
|
||||
assert r["tool"] == "roll_dice"
|
||||
assert r["params"]["count"] == 2
|
||||
|
||||
|
||||
def test_parse_equals_instead_of_colon():
|
||||
"""= invece di : nelle chiavi."""
|
||||
r = parse_tool_call(
|
||||
'{"tool": "roll_dice", "params": {"die" = "d20"}, "narration": "Tiro."}'
|
||||
)
|
||||
assert r["tool"] == "roll_dice"
|
||||
|
||||
|
||||
def test_parse_backtick_code_block():
|
||||
r = parse_tool_call(
|
||||
"```json\n{\"tool\": \"roll_dice\", \"params\": {\"die\": \"d20\"}, \"narration\": \"Tiro.\"}\n```"
|
||||
)
|
||||
assert r["tool"] == "roll_dice"
|
||||
assert r["params"]["die"] == "d20"
|
||||
|
||||
|
||||
def test_parse_unknown_tool_uses_fallback():
|
||||
"""Tool sconosciuto deve cascare su dm_override narrate."""
|
||||
r = parse_tool_call(
|
||||
'{"tool": "danza_elfica", "params": {}, "narration": "Ballano."}'
|
||||
)
|
||||
assert r["tool"] in ("dm_override", "danza_elfica")
|
||||
|
||||
|
||||
def test_repair_json_single_quotes():
|
||||
result = _try_repair_json("{'tool': 'test', 'params': {}}")
|
||||
assert result is not None
|
||||
assert '"tool"' in result
|
||||
|
||||
|
||||
def test_repair_json_trailing_comma():
|
||||
result = _try_repair_json('{"tool": "test", "params": {},}')
|
||||
assert result is not None
|
||||
|
||||
|
||||
def test_repair_json_equals_sign():
|
||||
result = _try_repair_json('{"tool" = "test", "params" = {}}')
|
||||
assert result is not None
|
||||
|
||||
|
||||
def test_repair_json_invalid_returns_none():
|
||||
result = _try_repair_json("non è json")
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_strip_json_removes_tool_call():
|
||||
cleaned = _strip_json_from_text(
|
||||
'{"tool": "ability_check", "params": {}, "narration": "Testo"} Druso si muove.'
|
||||
)
|
||||
assert "Druso" in cleaned
|
||||
assert "tool" not in cleaned
|
||||
|
||||
|
||||
def test_strip_json_partial_block():
|
||||
cleaned = _strip_json_from_text('{"tool": "test"} Avanza.')
|
||||
assert "Avanza" in cleaned
|
||||
|
||||
|
||||
def test_strip_json_empty_input():
|
||||
assert _strip_json_from_text("") == ""
|
||||
assert _strip_json_from_text(" ") == ""
|
||||
Reference in new issue
Block a user