WhoIsLuwah commited on
Commit
e335b6a
·
1 Parent(s): f6df0e9

fix(backend): embed_reel never raises and never returns an unstorable vector

Browse files
Files changed (2) hide show
  1. main.py +11 -5
  2. test_pipeline.py +20 -4
main.py CHANGED
@@ -210,6 +210,7 @@ EMBEDDING_MODEL_NAME = os.getenv(
210
  "EMBEDDING_MODEL_NAME",
211
  "sentence-transformers/all-MiniLM-L6-v2",
212
  )
 
213
  CLUSTER_SAMPLE_SIZE = 4
214
  MIN_SEMANTIC_CLUSTERS = 8
215
  MAX_SEMANTIC_CLUSTERS = 12
@@ -261,15 +262,20 @@ def embed_reel(extracted_json: Optional[dict]) -> Optional[List[float]]:
261
  Never raises: a reel must still save when embedding is unavailable
262
  (backfill_embeddings.py repairs it later). Returns None when there is
263
  nothing to embed."""
264
- text = _reel_embedding_text(extracted_json)
265
- if not text:
266
- return None
267
  try:
268
- vector = next(iter(_get_embedding_model().embed([text])))
269
- return [float(x) for x in vector]
 
 
270
  except Exception as e:
271
  logger.warning(f"Could not embed reel for search: {getattr(e, 'detail', e)}")
272
  return None
 
 
 
 
 
 
273
 
274
 
275
  def _target_cluster_count(item_count: int) -> int:
 
210
  "EMBEDDING_MODEL_NAME",
211
  "sentence-transformers/all-MiniLM-L6-v2",
212
  )
213
+ EMBEDDING_DIMENSIONS = 384 # size of the saved_reels.embedding vector(384) column
214
  CLUSTER_SAMPLE_SIZE = 4
215
  MIN_SEMANTIC_CLUSTERS = 8
216
  MAX_SEMANTIC_CLUSTERS = 12
 
262
  Never raises: a reel must still save when embedding is unavailable
263
  (backfill_embeddings.py repairs it later). Returns None when there is
264
  nothing to embed."""
 
 
 
265
  try:
266
+ text = _reel_embedding_text(extracted_json)
267
+ if not text:
268
+ return None
269
+ vector = [float(x) for x in next(iter(_get_embedding_model().embed([text])))]
270
  except Exception as e:
271
  logger.warning(f"Could not embed reel for search: {getattr(e, 'detail', e)}")
272
  return None
273
+ if len(vector) != EMBEDDING_DIMENSIONS:
274
+ # pgvector would reject the whole row, losing the reel along with the vector.
275
+ logger.warning("Embedding has %s dimensions, expected %s; not storing it. Check EMBEDDING_MODEL_NAME.",
276
+ len(vector), EMBEDDING_DIMENSIONS)
277
+ return None
278
+ return vector
279
 
280
 
281
  def _target_cluster_count(item_count: int) -> int:
test_pipeline.py CHANGED
@@ -24,6 +24,7 @@ client = TestClient(main.app)
24
 
25
  REEL_URL = "https://www.instagram.com/reel/Z/"
26
  LONG_TRANSCRIPT = " ".join(["word"] * 30) # clears the 15-word silent-hook guard
 
27
  EXTRACTION = ReelExtraction(
28
  core_topic="Topic", key_takeaway="Takeaway", action_items=["a"], tools_or_resources=["b"])
29
 
@@ -111,7 +112,7 @@ class FakeEmbeddingModel:
111
 
112
  def embed(self, texts):
113
  self.texts.extend(texts)
114
- return ([0.25, 0.5] for _ in texts)
115
 
116
 
117
  @pytest.fixture(autouse=True)
@@ -309,7 +310,7 @@ def test_embed_reel_uses_topic_takeaway_and_tools(embedding_model):
309
  vector = main.embed_reel({
310
  "core_topic": "AI email tools", "key_takeaway": "Automate replies",
311
  "tools_or_resources": ["Zapier", "ChatGPT"], "action_items": ["not embedded"]})
312
- assert vector == [0.25, 0.5]
313
  assert embedding_model.texts == ["AI email tools Automate replies Zapier, ChatGPT"]
314
 
315
 
