Files
gbrain/test/embed.test.ts
T
Garry TanandClaude Opus 4.7 27762abb7a Merge origin/master into garrytan/gbrain-evals
Master landed significant work since this branch was cut (v0.15.x → v0.16.x →
v0.17.0 gbrain dream + runCycle → v0.18.0 multi-source brains → v0.18.1 RLS
hardening). Bumped this branch's version from the claimed 0.18.0 to 0.19.0
because master already owns 0.18.x.

Conflicts resolved:
- VERSION: 0.19.0 (was 0.18.0 on HEAD vs 0.18.1 on master)
- package.json: 0.19.0, kept all 11 eval-facing exports, merged master's
  typescript devDep + postinstall script + test script (typecheck added)
- src/core/types.ts: union of both PageType additions. Master had added
  `meeting | note`; this branch added `email | slack | calendar-event`
  for inbox/chat/calendar ingest. Final enum carries all five.
- CHANGELOG.md: renumbered the BrainBench-extraction entry to 0.19.0 and
  placed it above master's 0.18.1 RLS entry. Tweaked copy ("In v0.17 it
  lived inside this repo" → "Previously it lived inside this repo") to
  stop implying a specific version that never shipped.
- CLAUDE.md: adjusted "BrainBench in a sibling repo" heading from
  (v0.18+) → (v0.19+).
- docs/benchmarks/2026-04-18-minions-vs-openclaw-production.md:
  resolved modify-vs-delete conflict in favor of delete (the extraction).
- scripts/llms-config.ts: dropped the docs/benchmarks/ entry (directory
  no longer exists here; lives in gbrain-evals).
- llms.txt / llms-full.txt: regenerated after the config change.
- bun.lock: accepted master's (master already dropped pdf-parse as a
  drive-by; aligned with our removal).

Tests: 2094 pass, 236 skip, 18 fail. Spot-checked failures — build-llms,
dream, orphans tests all pass in isolation. Failures reproduce only under
full-suite parallel load and are pre-existing master flakiness (matches the
graph-quality flake noted in the earlier summary). Not merge-introduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:45:02 -07:00

256 lines
9.9 KiB
TypeScript

