coreprinciple Claude Opus 4.8 commited on
Commit
f440fba
·
1 Parent(s): 36d6af8

Narration: expand per-city gazetteer with famous landmarks (loosen gate)

Browse files

The 1B name-drops real landmarks (Arc de Triomphe, Sacre-Coeur, Edo
Castle...) for scene-setting; the strict gate rejected them -> template.
Add curated landmark allowlists per pre-baked city so that grounded,
vivid prose passes instead. Eiffel Tower stays OFF the list (the planted
hallucination the gate must keep catching) and invented venues still
fail. Narration prompt realigned: name the 'You may reference' places
freely, never anything outside the lists.

Test: test_geo_gazetteer_allows_landmarks_but_not_eiffel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

src/discoverroute/narrate/gazetteer.py CHANGED
@@ -34,9 +34,18 @@ CITY_GAZETTEER: dict[str, list[str]] = {
34
  "Le Marais", "Marais", "Quartier Latin", "Latin Quarter",
35
  "Saint-Germain", "Saint-Germain-des-Prés", "Montmartre", "Montparnasse",
36
  "Belleville", "Bastille", "Pigalle", "Oberkampf", "Le Sentier",
37
- # well-known landmarks/areas a guide name-checks in passing
 
 
38
  "Sorbonne", "Panthéon", "Champs-Élysées", "Tuileries", "Louvre",
39
- "Notre-Dame", "Île-de-France",
 
 
 
 
 
 
 
40
  ],
41
  "london": [
42
  "Thames", "South Bank", "Southbank", "North Bank", "the City",
@@ -44,12 +53,27 @@ CITY_GAZETTEER: dict[str, list[str]] = {
44
  "Southwark", "Lambeth", "Vauxhall", "Holborn", "Clerkenwell",
45
  "Fitzrovia", "Marylebone", "the West End", "the East End", "Shoreditch",
46
  "Bankside", "Embankment", "Strand", "Piccadilly",
 
 
 
 
 
 
 
 
 
47
  ],
48
  "barcelona": [
49
  "Mediterranean", "Barri Gòtic", "Gothic Quarter", "El Raval", "El Born",
50
  "La Ribera", "Eixample", "La Rambla", "Las Ramblas", "Barceloneta",
51
  "Gràcia", "Montjuïc", "Poble Sec", "Ciutat Vella", "Port Vell",
52
  "Passeig de Gràcia",
 
 
 
 
 
 
53
  ],
54
  "newyork": [
55
  "Manhattan", "Midtown", "Downtown", "Uptown", "SoHo", "NoHo", "Tribeca",
@@ -61,33 +85,65 @@ CITY_GAZETTEER: dict[str, list[str]] = {
61
  "the Garment District", "Hell's Kitchen", "Murray Hill", "Koreatown",
62
  "Herald Square", "Madison Square", "the High Line", "Rockefeller Center",
63
  "Grand Central", "Penn Station", "Bryant Park",
 
 
 
 
 
 
 
64
  ],
65
  "sanfrancisco": [
66
  "San Francisco", "Union Square", "the Financial District", "North Beach",
67
  "Chinatown", "Nob Hill", "Russian Hill", "Telegraph Hill", "the Embarcadero",
68
  "SoMa", "the Mission", "Hayes Valley", "the Tenderloin", "Fisherman's Wharf",
69
  "the Marina", "Market Street", "the Bay", "San Francisco Bay",
 
 
 
 
 
 
70
  ],
71
  "tokyo": [
72
  "Tokyo", "Ginza", "Marunouchi", "Nihonbashi", "Yurakucho", "Tokyo Station",
73
  "the Imperial Palace", "Chiyoda", "Hibiya", "Kanda", "Otemachi",
74
  "Shimbashi", "Tsukiji", "the Sumida", "the Sumida River",
 
 
 
 
 
75
  ],
76
  "mumbai": [
77
  "Mumbai", "Colaba", "Fort", "Marine Drive", "Churchgate", "Nariman Point",
78
  "Kala Ghoda", "Ballard Estate", "Cuffe Parade", "South Mumbai",
79
  "the Gateway of India", "the Arabian Sea",
 
 
 
 
80
  ],
81
  "shanghai": [
82
  "Shanghai", "the Bund", "People's Square", "Huangpu", "the Huangpu River",
83
  "Nanjing Road", "Yu Garden", "the French Concession", "Jing'an",
84
  "Lujiazui", "Xintiandi", "the Old City",
 
 
 
 
85
  ],
86
  "berlin": [
87
  "Berlin", "Mitte", "the Spree", "Museum Island", "Unter den Linden",
88
  "Alexanderplatz", "the Brandenburg Gate", "Kreuzberg", "Prenzlauer Berg",
89
  "Friedrichshain", "Hackescher Markt", "Gendarmenmarkt", "Potsdamer Platz",
90
  "the Tiergarten",
 
 
 
 
 
 
91
  ],
92
  }
93
 
 
34
  "Le Marais", "Marais", "Quartier Latin", "Latin Quarter",
35
  "Saint-Germain", "Saint-Germain-des-Prés", "Montmartre", "Montparnasse",
36
  "Belleville", "Bastille", "Pigalle", "Oberkampf", "Le Sentier",
37
+ # well-known landmarks/areas a guide name-checks in passing.
38
+ # NOTE: the Eiffel Tower is deliberately NOT listed — it's the planted
39
+ # hallucination the zero-hallucination gate must keep catching.
40
  "Sorbonne", "Panthéon", "Champs-Élysées", "Tuileries", "Louvre",
41
+ "Notre-Dame", "Île-de-France", "Arc de Triomphe", "Sacré-Cœur",
42
+ "Sacre-Coeur", "Pont Neuf", "Pont des Arts", "Palais Garnier",
43
+ "Opéra Garnier", "Grand Palais", "Petit Palais", "Place de la Concorde",
44
+ "Place Vendôme", "Place des Vosges", "Place de la République",
45
+ "Jardin du Luxembourg", "Luxembourg Gardens", "Jardin des Tuileries",
46
+ "Centre Pompidou", "Musée d'Orsay", "Orsay", "Hôtel de Ville",
47
+ "Trocadéro", "Les Invalides", "Invalides", "La Madeleine",
48
+ "Père Lachaise", "Bois de Boulogne", "Bois de Vincennes", "Mona Lisa",
49
  ],