@@ -323,6 +324,21 @@ def test_embed_reel_returns_none_when_model_unavailable():
323
  assert main.embed_reel({"core_topic": "AI"}) is None
324
 
325
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  def test_every_save_path_stores_an_embedding(monkeypatch, pipeline, embedding_model):
327
  saved = []
328
 
@@ -341,7 +357,7 @@ def test_every_save_path_stores_an_embedding(monkeypatch, pipeline, embedding_mo
341
  files = {"file": ("clip.mp3", io.BytesIO(b"audio"), "audio/mpeg")}
342
  assert client.post("/extract/file", files=files).status_code == 200
343
  assert main.worker_tick() is True
344
- assert saved == [[0.25, 0.5]] * 4
345
 
346
 
347
  def test_reel_still_saves_when_embedding_fails(monkeypatch):
@@ -368,7 +384,7 @@ def test_backfill_embeds_only_rows_with_content(monkeypatch, embedding_model):
368
  saved = {}
369
  monkeypatch.setattr(db, "set_embedding", lambda reel_id, embedding: saved.update({reel_id: embedding}))
370
  assert backfill_embeddings.backfill() == (2, 1)
371
- assert saved == {"a": [0.25, 0.5], "c": [0.25, 0.5]}
372
 
373
 
374
  def test_backfill_fails_loudly_without_a_model(monkeypatch):
 
24
 
25
  REEL_URL = "https://www.instagram.com/reel/Z/"
26
  LONG_TRANSCRIPT = " ".join(["word"] * 30) # clears the 15-word silent-hook guard
27
+ FAKE_VECTOR = [0.25] * 384 # same size as the saved_reels.embedding vector(384) column
28
  EXTRACTION = ReelExtraction(
29
  core_topic="Topic", key_takeaway="Takeaway", action_items=["a"], tools_or_resources=["b"])
30
 
 
112
 
113
  def embed(self, texts):
114
  self.texts.extend(texts)
115
+ return (FAKE_VECTOR for _ in texts)
116
 
117
 
118
  @pytest.fixture(autouse=True)
 
310
  vector = main.embed_reel({
311
  "core_topic": "AI email tools", "key_takeaway": "Automate replies",
312
  "tools_or_resources": ["Zapier", "ChatGPT"], "action_items": ["not embedded"]})
313
+ assert vector == FAKE_VECTOR
314
  assert embedding_model.texts == ["AI email tools Automate replies Zapier, ChatGPT"]
315
 
316
 
 
324
  assert main.embed_reel({"core_topic": "AI"}) is None
325
 
326
 
327
+ def test_embed_reel_never_raises_on_malformed_extraction(embedding_model):
328
+ assert main.embed_reel("a double-encoded string") is None
329
+ assert main.embed_reel(["a", "list"]) is None
330
+ assert main.embed_reel({"core_topic": "AI", "tools_or_resources": 5}) is None
331
+
332
+
333
+ def test_embed_reel_drops_a_vector_the_database_cannot_store(monkeypatch):
334
+ class WrongSizeModel:
335
+ def embed(self, texts):
336
+ return ([0.1, 0.2, 0.3] for _ in texts)
337
+
338
+ monkeypatch.setattr(main, "_get_embedding_model", lambda: WrongSizeModel())
339
+ assert main.embed_reel({"core_topic": "AI"}) is None
340
+
341
+
342
  def test_every_save_path_stores_an_embedding(monkeypatch, pipeline, embedding_model):
343
  saved = []
344
 
 
357
  files = {"file": ("clip.mp3", io.BytesIO(b"audio"), "audio/mpeg")}
358
  assert client.post("/extract/file", files=files).status_code == 200
359
  assert main.worker_tick() is True
360
+ assert saved == [FAKE_VECTOR] * 4
361
 
362
 
363
  def test_reel_still_saves_when_embedding_fails(monkeypatch):
 
384
  saved = {}
385
  monkeypatch.setattr(db, "set_embedding", lambda reel_id, embedding: saved.update({reel_id: embedding}))
386
  assert backfill_embeddings.backfill() == (2, 1)
387
+ assert saved == {"a": FAKE_VECTOR, "c": FAKE_VECTOR}
388
 
389
 
390
  def test_backfill_fails_loudly_without_a_model(monkeypatch):