mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
Wires edge-derived candidates into bare hybridSearch as a FOURTH RRF arm (relational-recall.ts): parse the original query -> scope-aware, confidence-gated seed resolution (drops fallback_slugify; never traverses from a guess) -> relationalFanout -> batch-hydrate, reinforcing each page's REAL canonical chunk (page-level key for chunkless entity pages) -> --explain attribution + fail-open audit row. Text-only (no-op in image mode); pure no-op for non-relational queries; rides every downstream stage (cosine, post-fusion boosts, dedup, reranker, autocut, token budget). Mode wiring: relationalRetrieval + relational_retrieval_depth knobs (conservative off; balanced/tokenmax on; depth 2), per-call thread-through in both bare + cached paths, KNOBS_HASH_VERSION 9->10 (rel=/reld=), config keys, modes-dashboard descriptions, and a `relational` param on the query op. Federation hardening (structural, engine-wide): the RRF/dedup key now carries source_id via a shared rrfKey() (fixes a latent cross-source collapse where same-slug pages in different sources merged), and the query cache scopes by a canonical source-set key (cacheScopeKey) so a federated read can't be served a single-source row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
71 lines
3.0 KiB
TypeScript
71 lines
3.0 KiB
TypeScript
/**
|
|
* RRF fusion key is source-aware (federation hardening).
|
|
*
|
|
* Pre-fix the RRF/dedup key was `slug:chunk_id`, which collapsed two
|
|
* same-slug pages living in different federated sources into one fusion
|
|
* entry — a cross-source recall bug. The key is now
|
|
* `(source_id, slug, chunk_id)`, matching `dedup.ts:pageKey`'s composite
|
|
* discipline at chunk granularity. These tests pin that behavior for both
|
|
* `rrfFusion` and `rrfFusionWeighted` and confirm single-source ranking is
|
|
* unchanged.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { rrfFusion, rrfFusionWeighted } from '../src/core/search/hybrid.ts';
|
|
import type { SearchResult } from '../src/core/types.ts';
|
|
|
|
function makeResult(overrides: Partial<SearchResult> = {}): SearchResult {
|
|
return {
|
|
slug: 'test-page',
|
|
page_id: 1,
|
|
title: 'Test',
|
|
type: 'concept',
|
|
chunk_text: 'unique chunk text',
|
|
chunk_source: 'timeline', // avoid compiled_truth 2x boost noise
|
|
chunk_id: 1,
|
|
chunk_index: 0,
|
|
score: 0.5,
|
|
stale: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('RRF key is source-aware', () => {
|
|
test('same slug + chunk_id in DIFFERENT sources do NOT collapse', () => {
|
|
const a = makeResult({ slug: 'people/alice', chunk_id: 1, source_id: 'team-a', chunk_text: 'x' });
|
|
const b = makeResult({ slug: 'people/alice', chunk_id: 1, source_id: 'team-b', chunk_text: 'y' });
|
|
const out = rrfFusion([[a, b]], 60);
|
|
expect(out.length).toBe(2);
|
|
expect(new Set(out.map(r => r.source_id))).toEqual(new Set(['team-a', 'team-b']));
|
|
});
|
|
|
|
test('same slug + chunk_id + SAME source DO collapse (reinforce across lists)', () => {
|
|
const a = makeResult({ slug: 'people/alice', chunk_id: 1, source_id: 'team-a', chunk_text: 'x' });
|
|
const b = makeResult({ slug: 'people/alice', chunk_id: 1, source_id: 'team-a', chunk_text: 'x' });
|
|
const out = rrfFusion([[a], [b]], 60);
|
|
expect(out.length).toBe(1);
|
|
});
|
|
|
|
test('single-source (no source_id → "default") ranking unchanged', () => {
|
|
const a = makeResult({ slug: 'a', chunk_id: 1, chunk_text: 'aaa' });
|
|
const b = makeResult({ slug: 'b', chunk_id: 2, chunk_text: 'bbb' });
|
|
const out = rrfFusion([[a, b]], 60);
|
|
expect(out.length).toBe(2);
|
|
expect(out[0].slug).toBe('a'); // rank-0 wins, as before the key change
|
|
});
|
|
|
|
test('chunkless rows (null chunk_id) stay distinct per source via text prefix', () => {
|
|
const a = makeResult({ slug: 'people/alice', chunk_id: undefined as unknown as number, source_id: 'team-a', chunk_text: 'alice page' });
|
|
const b = makeResult({ slug: 'people/alice', chunk_id: undefined as unknown as number, source_id: 'team-b', chunk_text: 'alice page' });
|
|
const out = rrfFusion([[a, b]], 60);
|
|
expect(out.length).toBe(2);
|
|
});
|
|
|
|
test('weighted RRF is also source-aware', () => {
|
|
const a = makeResult({ slug: 'people/alice', chunk_id: 1, source_id: 'team-a' });
|
|
const b = makeResult({ slug: 'people/alice', chunk_id: 1, source_id: 'team-b' });
|
|
const out = rrfFusionWeighted([{ list: [a, b], k: 60 }]);
|
|
expect(out.length).toBe(2);
|
|
});
|
|
});
|