55f9ee3cb0
hit@5 68.6% -> 74.3%, phrase 71.4% -> 100%, MRR@10 0.461 -> 0.502, latency p50 0.97x. Zero losses, zero run-to-run drift on both sides. 2 queries moved against a 6-query noise floor: NO MEASURABLE DIFFERENCE by the harness's own test (McNemar exact p=0.50). Unaddressable records in returned slots: 57 -> 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
143 lines
6.4 KiB
Python
143 lines
6.4 KiB
Python
import numpy as np, json, urllib.request, collections, math, re, sys, time
|
|
SP="/private/tmp/claude-501/-Users-timlingo/82369039-a20e-4b5a-8a5e-28234a57b996/scratchpad"
|
|
EV="/Users/timlingo/Development/neuron-technologies/_wt-semseed/tools/retrieval-eval/"
|
|
np.seterr(all='ignore')
|
|
t0=time.time()
|
|
M=np.load(SP+'/emb.npy'); eids=open(SP+'/ids.txt',encoding='utf-8',errors='surrogateescape').read().split('\n')
|
|
eidx={k:i for i,k in enumerate(eids)}
|
|
d=json.load(open('/Users/timlingo/neuron-memory-backups/snapshot-pre-repair-20260806.json',encoding='utf-8',errors='surrogateescape'))
|
|
print("loaded corpus %.1fs"%(time.time()-t0),file=sys.stderr)
|
|
STRUCT={"identity","contains","superseded_by","references","embodies","demonstrated_by","canonical-self","depends_on","currently_holds","activates"}
|
|
adj=collections.defaultdict(list)
|
|
for e in d['edges']:
|
|
if e.get('relation') not in STRUCT: continue
|
|
w=float(e.get('weight') or 0.0)
|
|
adj[e['from_id']].append((e['to_id'],w)); adj[e['to_id']].append((e['from_id'],w))
|
|
nodes=d['nodes']
|
|
N={n['id']:n for n in nodes}
|
|
PRINT=re.compile(r'^[\x20-\x7e]+$')
|
|
ids=[]; hay=[]; dl=[]; sal=[]; addressable=[]
|
|
for n in nodes:
|
|
i=n.get('id') or ''
|
|
h=((n.get('content') or '')+'\x00'+(n.get('label') or '')+'\x00'+(n.get('tags') or '')).lower()
|
|
ids.append(i); hay.append(h); dl.append(len(h)); sal.append(float(n.get('salience') or 0.0))
|
|
addressable.append(bool(PRINT.match(i)))
|
|
del d
|
|
NN=len(ids); avgdl=sum(dl)/NN
|
|
print("nodes=%d avgdl=%.0f addressable=%d %.1fs"%(NN,avgdl,sum(addressable),time.time()-t0),file=sys.stderr)
|
|
gold={q['id']:q for q in json.load(open(EV+"gold_set.json"))['queries']}
|
|
LEXMAIN={r['id']:r['returned'] for r in json.load(open(EV+"results-main.json"),) ['rows']} if False else {r['id']:r['returned'] for r in json.load(open(EV+"results-main.json",encoding='utf-8',errors='surrogateescape'))['rows']}
|
|
CACHE={}
|
|
def emb(t):
|
|
if t in CACHE: return CACHE[t]
|
|
b=json.dumps({"model":"nomic-embed-text","prompt":t}).encode()
|
|
r=urllib.request.Request("http://127.0.0.1:11434/api/embeddings",data=b,headers={"Content-Type":"application/json"})
|
|
v=np.array(json.load(urllib.request.urlopen(r,timeout=60))["embedding"],dtype=np.float32)
|
|
v=v/(np.linalg.norm(v)+1e-9); CACHE[t]=v; return v
|
|
K1,B=1.2,0.75
|
|
def lexleg(query, mode, guard, lim=10):
|
|
toks=[]
|
|
for w in query.split():
|
|
wl=w.lower()
|
|
if wl not in toks: toks.append(wl)
|
|
nt=len(toks)
|
|
masks=[]; df=[0]*nt
|
|
for i in range(NN):
|
|
if guard and not addressable[i]: continue
|
|
h=hay[i]; m=0; sc=0
|
|
for t in range(nt):
|
|
if toks[t] in h: m|=(1<<t); sc+=1; df[t]+=1
|
|
if sc: masks.append((i,m,sc))
|
|
if mode=='tokcount':
|
|
masks.sort(key=lambda x:(-x[2], -sal[x[0]]))
|
|
return [ids[i] for i,m,sc in masks[:lim]]
|
|
idf=[math.log(1.0+(NN-df[t]+0.5)/(df[t]+0.5)) for t in range(nt)]
|
|
scored=[]
|
|
for i,m,sc in masks:
|
|
norm=1.0-B+B*dl[i]/avgdl
|
|
s=0.0
|
|
for t in range(nt):
|
|
if m&(1<<t): s+=idf[t]*(K1+1.0)/(1.0+K1*norm)
|
|
scored.append((s,i))
|
|
scored.sort(key=lambda x:(-x[0], -sal[x[1]]))
|
|
return [ids[i] for s,i in scored[:lim]]
|
|
FIRE=0.02; DECAY=0.7; DEPTH=2; SEED_MIN=0.60; ASSOC_MAX=64
|
|
def assoc(seeds, s):
|
|
act={x:1.0 for x in seeds}; seen={x:2 for x in seeds}
|
|
Q=[(x,0) for x in seeds]; h=0
|
|
while h<len(Q):
|
|
cur,hop=Q[h]; h+=1
|
|
if hop>=DEPTH: continue
|
|
p=act[cur]
|
|
for oid,w in adj.get(cur,()):
|
|
n=N.get(oid)
|
|
if not n or n.get('node_type') in ('Tag','InternalStateEvent'): continue
|
|
na=p*w*DECAY*float(n.get('salience') or 0.0)
|
|
if na<FIRE: continue
|
|
if oid in seen and na<=act.get(oid,0): continue
|
|
act[oid]=na
|
|
if oid not in seen: seen[oid]=1
|
|
Q.append((oid,hop+1))
|
|
out=[]
|
|
for k,v in seen.items():
|
|
if v!=1 or k not in eidx: continue
|
|
c=float(s[eidx[k]])
|
|
if c<=0: continue
|
|
out.append((c,k))
|
|
out.sort(reverse=True)
|
|
return [k for c,k in out[:ASSOC_MAX]]
|
|
def inter3(L,S,A,lim=10):
|
|
out=[]; li=si=ai=0
|
|
while len(out)<lim and (li<len(L) or si<len(S) or ai<len(A)):
|
|
if li<len(L):
|
|
if L[li] not in out: out.append(L[li])
|
|
li+=1
|
|
if len(out)>=lim: break
|
|
if si<len(S):
|
|
if S[si] not in out: out.append(S[si])
|
|
si+=1
|
|
if len(out)>=lim: break
|
|
if ai<len(A):
|
|
if A[ai] not in out: out.append(A[ai])
|
|
ai+=1
|
|
return out
|
|
def run(mode, guard, use_main_lex=False):
|
|
res={}; legs={}
|
|
for qid,q in gold.items():
|
|
v=emb(q['query']); s=M@v; s[~np.isfinite(s)]=-1
|
|
L = LEXMAIN[qid][:10] if use_main_lex else lexleg(q['query'], mode, guard)
|
|
ordr=np.argsort(-s)
|
|
S=[eids[j] for j in ordr[:10] if s[j]>SEED_MIN]
|
|
if guard: S=[x for x in S if PRINT.match(x or '')]
|
|
seeds=[x for x in L[:3] if x in N]
|
|
seeds=seeds+[eids[j] for j in ordr[:8] if eids[j] in N and eids[j] not in seeds and (not guard or PRINT.match(eids[j] or ''))]
|
|
A=assoc(seeds,s) if seeds else []
|
|
if guard: A=[x for x in A if PRINT.match(x or '')]
|
|
res[qid]=inter3(L,S,A); legs[qid]=(L,S,A)
|
|
return res,legs
|
|
def score(res,label,verbose=False):
|
|
det={}
|
|
for qid,q in gold.items():
|
|
out=res[qid][:5]
|
|
if q['category']=='nonsense': ok=(len(res[qid])==0)
|
|
elif q['category']=='superseded':
|
|
must=q.get('must_outrank') or {}; ok=False
|
|
for good,bad in (must.items() if isinstance(must,dict) else []):
|
|
ok = good in res[qid] and (bad not in res[qid] or res[qid].index(good)<res[qid].index(bad))
|
|
if not must: ok=any(r in out for r in q['relevant'])
|
|
else: ok=any(r in out for r in q['relevant'])
|
|
det[qid]=ok
|
|
print("%-28s outcome-true=%d/38"%(label,sum(det.values())))
|
|
return det
|
|
if __name__=="__main__":
|
|
base,_=run('tokcount',False,use_main_lex=True); b=score(base,'BASELINE semseed(real lex)')
|
|
variants=[('tokcount',False,'replica: tokcount,noguard'),
|
|
('tokcount',True ,'A: tokcount + idguard'),
|
|
('bm25', False,'B: bm25 only'),
|
|
('bm25', True ,'C: bm25 + idguard')]
|
|
dets={}
|
|
for m,g,lab in variants:
|
|
r,_=run(m,g); dets[lab]=score(r,lab)
|
|
dd=[q for q in gold if dets[lab][q]!=b[q]]
|
|
print(" vs BASELINE moved=%d gains=%s losses=%s"%(len(dd),[q for q in dd if dets[lab][q]],[q for q in dd if not dets[lab][q]]))
|