remdms Claude Opus 4.6 commited on
Commit
c5592ad
·
1 Parent(s): e06f7cb

test: expand generation tests to 30 queries across full spectrum

Browse files

Cover all 7 categories from eval ground truth:
- Geographic (5): Congo, Afghanistan, East Africa, Latin America, Israel-Palestine
- Thematic (5): PTSD, climate, women's rights, wildlife, immigration
- Temporal (4): 2005-2006, 2022-2025, financial crisis, around 2010
- People (4): Salgado, McCullin, Ai Weiwei, Angelina Jolie
- Genre (4): photo essays, crisis guides, family/aging, animation
- Awards (4): Emmy, World Press Photo, Iraq war awards, Webby
- Edge cases (4): quantum, pasta, Taylor Swift, crypto

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

Files changed (1) hide show
  1. tests/test_generation.py +211 -41
tests/test_generation.py CHANGED
@@ -1,8 +1,12 @@
1
- """Tests for generation quality — validates Flash responses meet expectations."""
 
 
 
 
2
  import pytest
3
 
4
  from mediastorm.rag.generator import generate_response
5
- from mediastorm.rag.retriever import HybridRetriever, RetrievalResult
6
  from mediastorm.vectorize.store import VectorStore
7
  from mediastorm.vectorize.embedder import Embedder
8
  from mediastorm.vectorize.bm25_store import BM25Store
@@ -10,6 +14,7 @@ from mediastorm.rag.reranker import Reranker
10
  from mediastorm.rag.router import QueryRouter
11
  from mediastorm.config import CHROMADB_PATH, BM25_INDEX_PATH
12
 
 
13
  @pytest.fixture(autouse=True)
14
  def _reset_gemini_client():
15
  """Reset global Gemini client between tests to avoid event loop issues."""
@@ -46,63 +51,228 @@ async def _ask(retriever, query: str) -> str:
46
  return await generate_response(query, result, [])
47
 
48
 