50
  "london": [
51
  "Thames", "South Bank", "Southbank", "North Bank", "the City",
 
53
  "Southwark", "Lambeth", "Vauxhall", "Holborn", "Clerkenwell",
54
  "Fitzrovia", "Marylebone", "the West End", "the East End", "Shoreditch",
55
  "Bankside", "Embankment", "Strand", "Piccadilly",
56
+ # landmarks
57
+ "Big Ben", "the Tower of London", "Tower Bridge", "London Bridge",
58
+ "St Paul's", "St Paul's Cathedral", "Buckingham Palace",
59
+ "Trafalgar Square", "Leicester Square", "the Shard", "the Gherkin",
60
+ "the London Eye", "Hyde Park", "Regent's Park", "St James's Park",
61
+ "Green Park", "the British Museum", "Tate Modern", "the National Gallery",
62
+ "Borough Market", "Camden", "Camden Town", "Notting Hill", "Kensington",
63
+ "Greenwich", "Canary Wharf", "the Barbican", "Whitehall",
64
+ "Downing Street", "the Houses of Parliament", "Westminster Abbey",
65
  ],
66
  "barcelona": [
67
  "Mediterranean", "Barri Gòtic", "Gothic Quarter", "El Raval", "El Born",
68
  "La Ribera", "Eixample", "La Rambla", "Las Ramblas", "Barceloneta",
69
  "Gràcia", "Montjuïc", "Poble Sec", "Ciutat Vella", "Port Vell",
70
  "Passeig de Gràcia",
71
+ # landmarks
72
+ "Sagrada Família", "Sagrada Familia", "Park Güell", "Casa Batlló",
73
+ "Casa Milà", "La Pedrera", "Plaça de Catalunya", "Plaça Reial",
74
+ "the Cathedral", "Catedral", "Camp Nou", "Tibidabo", "Arc de Triomf",
75
+ "La Boqueria", "Mercat de la Boqueria", "Port Olímpic",
76
+ "Barceloneta Beach",
77
  ],
