From 54c0c93376b02a72b2b39ad59c5bb588712a8fea Mon Sep 17 00:00:00 2001 From: Time Attakc <89218912+time-attack@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:27:48 -0700 Subject: [PATCH] reland: feat(recipes): add reranker touchpoint to OpenRouter (#2164) (#3302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(recipes): add reranker touchpoint to OpenRouter (#2164) OpenRouter's POST /api/v1/rerank is wire-compatible with gateway.rerank() ({query, documents, model} → {results: [{index, relevance_score}]}). This adds a recipe-only reranker touchpoint declaring four models: - cohere/rerank-v3.5 (default; $0.001/search) - cohere/rerank-4-fast ($0.002/search, 32K context) - cohere/rerank-4-pro ($0.0025/search, SOTA quality) - nvidia/llama-nemotron-rerank-vl-1b-v2:free (multimodal) Unlike embedding/chat, the reranker path strictly enforces the models allowlist — the openai-compat extended-model bypass does not apply. New rerank models must be added to this recipe before they can be called. The cost_per_1m_tokens_usd value is a pseudo-rate for the budget tracker's chars/4 heuristic — Cohere bills per-search, not per-token. At ~4K chars the estimated cost is in the right ballpark. Recipe-only change; no gateway or search-layer modifications. gateway auto-concatenates path → .../api/v1/rerank. Adds hermetic unit test (test/openrouter-reranker-recipe.test.ts) covering shape, models, default_model, path, max_payload_bytes, default_timeout_ms, and cost field. No DB, no env mutation — survives the parallel 8-shard fan-out. Verified: bun run verify (30/30 green); 285 targeted recipe+rerank+budget tests pass. Co-authored-by: Hippityy * test(facts): pin gateway to 1536d in facts-engine.test.ts beforeAll Shard-composition hermeticity fix. The legacy preload's beforeEach only re-applies the 1536-d gateway default before each TEST, not before a file's beforeAll — so when the previous file in the shard resets the gateway in its teardown (e.g. test/providers-test-model-base-url.test.ts via afterEach), this file's initSchema() sized facts.embedding at the 1280-d production default and the 1536-d fixture inserts threw 'expected 1280 dimensions, not 1536' (CI shard 1 failure on #3302). Same pattern as test/consolidate-valid-until.test.ts. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Ryan Xie <64182766+Hippityy@users.noreply.github.com> Co-authored-by: Hippityy Co-authored-by: Garry Tan Co-authored-by: Claude Fable 5 --- src/core/ai/recipes/openrouter.ts | 32 ++++++++++++++++++ test/facts-engine.test.ts | 1 + test/openrouter-reranker-recipe.test.ts | 44 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 test/openrouter-reranker-recipe.test.ts diff --git a/src/core/ai/recipes/openrouter.ts b/src/core/ai/recipes/openrouter.ts index a46ac0666..bc19f5cca 100644 --- a/src/core/ai/recipes/openrouter.ts +++ b/src/core/ai/recipes/openrouter.ts @@ -119,6 +119,14 @@ export const openrouterCompatFetch = (async ( * envelope, not every individual model's capability. When in doubt about a * specific model, check https://openrouter.ai/models. * + * Reranker: `/api/v1/rerank` proxies cross-encoder rerankers (Cohere v3.5/4-fast/4-pro + * and NVIDIA Nemotron VL). Wire shape matches `gateway.rerank()`: + * `{ query, documents, model }` → `{ results: [{ index, relevance_score }] }`. + * Unlike embedding/chat, the reranker path strictly enforces the `models` + * allowlist (no openai-compat bypass) — adding new rerank models requires a + * recipe edit. Cohere bills per-search; the `cost_per_1m_tokens_usd` value + * is a pseudo-rate for the budget tracker's `chars/4` heuristic. + * * Attribution: OpenRouter recommends `HTTP-Referer` (required for app * attribution) + `X-OpenRouter-Title` (preferred; `X-Title` kept as * back-compat alias per OR docs). Defaults to `https://gbrain.ai` / `gbrain`; @@ -197,6 +205,30 @@ export const openrouter: Recipe = { // Let upstream errors surface per-model. price_last_verified: '2026-05-20', }, + reranker: { + models: [ + 'cohere/rerank-v3.5', + 'cohere/rerank-4-fast', + 'cohere/rerank-4-pro', + 'nvidia/llama-nemotron-rerank-vl-1b-v2:free', + ], + default_model: 'cohere/rerank-v3.5', + // Cohere bills per-search, not per-token. This is a pseudo-per-1M rate + // for the budget tracker's heuristic (estimates tokens as chars/4). + // At ~4K chars/search the tracker estimates ~$0.00025 — in the right + // ballpark for the per-search bill. Patch budget-tracker.ts to honour a + // `cost_per_search_usd` field for exact accounting. + cost_per_1m_tokens_usd: 0.001, + price_last_verified: '2026-06-13', + // OpenRouter doesn't publish an explicit payload cap; 5MB matches + // ZeroEntropy's upstream limit and the gateway's pre-flight ceiling. + max_payload_bytes: 5_000_000, + // OR serves /rerank under /api/v1. base_url_default already ends in /v1, + // so gateway concatenates to …/api/v1/rerank. + path: '/rerank', + // OpenRouter rerank is fast (<200 ms p50); 5 s covers cold path safely. + default_timeout_ms: 5_000, + }, }, setup_hint: 'Get an API key at https://openrouter.ai/settings/keys, then `export OPENROUTER_API_KEY=...` and use `openrouter:/`. Optional overrides: OPENROUTER_BASE_URL (proxy), OPENROUTER_REFERER (attribution URL), OPENROUTER_TITLE (attribution name).', diff --git a/test/facts-engine.test.ts b/test/facts-engine.test.ts index 74ea709f6..91cbb15d6 100644 --- a/test/facts-engine.test.ts +++ b/test/facts-engine.test.ts @@ -27,6 +27,7 @@ beforeAll(async () => { // dimensions, not 1536"). Same fix + rationale as // doctor-hidden-by-search-policy.test.ts (#2801), // engine-find-trajectory.test.ts and cosine-rescore-column.test.ts. + resetGateway(); configureGateway({ embedding_model: 'openai:text-embedding-3-large', embedding_dimensions: 1536, diff --git a/test/openrouter-reranker-recipe.test.ts b/test/openrouter-reranker-recipe.test.ts new file mode 100644 index 000000000..508b7b945 --- /dev/null +++ b/test/openrouter-reranker-recipe.test.ts @@ -0,0 +1,44 @@ +import { describe, test, expect } from 'bun:test'; +import { getRecipe } from '../src/core/ai/recipes/index.ts'; + +describe('OpenRouter recipe — reranker touchpoint', () => { + test('declares a reranker touchpoint', () => { + const r = getRecipe('openrouter'); + expect(r).toBeDefined(); + expect(r!.touchpoints.reranker).toBeDefined(); + }); + + test('models list includes all supported IDs (incl. NVIDIA :free suffix)', () => { + const m = getRecipe('openrouter')!.touchpoints.reranker!.models; + expect(m).toContain('cohere/rerank-v3.5'); + expect(m).toContain('cohere/rerank-4-fast'); + expect(m).toContain('cohere/rerank-4-pro'); + // The :free suffix must appear in full — gateway.rerank() does exact + // string matching against the allowlist (no v0.31.12 extended-model bypass + // on the rerank path), so truncating to `nvidia/.../v2` would 403. + expect(m).toContain('nvidia/llama-nemotron-rerank-vl-1b-v2:free'); + }); + + test('default_model is cohere/rerank-v3.5', () => { + const tp = getRecipe('openrouter')!.touchpoints.reranker!; + expect(tp.default_model).toBe('cohere/rerank-v3.5'); + expect(tp.models).toContain(tp.default_model); + }); + + test('path is /rerank (NOT ZeroEntropy default /models/rerank)', () => { + const tp = getRecipe('openrouter')!.touchpoints.reranker!; + expect(tp.path).toBe('/rerank'); + }); + + test('max_payload_bytes and timeout match plan', () => { + const tp = getRecipe('openrouter')!.touchpoints.reranker!; + expect(tp.max_payload_bytes).toBe(5_000_000); + expect(tp.default_timeout_ms).toBe(5_000); + }); + + test('cost_per_1m_tokens_usd is set (pseudo-rate for per-search billing)', () => { + const tp = getRecipe('openrouter')!.touchpoints.reranker!; + expect(typeof tp.cost_per_1m_tokens_usd).toBe('number'); + expect(tp.cost_per_1m_tokens_usd).toBeGreaterThan(0); + }); +});