49
- class TestResponseCitations:
50
- """Every mentioned story should include a clickable mediastorm.com link."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  @pytest.mark.asyncio
53
- async def test_congo_war_has_links(self, retriever):
54
- response = await _ask(retriever, "Stories about the war in Congo")
55
- assert "https://www.mediastorm.com/" in response
 
56
 
57
  @pytest.mark.asyncio
58
- async def test_wildlife_has_links(self, retriever):
59
- response = await _ask(retriever, "Wildlife conservation stories")
60
- assert "https://www.mediastorm.com/" in response
 
 
 
 
 
 
 
61
 
62
  @pytest.mark.asyncio
63
- async def test_salgado_has_links(self, retriever):
64
- response = await _ask(retriever, "Stories about Sebastiao Salgado")
65
- assert "https://www.mediastorm.com/" in response
66
- assert "salgado" in response.lower()
67
 
 
 
 
 
 
68
 
69
- class TestResponseRelevance:
70
- """Responses should match the query topic and cite correct stories."""
 
 
 
71
 
72
  @pytest.mark.asyncio
73
- async def test_congo_mentions_condition_critical(self, retriever):
74
- response = await _ask(retriever, "Stories about the war in Congo")
75
- assert "condition" in response.lower() or "congo" in response.lower()
 
 
 
 
 
 
76
 
77
  @pytest.mark.asyncio
78
- async def test_climate_mentions_relevant_stories(self, retriever):
79
- response = await _ask(retriever, "Climate change documentaries")
80
- assert any(
81
- term in response.lower()
82
- for term in ["climate", "environment", "glacier", "water", "appalachia"]
83
- )
84
 
85
  @pytest.mark.asyncio
86
- async def test_person_search_returns_correct_person(self, retriever):
87
- response = await _ask(retriever, "Stories about Don McCullin")
88
- assert "mccullin" in response.lower()
89
 
 
 
 
 
90
 
 
 
 
 
 
 
 
 
 
91
  class TestEdgeCases:
92
- """Queries with no matching content should say so explicitly."""
93
 
94
  @pytest.mark.asyncio
95
- async def test_food_does_not_hallucinate(self, retriever):
96
- response = await _ask(retriever, "I want stories about food")
97
- # Should not fabricate story names that don't exist in the archive
98
- # Acceptable: saying nothing found, OR citing tangential real stories
99
- assert len(response) > 20 # got a response at all
 
 
 
 
 
 
 
 
100
 
101
  @pytest.mark.asyncio
102
- async def test_quantum_computing_returns_nothing(self, retriever):
103
- response = await _ask(retriever, "Quantum computing breakthroughs")
104
- response_lower = response.lower()
105
- assert any(
106
- phrase in response_lower
107
- for phrase in ["no stor", "not contain", "does not", "no relevant"]
108
- )
 
1
+ """Tests for generation quality — validates Flash responses across full query spectrum.
2
+
3
+ 30 queries covering: geographic, thematic, temporal, people, genre, awards, edge cases.
4
+ Each test checks: links present, relevant content mentioned, no hallucination.
5
+ """
6
  import pytest
7
 
8
  from mediastorm.rag.generator import generate_response
9
+ from mediastorm.rag.retriever import HybridRetriever
10
  from mediastorm.vectorize.store import VectorStore
11
  from mediastorm.vectorize.embedder import Embedder
12
  from mediastorm.vectorize.bm25_store import BM25Store
 
14
  from mediastorm.rag.router import QueryRouter
15
  from mediastorm.config import CHROMADB_PATH, BM25_INDEX_PATH
16
 
17
+
18
  @pytest.fixture(autouse=True)
19
  def _reset_gemini_client():
20
  """Reset global Gemini client between tests to avoid event loop issues."""
 
51
  return await generate_response(query, result, [])
52
 
53
 
54
+ def _has_link(response: str) -> bool:
55
+ return "https://www.mediastorm.com/" in response
56
+
57
+
58
+ def _has_any(response: str, terms: list[str]) -> bool:
59
+ lower = response.lower()
60
+ return any(t.lower() in lower for t in terms)
61
+
62
+
63
+ def _says_nothing_found(response: str) -> bool:
64
+ return _has_any(response, [
65
+ "no stor", "not contain", "does not", "no relevant",
66
+ "no direct", "no specific", "no primary", "not primarily",
67
+ "doesn't contain", "do not have",
68
+ ])
69
+
70
+
71
+ # -------------------------------------------------------------------------
72
+ # GEOGRAPHIC (5)
73
+ # -------------------------------------------------------------------------
74
+ class TestGeographic:
75
+
76
+ @pytest.mark.asyncio
77
+ async def test_congo_war(self, retriever):
78
+ r = await _ask(retriever, "Stories about the war in Congo")
79
+ assert _has_link(r)
80
+ assert _has_any(r, ["Congo", "Condition: Critical"])
81
+
82
+ @pytest.mark.asyncio
83
+ async def test_afghanistan(self, retriever):
84
+ r = await _ask(retriever, "Documentaries set in Afghanistan")
85
+ assert _has_link(r)
86
+ assert _has_any(r, ["Afghanistan", "Darkness Visible", "Taliban"])
87
+
88
+ @pytest.mark.asyncio
89
+ async def test_east_africa(self, retriever):
90
+ r = await _ask(retriever, "Stories about East Africa")
91
+ assert _has_link(r)
92
+ assert _has_any(r, ["Kenya", "Ethiopia", "Somalia", "Africa"])
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_latin_america(self, retriever):
96
+ r = await _ask(retriever, "Stories filmed in Latin America or Mexico")
97
+ assert _has_link(r)
98
+ assert _has_any(r, ["Mexico", "Peru", "Cuba", "Latin America", "Tequila"])
99
+
100
+ @pytest.mark.asyncio
101
+ async def test_israel_palestine(self, retriever):
102
+ r = await _ask(retriever, "Stories about the Israeli-Palestinian conflict")
103
+ assert _has_link(r)
104
+ assert _has_any(r, ["Israel", "Palestin", "Crisis Guide"])
105
+
106
+
107
+ # -------------------------------------------------------------------------
108
+ # THEMATIC (5)
109
+ # -------------------------------------------------------------------------
110
+ class TestThematic:
111
+
112
+ @pytest.mark.asyncio
113
+ async def test_ptsd_veterans(self, retriever):
114
+ r = await _ask(retriever, "Stories about PTSD and veterans returning from war")
115
+ assert _has_link(r)
116
+ assert _has_any(r, ["veteran", "PTSD", "soldier", "war", "marine"])
117
+
118
+ @pytest.mark.asyncio
119
+ async def test_climate_change(self, retriever):
120
+ r = await _ask(retriever, "Climate change and environmental destruction")
121
+ assert _has_link(r)
122
+ assert _has_any(r, ["climate", "environment", "glacier", "water", "mining"])
123
+
124
+ @pytest.mark.asyncio
125
+ async def test_womens_rights(self, retriever):
126
+ r = await _ask(retriever, "Child marriage and women's rights")
127
+ assert _has_link(r)
128
+ assert _has_any(r, ["marriage", "women", "bride", "girl", "violence"])
129
+
130
+ @pytest.mark.asyncio
131
+ async def test_wildlife(self, retriever):
132
+ r = await _ask(retriever, "Wildlife conservation and endangered species")
133
+ assert _has_link(r)
134
+ assert _has_any(r, ["wildlife", "conservation", "rhino", "elephant", "gorilla", "ivory", "fox"])
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_immigration(self, retriever):
138
+ r = await _ask(retriever, "Immigration and refugee stories")
139
+ assert _has_link(r)
140
+ assert _has_any(r, ["immigra", "refugee", "migration", "crossing", "undocumented"])
141
+
142
+
143
+ # -------------------------------------------------------------------------
144
+ # TEMPORAL (4)
145
+ # -------------------------------------------------------------------------
146
+ class TestTemporal:
147
+
148
+ @pytest.mark.asyncio
149
+ async def test_earliest_stories(self, retriever):
150
+ r = await _ask(retriever, "MediaStorm's earliest stories from 2005-2006")
151
+ assert _has_link(r) or _says_nothing_found(r)
152
+
153
+ @pytest.mark.asyncio
154
+ async def test_recent_stories(self, retriever):
155
+ r = await _ask(retriever, "Recent stories from 2022 to 2025")
156
+ assert _has_link(r) or _says_nothing_found(r)
157
+
158
+ @pytest.mark.asyncio
159
+ async def test_financial_crisis(self, retriever):
160
+ r = await _ask(retriever, "Stories from the 2008 financial crisis era")
161
+ assert _has_link(r)
162
+ assert _has_any(r, ["crisis", "econom", "financial", "Times of Crisis"])
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_around_2010(self, retriever):
166
+ r = await _ask(retriever, "Stories published around 2010")
167
+ assert _has_link(r) or _says_nothing_found(r)
168
+
169
+
170
+ # -------------------------------------------------------------------------
171
+ # PEOPLE (4)
172
+ # -------------------------------------------------------------------------
173
+ class TestPeople:
174
+
175
+ @pytest.mark.asyncio
176
+ async def test_salgado(self, retriever):
177
+ r = await _ask(retriever, "Stories about Sebastiao Salgado")
178
+ assert _has_link(r)
179
+ assert _has_any(r, ["Salgado"])
180
+
181
+ @pytest.mark.asyncio
182
+ async def test_don_mccullin(self, retriever):
183
+ r = await _ask(retriever, "Stories featuring Don McCullin")
184
+ assert _has_link(r)
185
+ assert _has_any(r, ["McCullin"])
186
 
187
  @pytest.mark.asyncio
188
+ async def test_ai_weiwei(self, retriever):
189
+ r = await _ask(retriever, "Stories about Ai Weiwei")
190
+ assert _has_link(r)
191
+ assert _has_any(r, ["Weiwei", "Ai Wei"])
192
 
193
  @pytest.mark.asyncio
194
+ async def test_angelina_jolie(self, retriever):
195
+ r = await _ask(retriever, "Stories about Angelina Jolie")
196
+ assert _has_link(r)
197
+ assert _has_any(r, ["Jolie", "Angelina"])
198
+
199
+
200
+ # -------------------------------------------------------------------------
201
+ # GENRE / FORMAT (4)
202
+ # -------------------------------------------------------------------------
203
+ class TestGenre:
204
 
205
  @pytest.mark.asyncio
206
+ async def test_photo_essays(self, retriever):
207
+ r = await _ask(retriever, "Photo essays in the archive")
208
+ assert _has_link(r)
209
+ assert _has_any(r, ["photo essay", "photo"])
210
 
211
+ @pytest.mark.asyncio
212
+ async def test_crisis_guides(self, retriever):
213
+ r = await _ask(retriever, "Interactive multimedia projects or crisis guides")
214
+ assert _has_link(r)
215
+ assert _has_any(r, ["crisis guide", "interactive", "multimedia"])
216
 
217
+ @pytest.mark.asyncio
218
+ async def test_family_aging(self, retriever):
219
+ r = await _ask(retriever, "Documentaries about family and aging")
220
+ assert _has_link(r)
221
+ assert _has_any(r, ["family", "aging", "dementia", "caregiv", "alzheimer"])
222
 
223
  @pytest.mark.asyncio
224
+ async def test_animation(self, retriever):
225
+ r = await _ask(retriever, "Animated or motion design pieces")
226
+ assert _has_link(r) or _says_nothing_found(r)
227
+
228
+
229
+ # -------------------------------------------------------------------------
230
+ # AWARDS (4)
231
+ # -------------------------------------------------------------------------
232
+ class TestAwards:
233
 
234
  @pytest.mark.asyncio
235
+ async def test_emmy_winners(self, retriever):
236
+ r = await _ask(retriever, "Emmy award winning stories")
237
+ assert _has_any(r, ["Emmy", "award"])
 
 
 
238
 
239
  @pytest.mark.asyncio
240
+ async def test_world_press_photo(self, retriever):
241
+ r = await _ask(retriever, "World Press Photo winners")
242
+ assert _has_any(r, ["World Press", "award", "photo"])
243
 
244
+ @pytest.mark.asyncio
245
+ async def test_iraq_war_awards(self, retriever):
246
+ r = await _ask(retriever, "Award-winning stories about the Iraq war")
247
+ assert _has_any(r, ["Iraq", "war", "award", "Marlboro"])
248
 
249
+ @pytest.mark.asyncio
250
+ async def test_webby_awards(self, retriever):
251
+ r = await _ask(retriever, "Stories that won at Webby Awards")
252
+ assert _has_any(r, ["Webby", "award"])
253
+
254
+
255
+ # -------------------------------------------------------------------------
256
+ # EDGE CASES — should return nothing relevant (4)
257
+ # -------------------------------------------------------------------------
258
  class TestEdgeCases:
 
259
 
260
  @pytest.mark.asyncio
261
+ async def test_quantum_computing(self, retriever):
262
+ r = await _ask(retriever, "Quantum computing breakthroughs")
263
+ assert _says_nothing_found(r)
264
+
265
+ @pytest.mark.asyncio
266
+ async def test_pasta_recipes(self, retriever):
267
+ r = await _ask(retriever, "Best Italian pasta recipes from Tuscany")
268
+ assert _says_nothing_found(r)
269
+
270
+ @pytest.mark.asyncio
271
+ async def test_taylor_swift(self, retriever):
272
+ r = await _ask(retriever, "Taylor Swift concert tour dates")
273
+ assert _says_nothing_found(r)
274
 
275
  @pytest.mark.asyncio
276
+ async def test_crypto_trading(self, retriever):
277
+ r = await _ask(retriever, "Stock market trading strategies and cryptocurrency")
278
+ assert _says_nothing_found(r)