Spaces:
Running on Zero
Running on Zero
| """Brick 6 tests: grounded narration + the hard 0% hallucination gate (P0-6).""" | |
| from __future__ import annotations | |
| import pytest | |
| from discoverroute import config | |
| from discoverroute.narrate import grounding | |
| from discoverroute.narrate.narrate import template_narration | |
| class FakePOI: | |
| def __init__(self, name, category): | |
| self.name, self.category = name, category | |
| POIS = [ | |
| FakePOI("Jardin des Plantes", "park_garden"), | |
| FakePOI("Fontaine Médicis", "water_feature"), | |
| FakePOI(None, "cafe"), # unnamed -> referred to by type, not a place name | |
| ] | |
| def test_extract_multiword_place_names(): | |
| mentions = grounding.extract_mentions( | |
| "Start at Place de la République, then visit Jardin des Plantes." | |
| ) | |
| joined = " | ".join(mentions) | |
| assert "Place de la République" in joined | |
| assert "Jardin des Plantes" in joined | |
| def test_gate_passes_grounded_text(): | |
| text = ("From Bastille, pass by Jardin des Plantes for some green, then " | |
| "Fontaine Médicis. Finally on to the Panthéon area.") | |
| ok, offenders = grounding.verify_grounded( | |
| text, POIS, start_label="Bastille", end_label="Panthéon area" | |
| ) | |
| assert ok, offenders | |
| def test_gate_catches_hallucination(): | |
| text = "Pass by Jardin des Plantes, then the Eiffel Tower, a lovely detour." | |
| ok, offenders = grounding.verify_grounded(text, POIS, start_label="Bastille") | |
| assert not ok | |
| assert any("Eiffel" in o for o in offenders) | |
| def test_gate_catches_appended_qualifier(): | |
| """A real name extended with an invented qualifier must NOT pass (the | |
| 'Café de la Paix' -> 'Café de la Paix sur Seine' hallucination vector).""" | |
| pois = [FakePOI("Fontaine Médicis", "water_feature")] | |
| text = "Pass by Fontaine Médicis sur Montmartre, a lovely spot." | |
| ok, offenders = grounding.verify_grounded(text, pois, start_label="Bastille") | |
| assert not ok | |
| assert any("Montmartre" in o for o in offenders) | |
| def test_gate_allows_shortened_reference(): | |
| """Referring to a place by a shortened form of its real name is fine.""" | |
| pois = [FakePOI("Jardin des Plantes de Paris", "park_garden")] | |
| text = "Stroll through Jardin des Plantes, then onward." | |
| ok, offenders = grounding.verify_grounded(text, pois, start_label="Bastille") | |
| assert ok, offenders | |
| def test_gate_allows_unnamed_by_type(): | |
| text = "Stop at a cafe near Jardin des Plantes for a coffee-stop pause." | |
| ok, offenders = grounding.verify_grounded(text, POIS, start_label="Bastille") | |
| assert ok, offenders | |
| def test_geo_gazetteer_allows_real_districts(): | |
| """A guide may name the city's real districts/river when given the gazetteer, | |
| so vivid prose passes the gate instead of being thrown out for the template.""" | |
| from discoverroute.narrate import gazetteer | |
| geo = gazetteer.geo_terms("paris", "Paris") | |
| text = ("From the Marais, drift down toward the Seine and into the Latin " | |
| "Quarter, passing Jardin des Plantes for some green and Fontaine " | |
| "Médicis. A very Parisian wander with Roman echoes near the Panthéon.") | |
| ok, offenders = grounding.verify_grounded( | |
| text, POIS, start_label="Bastille", end_label="Panthéon", | |
| extra_allowed=geo, | |
| ) | |
| assert ok, offenders | |
| def test_geo_gazetteer_allows_landmarks_but_not_eiffel(): | |
| """Expanded gazetteer lets a guide name real landmarks (Arc de Triomphe, | |
| Sacré-Cœur) for scene-setting, while the Eiffel Tower — the planted | |
| hallucination — must STILL be caught.""" | |
| from discoverroute.narrate import gazetteer | |
| geo = gazetteer.geo_terms("paris", "Paris") | |
| ok, off = grounding.verify_grounded( | |
| "From the Arc de Triomphe, wander toward Sacré-Cœur, passing " | |
| "Jardin des Plantes.", POIS, start_label="Bastille", extra_allowed=geo) | |
| assert ok, off | |
| # the famous-landmark allowlist must not punch a hole for the planted one | |
| ok2, off2 = grounding.verify_grounded( | |
| "A lovely detour past the Eiffel Tower, then Jardin des Plantes.", | |
| POIS, start_label="Bastille", extra_allowed=geo) | |
| assert not ok2 and any("Eiffel" in o for o in off2) | |
| def test_gate_ignores_art_movements_and_events(): | |
| """Capitalized non-places (art movements, historical events, sentence-starters) | |
| must not trip the gate — they were sinking whole narrations live | |
| ('Impressionism', 'French Revolution', 'Remember').""" | |
| text = ("Remember the French Revolution as you pass Jardin des Plantes; the " | |
| "Impressionism and Impressionist spirit lingers here.") | |
| ok, off = grounding.verify_grounded(text, POIS, start_label="Bastille") | |
| assert ok, off | |
| # but a genuinely invented venue in the same prose still fails | |
| ok2, off2 = grounding.verify_grounded( | |
| "Remember the Impressionists at Café des Mensonges near Jardin des Plantes.", | |
| POIS, start_label="Bastille") | |
| assert not ok2 and any("Mensonges" in o for o in off2) | |
| def test_geo_gazetteer_still_blocks_invented_venue(): | |
| """Loosening for districts must NOT let an invented venue through.""" | |
| from discoverroute.narrate import gazetteer | |
| geo = gazetteer.geo_terms("paris", "Paris") | |
| text = ("Cross the Marais, then stop at Café des Mensonges, a charming " | |
| "invented spot, before Jardin des Plantes.") | |
| ok, offenders = grounding.verify_grounded( | |
| text, POIS, start_label="Bastille", extra_allowed=geo, | |
| ) | |
| assert not ok | |
| assert any("Mensonges" in o for o in offenders) | |
| def test_uncurated_city_has_no_extra_context(): | |
| """An on-demand area (bbox-hash key) gets no gazetteer => fails closed.""" | |
| from discoverroute.narrate import gazetteer | |
| assert gazetteer.geo_terms("48.1,2.2,48.2,2.3", "this area") == ["this area"] | |
| class _Route: | |
| def __init__(self, time_min): | |
| self.time_min = time_min | |
| def test_template_is_grounded_by_construction(): | |
| plain, discovery = _Route(40), _Route(58) | |
| text = template_narration( | |
| plain, discovery, POIS, vibe="quiet green wander", mode="walk", | |
| start_label="Bastille", end_label="Panthéon", | |
| posture={"park_garden": "pass", "water_feature": "pass", "cafe": "stop"}, | |
| ) | |
| ok, offenders = grounding.verify_grounded( | |
| text, POIS, start_label="Bastille", end_label="Panthéon" | |
| ) | |
| assert ok, f"template leaked place names: {offenders}" | |
| assert "Jardin des Plantes" in text and "Fontaine Médicis" in text | |
| data_ready = pytest.mark.skipif( | |
| not (config.GRAPH_WALK_PATH.exists() and config.POIS_PATH.exists()), | |
| reason="Graph or POI table not built", | |
| ) | |
| def test_end_to_end_narration_grounded(): | |
| """The shipped itinerary for a real route must pass the gate (the release gate).""" | |
| from discoverroute.pipeline import plan_route | |
| r = plan_route("Place de la République, Paris", "Jardin du Luxembourg, Paris", | |
| budget=0.7, vibe="quiet green wander") | |
| assert r.discovery is not None and r.pois | |
| ok, offenders = grounding.verify_grounded( | |
| r.itinerary_md, r.pois, | |
| start_label="Place de la République, Paris", | |
| end_label="Jardin du Luxembourg, Paris", | |
| ) | |
| assert ok, f"hallucinated place names in shipped narration: {offenders}" | |