mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
* feat(search): autocut — score-discontinuity result-sizing on the rerank separatrix Cut the ranked set at the cross-encoder rerank-score cliff instead of a fixed top-K. Default-ON in reranked modes (balanced/tokenmax), no-op without a reranker. New pure src/core/search/autocut.ts; mode.ts knobs + reranker_top_n_in = searchLimit (no unscored tail); query op autocut param; --explain + glossary. * test(search): autocut pure-fn, agent-surface, behavioral + precision/recall eval gate Adds autocut.test.ts, query-op-autocut.test.ts, autocut-integration.serial.test.ts (IRON-RULE behavioral via rerankerFn seam), autocut-eval.test.ts (in-repo precision-lift-without-recall-regression gate). Updates existing knobsHash/bundle pins to v=7 + reranker_top_n_in. * chore: version + changelog + docs for autocut (v0.41.34.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(search): autocut preserves alias-hop exact matches + cache-HIT meta (codex P1/P2) P1: applyAliasHop injects the canonical page after reranking (no rerank_score); autocut would drop it when cutting on the scored set. applyAutocut gains an optional preserve predicate; hybrid passes r => r.alias_hit === true. P2: cache-HIT cachedMeta now carries autocut/adaptive_return/mode/embedding_column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version to v0.42.3.0 (autocut wave) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: PR titles lead with the version (IRON RULE in CLAUDE.md) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
// v0.42 Type Unification (T32) — alias_resolved search boost stage.
|
|
//
|
|
// Coverage: pages whose slug is a canonical_slug in slug_aliases get 1.05x
|
|
// score multiplier; non-alias-canonical pages unchanged; stage stamps
|
|
// alias_resolved_boost field for --explain; KNOBS_HASH_VERSION bumped.
|
|
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
|
|
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
|
import { resetPgliteState } from './helpers/reset-pglite.ts';
|
|
import { runPostFusionStages, type PostFusionOpts } from '../src/core/search/hybrid.ts';
|
|
import { KNOBS_HASH_VERSION } from '../src/core/search/mode.ts';
|
|
import type { SearchResult } from '../src/core/types.ts';
|
|
|
|
let engine: PGLiteEngine;
|
|
|
|
beforeAll(async () => {
|
|
engine = new PGLiteEngine();
|
|
await engine.connect({});
|
|
await engine.initSchema();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await engine.disconnect();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await resetPgliteState(engine);
|
|
});
|
|
|
|
const noopPostFusionOpts: PostFusionOpts = {
|
|
applyBacklinks: false,
|
|
salience: 'off',
|
|
recency: 'off',
|
|
graphSignalsEnabled: false,
|
|
};
|
|
|
|
describe('alias_resolved boost stage', () => {
|
|
it('applies 1.05x multiplier to pages that are canonicals of aliases', async () => {
|
|
// Insert an alias pointing at canonical-page
|
|
await engine.executeRaw(
|
|
`INSERT INTO slug_aliases (source_id, alias_slug, canonical_slug) VALUES ('default', 'old-name', 'canonical-page')`,
|
|
);
|
|
const results: SearchResult[] = [
|
|
{
|
|
slug: 'canonical-page', source_id: 'default', score: 1.0,
|
|
chunk_id: 1, page_id: 1, chunk_text: '', chunk_index: 0,
|
|
title: 'Canonical', type: 'concept' as never, slug_lower: 'canonical-page',
|
|
} as unknown as SearchResult,
|
|
{
|
|
slug: 'plain-page', source_id: 'default', score: 1.0,
|
|
chunk_id: 2, page_id: 2, chunk_text: '', chunk_index: 0,
|
|
title: 'Plain', type: 'concept' as never, slug_lower: 'plain-page',
|
|
} as unknown as SearchResult,
|
|
];
|
|
await runPostFusionStages(engine, results, noopPostFusionOpts);
|
|
// canonical-page gets 1.05x boost
|
|
expect(results[0].score).toBeCloseTo(1.05, 5);
|
|
expect(results[0].alias_resolved_boost).toBe(1.05);
|
|
// plain-page unchanged
|
|
expect(results[1].score).toBeCloseTo(1.0, 5);
|
|
expect(results[1].alias_resolved_boost).toBeUndefined();
|
|
});
|
|
|
|
it('does not boost when no aliases exist', async () => {
|
|
const results: SearchResult[] = [{
|
|
slug: 'plain', source_id: 'default', score: 1.0,
|
|
chunk_id: 1, page_id: 1, chunk_text: '', chunk_index: 0,
|
|
title: 'p', type: 'concept' as never, slug_lower: 'plain',
|
|
} as unknown as SearchResult];
|
|
await runPostFusionStages(engine, results, noopPostFusionOpts);
|
|
expect(results[0].score).toBeCloseTo(1.0, 5);
|
|
expect(results[0].alias_resolved_boost).toBeUndefined();
|
|
});
|
|
|
|
it('is source-scoped (F9): alias in source A does not boost in source B', async () => {
|
|
await engine.executeRaw(`INSERT INTO sources (id, name) VALUES ('alt', 'alt') ON CONFLICT DO NOTHING`);
|
|
await engine.executeRaw(
|
|
`INSERT INTO slug_aliases (source_id, alias_slug, canonical_slug) VALUES ('alt', 'old', 'shared')`,
|
|
);
|
|
// Same slug, different source — should NOT be boosted (alias is in 'alt')
|
|
const results: SearchResult[] = [{
|
|
slug: 'shared', source_id: 'default', score: 1.0,
|
|
chunk_id: 1, page_id: 1, chunk_text: '', chunk_index: 0,
|
|
title: 's', type: 'concept' as never, slug_lower: 'shared',
|
|
} as unknown as SearchResult];
|
|
await runPostFusionStages(engine, results, noopPostFusionOpts);
|
|
expect(results[0].alias_resolved_boost).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('KNOBS_HASH_VERSION', () => {
|
|
it('bumped to 6 to invalidate caches across v0.42 boost stage addition', () => {
|
|
expect(KNOBS_HASH_VERSION).toBe(8);
|
|
});
|
|
});
|