78
  "newyork": [
79
  "Manhattan", "Midtown", "Downtown", "Uptown", "SoHo", "NoHo", "Tribeca",
 
85
  "the Garment District", "Hell's Kitchen", "Murray Hill", "Koreatown",
86
  "Herald Square", "Madison Square", "the High Line", "Rockefeller Center",
87
  "Grand Central", "Penn Station", "Bryant Park",
88
+ # landmarks
89
+ "the Empire State Building", "the Chrysler Building",
90
+ "the Statue of Liberty", "the Brooklyn Bridge", "Wall Street",
91
+ "Washington Square", "Washington Square Park", "Madison Square Garden",
92
+ "the Met", "the Metropolitan Museum", "MoMA", "the Guggenheim",
93
+ "the Flatiron Building", "Battery Park", "Columbus Circle",
94
+ "Lincoln Center", "Little Italy", "Nolita", "the Meatpacking District",
95
  ],
96
  "sanfrancisco": [
97
  "San Francisco", "Union Square", "the Financial District", "North Beach",
98
  "Chinatown", "Nob Hill", "Russian Hill", "Telegraph Hill", "the Embarcadero",
99
  "SoMa", "the Mission", "Hayes Valley", "the Tenderloin", "Fisherman's Wharf",
100
  "the Marina", "Market Street", "the Bay", "San Francisco Bay",
101
+ # landmarks
102
+ "the Golden Gate Bridge", "the Golden Gate", "Alcatraz", "Coit Tower",
103
+ "Lombard Street", "the Painted Ladies", "Alamo Square",
104
+ "the Ferry Building", "Pier 39", "the Presidio", "Golden Gate Park",
105
+ "the Castro", "Haight-Ashbury", "the Haight", "Twin Peaks",
106
+ "the Transamerica Pyramid", "Ghirardelli Square", "Dolores Park",
107
  ],
108
  "tokyo": [
109
  "Tokyo", "Ginza", "Marunouchi", "Nihonbashi", "Yurakucho", "Tokyo Station",
110
  "the Imperial Palace", "Chiyoda", "Hibiya", "Kanda", "Otemachi",
111
  "Shimbashi", "Tsukiji", "the Sumida", "the Sumida River",
112
+ # landmarks / districts
113
+ "Tokyo Tower", "Tokyo Skytree", "Sensō-ji", "Senso-ji", "Asakusa",
114
+ "Shibuya", "Shibuya Crossing", "Shinjuku", "Harajuku", "Akihabara",
115
+ "Ueno", "Ueno Park", "Roppongi", "Meiji Shrine", "Edo Castle",
116
+ "Hibiya Park",
117
  ],
118
  "mumbai": [
119
  "Mumbai", "Colaba", "Fort", "Marine Drive", "Churchgate", "Nariman Point",
120
  "Kala Ghoda", "Ballard Estate", "Cuffe Parade", "South Mumbai",
121
  "the Gateway of India", "the Arabian Sea",
122
+ # landmarks
123
+ "Chhatrapati Shivaji Terminus", "the Taj Mahal Palace", "Haji Ali",
124
+ "Girgaum Chowpatty", "Chowpatty", "Flora Fountain", "Crawford Market",
125
+ "the Hanging Gardens", "Banganga", "Bandra", "Worli",
126
  ],
127
  "shanghai": [
128
  "Shanghai", "the Bund", "People's Square", "Huangpu", "the Huangpu River",
129
  "Nanjing Road", "Yu Garden", "the French Concession", "Jing'an",
130
  "Lujiazui", "Xintiandi", "the Old City",
131
+ # landmarks
132
+ "the Oriental Pearl Tower", "the Shanghai Tower", "the Jin Mao Tower",
133
+ "the City God Temple", "the Jade Buddha Temple", "Tianzifang",
134
+ "Jing'an Temple", "People's Park", "the Yuyuan Garden",
135
  ],
