| """Tests for the per-engine output normalizers. No GPU, no network. |
| |
| Run: python -m pytest tests/ -q |
| """ |
|
|
| import sys |
| from pathlib import Path |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
|
|
| from ttw.actions import _extract_json |
| from ttw.normalize import NORMALIZERS, harmony_final, identity |
|
|
|
|
| def test_harmony_extracts_final_channel_and_json_parses(): |
| """A harmony wrapper is stripped to the final channel, and the bare result |
| feeds _extract_json to the intended action dict.""" |
| text = ( |
| "analysis The owl is cautious, sell honey while it is high." |
| 'assistantfinal{"thought":"sell honey","offers":' |
| '[{"side":"sell","good":"honey","price":9,"qty":3}],"gossip":""}' |
| ) |
| final = harmony_final(text) |
| assert "analysis" not in final |
| parsed = _extract_json(final) |
| assert parsed is not None |
| assert parsed["thought"] == "sell honey" |
| assert parsed["offers"][0]["good"] == "honey" |
|
|
|
|
| def test_harmony_without_marker_is_unchanged(): |
| text = '{"thought":"hold","offers":[],"gossip":""}' |
| assert harmony_final(text) == text |
|
|
|
|
| def test_harmony_truncated_analysis_is_dropped(): |
| """When the budget runs out before `assistantfinal`, the model is still in |
| its analysis channel; raw chain-of-thought must never leak downstream.""" |
| text = "analysisWe need to produce a punchy headline, optionally a short qu" |
| assert harmony_final(text) == "" |
|
|
|
|
| def test_harmony_empty_string_is_empty(): |
| assert harmony_final("") == "" |
|
|
|
|
| def test_harmony_final_channel_is_stripped(): |
| """Leading/trailing whitespace around the final-channel answer is removed.""" |
| assert harmony_final('analysis ...assistantfinal {"x":1} ') == '{"x":1}' |
|
|
|
|
| def test_identity_returns_text_unchanged(): |
| text = "anything at all, even {broken json" |
| assert identity(text) == text |
|
|
|
|
| def test_normalizers_registry_maps_keys(): |
| assert NORMALIZERS["harmony"] is harmony_final |
| assert NORMALIZERS["identity"] is identity |
|
|