@@ -109,211 +109,6 @@ def __call__(self, candidate: OptimizerCandidate) -> float | None:
109109 return self .current_score ()
110110
111111
112- # ---------------------------------------------------------------------------
113- # P1-7 (GenAI lesson 15): heterogeneous held-out retrieval evaluation
114- # ---------------------------------------------------------------------------
115- #
116- # Cerebellum's built-in ``benchmark_run`` builds its QA set from the *indexed
117- # documents themselves* (query = content prefix, target = source_key) and
118- # scores hits by exact ``source_key`` string equality. Lesson 15 names both as
119- # weak-evaluation traps: a same-source eval set inflates scores (poor
120- # generalization), and exact-string scoring has zero tolerance for paraphrase.
121- # This evaluator fixes both on the DeepCode side:
122- #
123- # * **Held-out QA set** — queries come from sources *excluded* from the
124- # indexed store (:func:`split_held_out_qa`), so recall measures
125- # generalization, not self-consistency.
126- # * **Semantic scoring** — a hit matches when the *content* embedding is
127- # similar to the gold answer (default threshold), never by string equality.
128- # Without an embedder it degrades to exact-substring matching and reports
129- # ``weak=True`` so nobody mistakes it for a semantic score.
130-
131-
132- def _cosine_similarity (a : list [float ] | None , b : list [float ] | None ) -> float :
133- if not a or not b or len (a ) != len (b ):
134- return 0.0
135- import math
136-
137- dot = sum (x * y for x , y in zip (a , b ))
138- na = math .sqrt (sum (x * x for x in a ))
139- nb = math .sqrt (sum (y * y for y in b ))
140- if not na or not nb :
141- return 0.0
142- return dot / (na * nb )
143-
144-
145- def split_held_out_qa (
146- entries : list [dict [str , Any ]],
147- hold_out_sources : set [str ],
148- * ,
149- query_chars : int = 60 ,
150- ) -> tuple [list [dict [str , Any ]], set [str ]]:
151- """Split scored/indexable entries into a held-out QA set + indexed sources.
152-
153- ``entries`` are ``{"content", "source", ...}`` rows. Every entry whose
154- ``source`` is in ``hold_out_sources`` becomes an evaluation question
155- (query = content prefix, gold = full content); those sources must NOT be
156- present in the store the evaluator searches, or the eval is contaminated
157- (lesson 15: same-source eval inflates scores). Returns
158- ``(qa_set, indexed_sources)`` where ``indexed_sources`` = the sources that
159- stay in the index.
160- """
161- qa : list [dict [str , Any ]] = []
162- indexed : set [str ] = set ()
163- for entry in entries or []:
164- if not isinstance (entry , dict ):
165- continue
166- content = str (entry .get ("content" , "" )).strip ()
167- source = str (entry .get ("source" , "" ) or "" )
168- if not content :
169- continue
170- if source in hold_out_sources :
171- query = content [:query_chars ] + ("…" if len (content ) > query_chars else "" )
172- qa .append ({"query" : query , "gold" : content , "source" : source })
173- else :
174- indexed .add (source )
175- return qa , indexed
176-
177-
178- def evaluate_retrieval (
179- qa_set : list [dict [str , Any ]],
180- * ,
181- search_fn : Any ,
182- embed_fn : Any | None = None ,
183- top_k : int = 5 ,
184- similarity_threshold : float = 0.45 ,
185- ) -> dict [str , Any ]:
186- """Held-out retrieval evaluation with semantic scoring (P1-7).
187-
188- Parameters
189- ----------
190- qa_set:
191- ``[{"query", "gold", ...}]`` — queries heterogeneously sourced from
192- documents NOT in the searched index.
193- search_fn:
194- ``(query, limit) -> [{"content", ...}]`` — the retrieval channel
195- (e.g. cerebellum ``memory_search`` semantic_hits adapter).
196- embed_fn:
197- ``(text) -> list[float] | None`` — semantic embedder. When None,
198- scoring degrades to exact-substring matching and the result carries
199- ``weak=True`` (an explicit warning, not a silent downgrade).
200- similarity_threshold:
201- Minimum content-embedding cosine for a hit to count as the gold.
202-
203- Returns metrics ``{queries, recall@1, recall@k, mrr, weak, per_query}`` —
204- same shape family as cerebellum's ``benchmark_run`` so callers can compare.
205- """
206- results : dict [str , Any ] = {
207- "queries" : len (qa_set ),
208- "top_k" : top_k ,
209- "recall@1" : 0.0 ,
210- f"recall@{ top_k } " : 0.0 ,
211- "mrr" : 0.0 ,
212- "weak" : embed_fn is None ,
213- "per_query" : [],
214- }
215- if not qa_set :
216- return results
217-
218- gold_vectors : list [list [float ] | None ] = []
219- if embed_fn is not None :
220- for item in qa_set :
221- try :
222- gold_vectors .append (embed_fn (str (item .get ("gold" , "" ))))
223- except Exception : # noqa: BLE001 - a bad embed must not kill the eval
224- gold_vectors .append (None )
225-
226- hits = 0
227- hits_at_1 = 0
228- mrr_sum = 0.0
229- for index , item in enumerate (qa_set ):
230- query = str (item .get ("query" , "" ))
231- gold = str (item .get ("gold" , "" ))
232- try :
233- retrieved = search_fn (query , top_k ) or []
234- except Exception : # noqa: BLE001 - retrieval failure counts as a miss
235- retrieved = []
236- rank = 0
237- for position , hit in enumerate (retrieved , start = 1 ):
238- content = str ((hit or {}).get ("content" , "" )).strip ()
239- if not content :
240- continue
241- if embed_fn is not None :
242- try :
243- sim = _cosine_similarity (
244- gold_vectors [index ], embed_fn (content )
245- )
246- except Exception : # noqa: BLE001
247- sim = 0.0
248- if sim >= similarity_threshold :
249- rank = position
250- break
251- elif gold and gold in content :
252- rank = position
253- break
254- if rank :
255- hits += 1
256- if rank == 1 :
257- hits_at_1 += 1
258- mrr_sum += 1.0 / rank
259- results ["per_query" ].append ({"query" : query , "rank" : rank })
260-
261- n = len (qa_set )
262- results ["recall@1" ] = round (hits_at_1 / n , 3 )
263- results [f"recall@{ top_k } " ] = round (hits / n , 3 )
264- results ["mrr" ] = round (mrr_sum / n , 3 )
265- return results
266-
267-
268- def cerebellum_search_adapter (
269- db_path : str | Path | None = None ,
270- ) -> Any :
271- """Adapter: cerebellum ``memory_search`` semantic_hits → search_fn contract.
272-
273- Returns ``(query, limit) -> [{"content", "similarity", ...}]`` (the raw
274- semantic hits), or an always-empty callable when cerebellum is missing —
275- evaluation must never crash on a missing component.
276- """
277-
278- def _search (query : str , limit : int ) -> list [dict [str , Any ]]:
279- try :
280- mod = _import_cerebellum ()
281- mem = mod .CerebellumMemory (db_path or mod .DEFAULT_DB )
282- result = mem .search (query , limit = limit )
283- return result .get ("semantic_hits" , []) or []
284- except Exception : # noqa: BLE001 - evaluation must never crash
285- logger .debug ("cerebellum search adapter failed" , exc_info = True )
286- return []
287-
288- return _search
289-
290-
291- def cerebellum_embed_adapter (
292- db_path : str | Path | None = None ,
293- ) -> Any | None :
294- """Adapter: cerebellum ``ollama_embed`` → embed_fn contract, or None.
295-
296- ``None`` means no embedder is available (cerebellum missing/unimportable);
297- callers should then treat the evaluation as ``weak=True`` rather than
298- fabricating a semantic score. A returned callable that yields None per
299- call means the embedder is present but failed that call.
300- """
301- try :
302- _import_cerebellum ()
303- except Exception : # noqa: BLE001 - missing cerebellum is a soft condition
304- return None
305-
306- def _embed (text : str ) -> list [float ] | None :
307- try :
308- mod = _import_cerebellum ()
309- vectors = mod .ollama_embed ([text ])
310- return vectors [0 ] if vectors else None
311- except Exception : # noqa: BLE001
312- return None
313-
314- return _embed
315-
316-
317112# ---------------------------------------------------------------------------
318113# Skill optimizer: proposals → candidates → apply → benchmark → accept/rollback
319114# ---------------------------------------------------------------------------
@@ -511,8 +306,4 @@ def run_once(self, *, min_delta: float = 0.0) -> list[SkillOptimizationOutcome]:
511306 "CerebellumBenchmarkEvaluator" ,
512307 "CerebellumSkillOptimizer" ,
513308 "SkillOptimizationOutcome" ,
514- "cerebellum_embed_adapter" ,
515- "cerebellum_search_adapter" ,
516- "evaluate_retrieval" ,
517- "split_held_out_qa" ,
518309]
0 commit comments