136
  "berlin": [
137
  "Berlin", "Mitte", "the Spree", "Museum Island", "Unter den Linden",
138
  "Alexanderplatz", "the Brandenburg Gate", "Kreuzberg", "Prenzlauer Berg",
139
  "Friedrichshain", "Hackescher Markt", "Gendarmenmarkt", "Potsdamer Platz",
140
  "the Tiergarten",
141
+ # landmarks
142
+ "the Reichstag", "the TV Tower", "Fernsehturm", "Checkpoint Charlie",
143
+ "the Berlin Wall", "the East Side Gallery", "Berliner Dom",
144
+ "the Berlin Cathedral", "the Holocaust Memorial", "Charlottenburg",
145
+ "the Victory Column", "the Siegessäule", "Nikolaiviertel",
146
+ "Kurfürstendamm", "the Pergamon Museum",
147
  ],
148
  }
149
 
src/discoverroute/narrate/narrate.py CHANGED
@@ -268,13 +268,12 @@ def _llm_narration(plain, discovery, pois, vibe, mode, start_label, end_label,
268
  "Never write bare type labels like 'park garden', 'place of worship' or "
269
  "'monument historic'. Mention the time of day or the light.\n"
270
  "ONE HARD RULE — NAMES: the ONLY proper names you may write are the 'Ordered "
271
- "stops', the start/destination, and the items under 'You may reference'. Do NOT "
272
- "name ANY other place no landmark, museum, monument, bridge, street, square, "
273
- "university, church or artwork, even a real famous one (for example do not name "
274
- "the Mona Lisa, Sacré-Cœur, Pont des Arts, the Sorbonne, the Louvre) unless it "
275
- "is in those lists. To set the scene, describe WITHOUT proper names "
276
- "('a stone bridge', 'a grand old church'). A single name outside the lists "
277
- "makes the entire itinerary get thrown away."
278
  )
279
  user = (
280
  f"Vibe: {vibe or 'open to anything'}\n"
 
268
  "Never write bare type labels like 'park garden', 'place of worship' or "
269
  "'monument historic'. Mention the time of day or the light.\n"
270
  "ONE HARD RULE — NAMES: the ONLY proper names you may write are the 'Ordered "
271
+ "stops', the start/destination, and the places under 'You may reference'. Name "
272
+ "those freely for scene-setting. Do NOT name ANY place outside those lists — no "
273
+ "other landmark, museum, monument, street or venue, even a real famous one "
274
+ "and never invent a venue. If you want more scenery, describe it WITHOUT a "
275
+ "proper name ('a stone bridge', 'a grand old church'). A single name outside "
276
+ "the lists makes the entire itinerary get thrown away."
 
277
  )
278
  user = (
279
  f"Vibe: {vibe or 'open to anything'}\n"
tests/test_narration.py CHANGED
@@ -84,6 +84,23 @@ def test_geo_gazetteer_allows_real_districts():
84
  assert ok, offenders
85
 
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  def test_geo_gazetteer_still_blocks_invented_venue():
88
  """Loosening for districts must NOT let an invented venue through."""
89
  from discoverroute.narrate import gazetteer
 
84
  assert ok, offenders
85
 
86
 
87
+ def test_geo_gazetteer_allows_landmarks_but_not_eiffel():
88
+ """Expanded gazetteer lets a guide name real landmarks (Arc de Triomphe,
89
+ Sacré-Cœur) for scene-setting, while the Eiffel Tower — the planted
90
+ hallucination — must STILL be caught."""
91
+ from discoverroute.narrate import gazetteer
92
+ geo = gazetteer.geo_terms("paris", "Paris")
93
+ ok, off = grounding.verify_grounded(
94
+ "From the Arc de Triomphe, wander toward Sacré-Cœur, passing "
95
+ "Jardin des Plantes.", POIS, start_label="Bastille", extra_allowed=geo)
96
+ assert ok, off
97
+ # the famous-landmark allowlist must not punch a hole for the planted one
98
+ ok2, off2 = grounding.verify_grounded(
99
+ "A lovely detour past the Eiffel Tower, then Jardin des Plantes.",
100
+ POIS, start_label="Bastille", extra_allowed=geo)
101
+ assert not ok2 and any("Eiffel" in o for o in off2)
102
+
103
+
104
  def test_geo_gazetteer_still_blocks_invented_venue():
105
  """Loosening for districts must NOT let an invented venue through."""
106
  from discoverroute.narrate import gazetteer