import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test';
import type { BrainEngine } from '../src/core/engine.ts';
// Mock the embedding module BEFORE importing runEmbed, so runEmbed picks up
// the mocked embedBatch. We track max concurrent invocations via a counter
// that increments on entry and decrements when the mock resolves.
let activeEmbedCalls = 0;
let maxConcurrentEmbedCalls = 0;
let totalEmbedCalls = 0;
mock.module('../src/core/embedding.ts', () => ({
embedBatch: async (texts: string[]) => {
activeEmbedCalls++;
totalEmbedCalls++;
if (activeEmbedCalls > maxConcurrentEmbedCalls) {
maxConcurrentEmbedCalls = activeEmbedCalls;
}
// Simulate API latency so concurrent workers actually overlap.
await new Promise(r => setTimeout(r, 30));
activeEmbedCalls--;
return texts.map(() => new Float32Array(1536));
},
}));
// Import AFTER mocking.
const { runEmbed } = await import('../src/commands/embed.ts');
// Proxy-based mock engine that matches test/import-file.test.ts pattern.
function mockEngine(overrides: Partial<Record<string, any>> = {}): BrainEngine {
const calls: { method: string; args: any[] }[] = [];
const track = (method: string) => (...args: any[]) => {
calls.push({ method, args });
if (overrides[method]) return overrides[method](...args);
return Promise.resolve(null);
};
const engine = new Proxy({} as any, {
get(_, prop: string) {
if (prop === '_calls') return calls;
if (overrides[prop]) return overrides[prop];
return track(prop);
},
});
return engine;
}
beforeEach(() => {
activeEmbedCalls = 0;
maxConcurrentEmbedCalls = 0;
totalEmbedCalls = 0;
});
afterEach(() => {
delete process.env.GBRAIN_EMBED_CONCURRENCY;
});
describe('runEmbed --all (parallel)', () => {
test('runs embedBatch calls concurrently across pages', async () => {
const NUM_PAGES = 20;
const pages = Array.from({ length: NUM_PAGES }, (_, i) => ({ slug: `page-${i}` }));
// Each page has one chunk without an embedding (stale).
const chunksBySlug = new Map(
pages.map(p => [
p.slug,
[{ chunk_index: 0, chunk_text: `text for ${p.slug}`, chunk_source: 'compiled_truth', embedded_at: null, token_count: 4 }],
]),
);
const engine = mockEngine({
listPages: async () => pages,
getChunks: async (slug: string) => chunksBySlug.get(slug) || [],
upsertChunks: async () => {},
});
process.env.GBRAIN_EMBED_CONCURRENCY = '10';
await runEmbed(engine, ['--all']);
expect(totalEmbedCalls).toBe(NUM_PAGES);
// Concurrency actually happened.
expect(maxConcurrentEmbedCalls).toBeGreaterThan(1);
// And stayed within the configured limit.
expect(maxConcurrentEmbedCalls).toBeLessThanOrEqual(10);
});
test('respects GBRAIN_EMBED_CONCURRENCY=1 (serial)', async () => {
const pages = Array.from({ length: 5 }, (_, i) => ({ slug: `page-${i}` }));
const chunksBySlug = new Map(
pages.map(p => [
p.slug,
[{ chunk_index: 0, chunk_text: `text ${p.slug}`, chunk_source: 'compiled_truth', embedded_at: null, token_count: 4 }],
]),
);
const engine = mockEngine({
listPages: async () => pages,
getChunks: async (slug: string) => chunksBySlug.get(slug) || [],
upsertChunks: async () => {},
});
process.env.GBRAIN_EMBED_CONCURRENCY = '1';
await runEmbed(engine, ['--all']);
expect(totalEmbedCalls).toBe(5);
expect(maxConcurrentEmbedCalls).toBe(1);
});
test('skips pages whose chunks are all already embedded when --stale', async () => {
const pages = [{ slug: 'fresh' }, { slug: 'stale' }];
const chunksBySlug = new Map<string, any[]>([
['fresh', [{ chunk_index: 0, chunk_text: 'hi', chunk_source: 'compiled_truth', embedded_at: '2026-01-01', token_count: 1 }]],
['stale', [{ chunk_index: 0, chunk_text: 'hi', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 }]],
]);
const engine = mockEngine({
listPages: async () => pages,
getChunks: async (slug: string) => chunksBySlug.get(slug) || [],
upsertChunks: async () => {},
});
process.env.GBRAIN_EMBED_CONCURRENCY = '5';
await runEmbed(engine, ['--stale']);
// Only the stale page triggers an embedBatch call.
expect(totalEmbedCalls).toBe(1);
});
});
// ────────────────────────────────────────────────────────────────
// runEmbedCore dry-run mode (v0.17 regression guard)
// ────────────────────────────────────────────────────────────────
describe('runEmbedCore --dry-run never calls the embedding model', () => {
test('dry-run --all with stale chunks: no embedBatch calls, accurate would_embed', async () => {
const { runEmbedCore } = await import('../src/commands/embed.ts');
const pages = Array.from({ length: 3 }, (_, i) => ({ slug: `page-${i}` }));
// All 3 pages have 2 stale chunks each (none embedded).
const chunksBySlug = new Map<string, any[]>(
pages.map(p => [
p.slug,
[
{ chunk_index: 0, chunk_text: 'a', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
{ chunk_index: 1, chunk_text: 'b', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
],
]),
);
const upserts: string[] = [];
const engine = mockEngine({
listPages: async () => pages,
getChunks: async (slug: string) => chunksBySlug.get(slug) || [],
upsertChunks: async (slug: string) => { upserts.push(slug); },
});
const result = await runEmbedCore(engine, { stale: true, dryRun: true });
// No OpenAI calls.
expect(totalEmbedCalls).toBe(0);
// No DB writes.
expect(upserts).toEqual([]);
// Accurate counts.
expect(result.dryRun).toBe(true);
expect(result.embedded).toBe(0);
expect(result.would_embed).toBe(6); // 3 pages * 2 chunks each
expect(result.skipped).toBe(0);
expect(result.total_chunks).toBe(6);
expect(result.pages_processed).toBe(3);
});
test('dry-run --stale correctly separates stale from already-embedded', async () => {
const { runEmbedCore } = await import('../src/commands/embed.ts');
const pages = [{ slug: 'fresh' }, { slug: 'partial' }, { slug: 'all-stale' }];
const chunksBySlug = new Map<string, any[]>([
['fresh', [
{ chunk_index: 0, chunk_text: 'a', chunk_source: 'compiled_truth', embedded_at: '2026-01-01', token_count: 1 },
{ chunk_index: 1, chunk_text: 'b', chunk_source: 'compiled_truth', embedded_at: '2026-01-01', token_count: 1 },
]],
['partial', [
{ chunk_index: 0, chunk_text: 'a', chunk_source: 'compiled_truth', embedded_at: '2026-01-01', token_count: 1 },
{ chunk_index: 1, chunk_text: 'b', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
]],
['all-stale', [
{ chunk_index: 0, chunk_text: 'a', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
{ chunk_index: 1, chunk_text: 'b', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
]],
]);
const engine = mockEngine({
listPages: async () => pages,
getChunks: async (slug: string) => chunksBySlug.get(slug) || [],
upsertChunks: async () => {},
});
const result = await runEmbedCore(engine, { stale: true, dryRun: true });
expect(totalEmbedCalls).toBe(0);
expect(result.dryRun).toBe(true);
expect(result.would_embed).toBe(3); // 1 from 'partial' + 2 from 'all-stale'
expect(result.skipped).toBe(3); // 2 from 'fresh' + 1 from 'partial'
expect(result.total_chunks).toBe(6);
expect(result.pages_processed).toBe(3);
});
test('dry-run --slugs on a single page counts stale chunks, no API calls', async () => {
const { runEmbedCore } = await import('../src/commands/embed.ts');
const chunks = [
{ chunk_index: 0, chunk_text: 'a', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
{ chunk_index: 1, chunk_text: 'b', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
{ chunk_index: 2, chunk_text: 'c', chunk_source: 'compiled_truth', embedded_at: '2026-01-01', token_count: 1 },
];
const engine = mockEngine({
getPage: async () => ({ slug: 'my-page', compiled_truth: 'text', timeline: '' }),
getChunks: async () => chunks,
upsertChunks: async () => {},
});
const result = await runEmbedCore(engine, { slugs: ['my-page'], dryRun: true });
expect(totalEmbedCalls).toBe(0);
expect(result.dryRun).toBe(true);
expect(result.would_embed).toBe(2);
expect(result.skipped).toBe(1);
expect(result.total_chunks).toBe(3);
expect(result.pages_processed).toBe(1);
});
test('non-dry-run path reports accurate embedded count (regression guard)', async () => {
const { runEmbedCore } = await import('../src/commands/embed.ts');
const pages = [{ slug: 'a' }, { slug: 'b' }];
const chunksBySlug = new Map<string, any[]>([
['a', [{ chunk_index: 0, chunk_text: 'a', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 }]],
['b', [
{ chunk_index: 0, chunk_text: 'x', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
{ chunk_index: 1, chunk_text: 'y', chunk_source: 'compiled_truth', embedded_at: null, token_count: 1 },
]],
]);
const engine = mockEngine({
listPages: async () => pages,
getChunks: async (slug: string) => chunksBySlug.get(slug) || [],
upsertChunks: async () => {},
});
process.env.GBRAIN_EMBED_CONCURRENCY = '2';
const result = await runEmbedCore(engine, { stale: true });
expect(result.dryRun).toBe(false);
expect(result.embedded).toBe(3); // 1 from a + 2 from b
expect(result.would_embed).toBe(0);
expect(result.pages_processed).toBe(2);
});
});