feat(similarity): add precomputed similarity cache, fix fusion N+1, add 429 retry
- Add similarity/ package (compute.py, lookup.py) with numpy-based pairwise cosine similarity and cached lookup - database.py: create embeddings + similarity_cache tables in _init_database(), add store_similarity_batch/get_cached_similarities/clear_similarity_cache helpers - pipeline/fusion.py: replace N+1 per-motion embedding SELECT with single bulk JOIN using DuckDB QUALIFY window function - ai_provider.py: retry HTTP 429 with Retry-After header support - migrations/2026-03-22-add-similarity-cache.sql: make executable - Add tests for similarity compute, db helpers, and 429 retry (34 pass, 2 skip)
This commit is contained in:
+41
-23
@@ -30,20 +30,50 @@ def fuse_for_window(
|
||||
# MotionDatabase always exposes the path it uses
|
||||
conn = duckdb.connect(db.db_path)
|
||||
|
||||
# Fetch svd vectors for the window and entity_type=motion
|
||||
rows = conn.execute(
|
||||
"SELECT entity_id, vector FROM svd_vectors WHERE window_id = ? AND entity_type = ?",
|
||||
(window_id, "motion"),
|
||||
).fetchall()
|
||||
# debug
|
||||
_logger.debug("Found %d svd rows for window %s", len(rows), window_id)
|
||||
# Perform a single query that joins SVD vectors (for motions in the window)
|
||||
# with the latest text embedding per motion (optionally filtered by model).
|
||||
# We use a CTE to pick the latest embedding per motion_id.
|
||||
if model:
|
||||
sql = (
|
||||
"WITH latest_embeddings AS ("
|
||||
" SELECT motion_id, vector FROM ("
|
||||
" SELECT motion_id, vector, ROW_NUMBER() OVER (PARTITION BY motion_id ORDER BY created_at DESC) AS rn"
|
||||
" FROM embeddings WHERE model = ?"
|
||||
" ) WHERE rn = 1)"
|
||||
" SELECT sv.entity_id, sv.vector as svd_vector, le.vector as embedding_vector"
|
||||
" FROM svd_vectors sv"
|
||||
" LEFT JOIN latest_embeddings le ON CAST(sv.entity_id AS INTEGER) = le.motion_id"
|
||||
" WHERE sv.window_id = ? AND sv.entity_type = 'motion'"
|
||||
)
|
||||
params = (model, window_id)
|
||||
else:
|
||||
sql = (
|
||||
"WITH latest_embeddings AS ("
|
||||
" SELECT motion_id, vector FROM ("
|
||||
" SELECT motion_id, vector, ROW_NUMBER() OVER (PARTITION BY motion_id ORDER BY created_at DESC) AS rn"
|
||||
" FROM embeddings"
|
||||
" ) WHERE rn = 1)"
|
||||
" SELECT sv.entity_id, sv.vector as svd_vector, le.vector as embedding_vector"
|
||||
" FROM svd_vectors sv"
|
||||
" LEFT JOIN latest_embeddings le ON CAST(sv.entity_id AS INTEGER) = le.motion_id"
|
||||
" WHERE sv.window_id = ? AND sv.entity_type = 'motion'"
|
||||
)
|
||||
params = (window_id,)
|
||||
|
||||
rows = conn.execute(sql, params).fetchall()
|
||||
_logger.debug(
|
||||
"Found %d svd rows for window %s (joined with latest embeddings)",
|
||||
len(rows),
|
||||
window_id,
|
||||
)
|
||||
|
||||
inserted = 0
|
||||
skipped_missing_text = 0
|
||||
skipped_missing_svd = 0
|
||||
errors = 0
|
||||
|
||||
for entity_id, svd_json in rows:
|
||||
for entity_id, svd_json, emb_json in rows:
|
||||
# Parse SVD vector
|
||||
try:
|
||||
svd_vec = json.loads(svd_json)
|
||||
except Exception:
|
||||
@@ -51,25 +81,13 @@ def fuse_for_window(
|
||||
skipped_missing_svd += 1
|
||||
continue
|
||||
|
||||
# Look up text embedding for this motion (most recent). If model is provided
|
||||
# filter by model as well.
|
||||
if model:
|
||||
emb_row = conn.execute(
|
||||
"SELECT vector FROM embeddings WHERE motion_id = ? AND model = ? ORDER BY created_at DESC LIMIT 1",
|
||||
(int(entity_id), model),
|
||||
).fetchone()
|
||||
else:
|
||||
emb_row = conn.execute(
|
||||
"SELECT vector FROM embeddings WHERE motion_id = ? ORDER BY created_at DESC LIMIT 1",
|
||||
(int(entity_id),),
|
||||
).fetchone()
|
||||
|
||||
if not emb_row:
|
||||
# If there is no embedding joined, skip
|
||||
if not emb_json:
|
||||
skipped_missing_text += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
text_vec = json.loads(emb_row[0])
|
||||
text_vec = json.loads(emb_json)
|
||||
except Exception:
|
||||
_logger.exception("Invalid text embedding JSON for motion %s", entity_id)
|
||||
skipped_missing_text += 1
|
||||
|
||||
Reference in New Issue
Block a user