@@ -188,6 +188,8 @@ class Embeddings(BaseModel): # type: ignore
188188
189189 embeddings_url = sqla .Column (sqla .String , nullable = True , default = None )
190190 embeddings_content = sqla .Column (sqla .String , nullable = True , default = None )
191+ embedding_model_id = sqla .Column (sqla .String , nullable = True , default = None )
192+ embedding_dim = sqla .Column (sqla .Integer , nullable = True , default = None )
191193
192194
193195class GapAnalysisResults (BaseModel ):
@@ -2286,6 +2288,18 @@ def add_embedding(
22862288 For nodes, ``embeddings_url`` is the resolved URL used for fetch/embed alignment
22872289 (may include a fragment). When ``None``, defaults to ``db_object.link`` (importer hyperlink).
22882290 """
2291+ expected_dim_raw = (os .environ .get ("CRE_EMBED_EXPECTED_DIM" , "" ) or "" ).strip ()
2292+ embedding_model_id = (
2293+ os .environ .get ("CRE_EMBED_MODEL" , "" ) or ""
2294+ ).strip () or "openai/text-embedding-3-small"
2295+ embedding_dim = len (embeddings )
2296+ if expected_dim_raw :
2297+ expected_dim = int (expected_dim_raw )
2298+ if len (embeddings ) != expected_dim :
2299+ raise ValueError (
2300+ f"embedding dimension mismatch for { db_object .id } : "
2301+ f"expected { expected_dim } , got { len (embeddings )} "
2302+ )
22892303 existing = self .get_embedding (db_object .id )
22902304 embeddings_str = "," .join ([str (e ) for e in embeddings ])
22912305 resolved_node_url : Optional [str ] = None
@@ -2302,6 +2316,8 @@ def add_embedding(
23022316 cre_id = db_object .id ,
23032317 doc_type = cre_defs .Credoctypes .CRE .value ,
23042318 embeddings_content = embedding_text ,
2319+ embedding_model_id = embedding_model_id ,
2320+ embedding_dim = embedding_dim ,
23052321 )
23062322 else :
23072323 emb = Embeddings (
@@ -2310,6 +2326,8 @@ def add_embedding(
23102326 doc_type = db_object .ntype ,
23112327 embeddings_content = embedding_text ,
23122328 embeddings_url = resolved_node_url ,
2329+ embedding_model_id = embedding_model_id ,
2330+ embedding_dim = embedding_dim ,
23132331 )
23142332 self .session .add (emb )
23152333 self .session .commit ()
@@ -2318,6 +2336,8 @@ def add_embedding(
23182336 logger .debug (f"knew of embedding for object { db_object .id } ,updating" )
23192337 existing [0 ].embeddings = embeddings_str
23202338 existing [0 ].embeddings_content = embedding_text
2339+ existing [0 ].embedding_model_id = embedding_model_id
2340+ existing [0 ].embedding_dim = embedding_dim
23212341 if doctype != cre_defs .Credoctypes .CRE :
23222342 if embeddings_url is not None :
23232343 existing [0 ].embeddings_url = embeddings_url
@@ -2327,6 +2347,57 @@ def add_embedding(
23272347
23282348 return existing
23292349
2350+ def assert_embedding_contract (
2351+ self ,
2352+ * ,
2353+ expected_model_id : Optional [str ],
2354+ expected_dim : Optional [int ],
2355+ ) -> None :
2356+ """
2357+ Validate persisted embedding metadata consistency.
2358+
2359+ - Fails when multiple dimensions are stored.
2360+ - Fails when metadata is missing or mismatched against expected model/dimension.
2361+ """
2362+ rows = self .session .query (
2363+ Embeddings .embedding_dim , Embeddings .embedding_model_id
2364+ ).all ()
2365+ if not rows :
2366+ return
2367+
2368+ dims = {int (r [0 ]) for r in rows if r [0 ] is not None }
2369+ model_ids = {str (r [1 ]) for r in rows if r [1 ]}
2370+ has_missing_dim = any (r [0 ] is None for r in rows )
2371+ has_missing_model = any (not r [1 ] for r in rows )
2372+
2373+ if len (dims ) > 1 :
2374+ raise RuntimeError (
2375+ f"multiple embedding dimensions detected in DB: { sorted (dims )} "
2376+ )
2377+ if len (model_ids ) > 1 :
2378+ raise RuntimeError (
2379+ f"multiple embedding models detected in DB: { sorted (model_ids )} "
2380+ )
2381+
2382+ if has_missing_dim or has_missing_model :
2383+ raise RuntimeError (
2384+ "embedding metadata missing in DB; run metadata migration/backfill"
2385+ )
2386+
2387+ if expected_dim is not None and dims :
2388+ db_dim = next (iter (dims ))
2389+ if db_dim != expected_dim :
2390+ raise RuntimeError (
2391+ f"DB embedding dim { db_dim } does not match expected dim { expected_dim } "
2392+ )
2393+
2394+ if expected_model_id and model_ids :
2395+ db_model = next (iter (model_ids ))
2396+ if db_model != expected_model_id :
2397+ raise RuntimeError (
2398+ f"DB embedding model { db_model } does not match expected model { expected_model_id } "
2399+ )
2400+
23302401 def gap_analysis_exists (self , cache_key ) -> bool :
23312402 row = (
23322403 self .session .query (GapAnalysisResults )
0 commit comments