From ad5a72c386dd27d9bf1106dcad003711d798a950 Mon Sep 17 00:00:00 2001 From: Matt Rubens <2600+mrubens@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:04:36 -0400 Subject: [PATCH 1/5] Proxy Brain reranking through OpenRouter --- .docker/gbrain/entrypoint.sh | 26 ++++++++++ .../__tests__/brain-inference.test.ts | 51 +++++++++++++++++++ .../api/src/handlers/brain-inference/index.ts | 23 +++++++-- deploy/compose/docker-compose.prod.yml | 1 + deploy/coolify/docker-compose.yaml | 1 + deploy/railway/template.yaml | 1 + docker-compose.self-host.yml | 1 + docker-compose.yml | 1 + render.yaml | 2 + 9 files changed, 103 insertions(+), 4 deletions(-) diff --git a/.docker/gbrain/entrypoint.sh b/.docker/gbrain/entrypoint.sh index 07e69fbd7..b833d2c6f 100644 --- a/.docker/gbrain/entrypoint.sh +++ b/.docker/gbrain/entrypoint.sh @@ -218,6 +218,32 @@ if [ ! -s "$CONFIG_FILE" ]; then fi fi +# Route gbrain's OpenRouter reranker through the same Roomote credential +# gateway as embeddings and chat. Do this after initialization so exposing an +# OpenRouter-compatible endpoint does not change which provider gbrain chooses +# when it creates the Brain. +if [ -n "${GBRAIN_RERANKER_MODEL:-}" ]; then + case "$GBRAIN_RERANKER_MODEL" in + openrouter:*) + if [ -z "${OPENROUTER_BASE_URL:-}" ] && [ -n "${OPENAI_BASE_URL:-}" ]; then + OPENROUTER_BASE_URL="${OPENAI_BASE_URL%/}/v1" + export OPENROUTER_BASE_URL + fi + if [ -z "${OPENROUTER_API_KEY:-}" ] && [ -n "${OPENAI_API_KEY:-}" ]; then + OPENROUTER_API_KEY="$OPENAI_API_KEY" + export OPENROUTER_API_KEY + fi + if [ -z "${OPENROUTER_BASE_URL:-}" ] || [ -z "${OPENROUTER_API_KEY:-}" ]; then + echo "[gbrain-entrypoint] WARNING: $GBRAIN_RERANKER_MODEL needs OPENROUTER_BASE_URL and OPENROUTER_API_KEY." + echo "[gbrain-entrypoint] WARNING: reranking will remain fail-open until the gateway is configured." + fi + ;; + esac + + gbrain config set search.reranker.model "$GBRAIN_RERANKER_MODEL" >/dev/null + echo "[gbrain-entrypoint] reranker: $GBRAIN_RERANKER_MODEL" +fi + # Adding a key to a brain created without one is a first-class flow rather # than an edge case: on hosts whose compose parser ignores `profiles` the # service always runs, so a keyless first deploy followed by filling the key diff --git a/apps/api/src/handlers/brain-inference/__tests__/brain-inference.test.ts b/apps/api/src/handlers/brain-inference/__tests__/brain-inference.test.ts index f5bcc3a93..9c8fc6f9c 100644 --- a/apps/api/src/handlers/brain-inference/__tests__/brain-inference.test.ts +++ b/apps/api/src/handlers/brain-inference/__tests__/brain-inference.test.ts @@ -139,6 +139,57 @@ describe('brain inference gateway', () => { }); }); + it('routes reranking through OpenRouter without exposing its key to gbrain', async () => { + const fetchMock = vi.fn( + async (_url: string, _init: RequestInit) => + new Response(JSON.stringify({ results: [] }), { status: 200 }), + ); + vi.stubGlobal('fetch', fetchMock); + + const body = { + model: 'cohere/rerank-v3.5', + query: 'Which result is relevant?', + documents: ['relevant', 'unrelated'], + top_n: 2, + }; + const response = await post('/v1/rerank', { + token: GATEWAY_TOKEN, + body, + }); + + expect(response.status).toBe(200); + const [url, init] = fetchMock.mock.calls[0]!; + expect(url).toBe('https://openrouter.ai/api/v1/rerank'); + expect((init.headers as Headers).get('authorization')).toBe( + `Bearer ${OPENROUTER.apiKey}`, + ); + expect(JSON.parse(init.body as string)).toEqual(body); + }); + + it('reports reranking as unavailable when only OpenAI is configured', async () => { + mockResolveBrainInferenceProvider.mockResolvedValue({ + providerId: 'openai', + apiKey: 'sk-openai-provider-key', + }); + const fetchMock = vi.fn(); + vi.stubGlobal('fetch', fetchMock); + + const response = await post('/v1/rerank', { + token: GATEWAY_TOKEN, + body: { + model: 'cohere/rerank-v3.5', + query: 'query', + documents: ['document'], + }, + }); + + expect(response.status).toBe(503); + await expect(response.json()).resolves.toMatchObject({ + error: expect.stringContaining('OpenRouter'), + }); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it('surfaces an unreachable provider as 502 rather than a crash', async () => { vi.stubGlobal( 'fetch', diff --git a/apps/api/src/handlers/brain-inference/index.ts b/apps/api/src/handlers/brain-inference/index.ts index d918baf76..4075b411f 100644 --- a/apps/api/src/handlers/brain-inference/index.ts +++ b/apps/api/src/handlers/brain-inference/index.ts @@ -18,13 +18,14 @@ import type { Variables } from '../../types'; const LOG_PREFIX = '[Brain Inference]'; /** - * The Brain's whole inference surface: embeddings for recall, and one chat - * path for sourced synthesis and query expansion. Deliberately narrower than - * the task-sandbox gateway's allowlist, because this credential is a static - * deployment secret rather than a short-lived run token. + * The Brain's whole inference surface: embeddings for recall, reranking for + * precision, and chat for sourced synthesis and query expansion. Deliberately + * narrower than the task-sandbox gateway's allowlist, because this credential + * is a static deployment secret rather than a short-lived run token. */ const BRAIN_ALLOWED_PATHS = new Set([ '/v1/embeddings', + '/v1/rerank', '/v1/chat/completions', '/v1/responses', ]); @@ -176,6 +177,20 @@ brainInference.post('/*', async (c) => { ); } + // gbrain's OpenRouter reranker speaks the same authenticated gateway + // contract as embeddings and chat, but OpenAI itself has no compatible + // rerank endpoint. Fail explicitly instead of forwarding a doomed request + // to api.openai.com and obscuring the missing capability as a 404. + if (upstreamPath === '/v1/rerank' && resolved.providerId !== 'openrouter') { + return c.json( + { + error: + 'Brain reranking requires an OpenRouter provider configured in Settings.', + }, + 503, + ); + } + const provider = getInferenceGatewayProvider(resolved.providerId); if (!provider?.authHeader) { diff --git a/deploy/compose/docker-compose.prod.yml b/deploy/compose/docker-compose.prod.yml index 9c355f823..cf2f68dc0 100644 --- a/deploy/compose/docker-compose.prod.yml +++ b/deploy/compose/docker-compose.prod.yml @@ -534,6 +534,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} + GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/coolify/docker-compose.yaml b/deploy/coolify/docker-compose.yaml index 33dee1a02..c0662e6ae 100644 --- a/deploy/coolify/docker-compose.yaml +++ b/deploy/coolify/docker-compose.yaml @@ -165,6 +165,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${SERVICE_PASSWORD_64_BRAINGATEWAY} + GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/railway/template.yaml b/deploy/railway/template.yaml index c85b8dd91..d6341c79a 100644 --- a/deploy/railway/template.yaml +++ b/deploy/railway/template.yaml @@ -143,6 +143,7 @@ services: # goes over the public origin, exactly as TRPC_URL already does. OPENAI_BASE_URL: https://${{api.RAILWAY_PUBLIC_DOMAIN}}/api/brain/inference OPENAI_API_KEY: ${{api.R_BRAIN_GATEWAY_TOKEN}} + GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/docker-compose.self-host.yml b/docker-compose.self-host.yml index 3631c29c8..1ec19d071 100644 --- a/docker-compose.self-host.yml +++ b/docker-compose.self-host.yml @@ -297,6 +297,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} + GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/docker-compose.yml b/docker-compose.yml index f860be001..feae94d2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -105,6 +105,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://host.docker.internal:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} + GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/render.yaml b/render.yaml index 8f12fad5c..ba4851e5b 100644 --- a/render.yaml +++ b/render.yaml @@ -162,6 +162,8 @@ services: type: web name: roomote-api envVarKey: R_BRAIN_GATEWAY_TOKEN + - key: GBRAIN_RERANKER_MODEL + value: openrouter:cohere/rerank-v3.5 # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. From 50016193cc649cfba75cf104fb6e19dbfb1c0c8f Mon Sep 17 00:00:00 2001 From: Matt Rubens <2600+mrubens@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:13:15 -0400 Subject: [PATCH 2/5] Make the Brain reranker configurable --- apps/docs/brain.mdx | 25 ++++++++++++++++--------- deploy/compose/docker-compose.prod.yml | 2 +- deploy/coolify/docker-compose.yaml | 2 +- deploy/railway/template.yaml | 2 +- docker-compose.self-host.yml | 2 +- docker-compose.yml | 2 +- render.yaml | 2 +- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/apps/docs/brain.mdx b/apps/docs/brain.mdx index c906dee43..57a4533d3 100644 --- a/apps/docs/brain.mdx +++ b/apps/docs/brain.mdx @@ -46,9 +46,9 @@ up a provider for tasks, the Brain has what it needs and there is nothing else to do. Changing that key later takes effect on the Brain's next request, with no redeploy. -OpenRouter and OpenAI are both supported, and the Brain runs the same OpenAI -models either way, so the choice is about routing and billing rather than -capability. To bill the Brain separately from task inference, set +OpenRouter and OpenAI both support the Brain's embedding and synthesis calls, +but search reranking requires OpenRouter. To bill the Brain separately from +task inference, set `R_BRAIN_OPENROUTER_API_KEY` or `R_BRAIN_OPENAI_API_KEY`; those take precedence over the deployment's general provider keys. @@ -82,16 +82,23 @@ in staging is distinguishable from one written against production. ## Choosing models -Two settings pick the Brain's models: +Three settings pick the Brain's models: -| Variable | What it does | Written as | Changeable | -| -------------------------- | ----------------- | ------------------------------ | --------------------- | -| `R_BRAIN_MODEL` | Sourced synthesis | your provider's naming | any time | -| `R_BRAIN_EMBEDDING_MODEL` | Semantic recall | a plain model id | before the first boot | +| Variable | What it does | Written as | Changeable | +| ----------------------------- | ----------------- | --------------------------------- | --------------------- | +| `R_BRAIN_MODEL` | Sourced synthesis | your provider's naming | any time | +| `R_BRAIN_EMBEDDING_MODEL` | Semantic recall | a plain model id | before the first boot | +| `GBRAIN_RERANKER_MODEL` | Search precision | `openrouter:/` | after a restart | -Leave both unset and the Brain uses OpenAI's `gpt-5.6-luna` and +Leave the first two unset and the Brain uses OpenAI's `gpt-5.6-luna` and `text-embedding-3-small` through whichever provider you configured. +The reranker defaults to OpenRouter's `voyageai/rerank-2.5-lite`. Set +`GBRAIN_RERANKER_MODEL` on the gbrain service to choose another model from +OpenRouter's reranker catalog. Reranking requires an OpenRouter key; with only +OpenAI configured, gbrain keeps the unreranked results instead of failing the +search. + The synthesis model is applied by Roomote when it forwards the call and passed to the provider as written, so use that provider's naming (`openai/gpt-5.6-mini` on OpenRouter, `gpt-5.6-mini` on OpenAI). Changing diff --git a/deploy/compose/docker-compose.prod.yml b/deploy/compose/docker-compose.prod.yml index cf2f68dc0..e63ca2e44 100644 --- a/deploy/compose/docker-compose.prod.yml +++ b/deploy/compose/docker-compose.prod.yml @@ -534,7 +534,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 + GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/coolify/docker-compose.yaml b/deploy/coolify/docker-compose.yaml index c0662e6ae..649fae577 100644 --- a/deploy/coolify/docker-compose.yaml +++ b/deploy/coolify/docker-compose.yaml @@ -165,7 +165,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${SERVICE_PASSWORD_64_BRAINGATEWAY} - GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 + GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/railway/template.yaml b/deploy/railway/template.yaml index d6341c79a..8e40a5d7f 100644 --- a/deploy/railway/template.yaml +++ b/deploy/railway/template.yaml @@ -143,7 +143,7 @@ services: # goes over the public origin, exactly as TRPC_URL already does. OPENAI_BASE_URL: https://${{api.RAILWAY_PUBLIC_DOMAIN}}/api/brain/inference OPENAI_API_KEY: ${{api.R_BRAIN_GATEWAY_TOKEN}} - GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 + GBRAIN_RERANKER_MODEL: openrouter:voyageai/rerank-2.5-lite # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/docker-compose.self-host.yml b/docker-compose.self-host.yml index 1ec19d071..405ef3d11 100644 --- a/docker-compose.self-host.yml +++ b/docker-compose.self-host.yml @@ -297,7 +297,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 + GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/docker-compose.yml b/docker-compose.yml index feae94d2d..2fe4bd1f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -105,7 +105,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://host.docker.internal:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: openrouter:cohere/rerank-v3.5 + GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/render.yaml b/render.yaml index ba4851e5b..88eb2075e 100644 --- a/render.yaml +++ b/render.yaml @@ -163,7 +163,7 @@ services: name: roomote-api envVarKey: R_BRAIN_GATEWAY_TOKEN - key: GBRAIN_RERANKER_MODEL - value: openrouter:cohere/rerank-v3.5 + value: openrouter:voyageai/rerank-2.5-lite # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. From f95f1750dbe11c242d6a006968c0966ed5ea4c5a Mon Sep 17 00:00:00 2001 From: Matt Rubens <2600+mrubens@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:18:44 -0400 Subject: [PATCH 3/5] Preserve versioned reranker base URLs --- .docker/gbrain/entrypoint.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.docker/gbrain/entrypoint.sh b/.docker/gbrain/entrypoint.sh index b833d2c6f..0a49bcf82 100644 --- a/.docker/gbrain/entrypoint.sh +++ b/.docker/gbrain/entrypoint.sh @@ -226,7 +226,11 @@ if [ -n "${GBRAIN_RERANKER_MODEL:-}" ]; then case "$GBRAIN_RERANKER_MODEL" in openrouter:*) if [ -z "${OPENROUTER_BASE_URL:-}" ] && [ -n "${OPENAI_BASE_URL:-}" ]; then - OPENROUTER_BASE_URL="${OPENAI_BASE_URL%/}/v1" + OPENROUTER_BASE_URL="${OPENAI_BASE_URL%/}" + case "$OPENROUTER_BASE_URL" in + */v1) ;; + *) OPENROUTER_BASE_URL="$OPENROUTER_BASE_URL/v1" ;; + esac export OPENROUTER_BASE_URL fi if [ -z "${OPENROUTER_API_KEY:-}" ] && [ -n "${OPENAI_API_KEY:-}" ]; then From 5d3fac3bfd8abaa5f845b572ceb7d5a0ea9897a3 Mon Sep 17 00:00:00 2001 From: Matt Rubens <2600+mrubens@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:25:11 -0400 Subject: [PATCH 4/5] Standardize the Brain reranker setting --- apps/docs/brain.mdx | 4 ++-- deploy/compose/docker-compose.prod.yml | 3 ++- deploy/coolify/README.md | 7 ++++--- deploy/coolify/docker-compose.yaml | 3 ++- deploy/railway/README.md | 7 ++++--- deploy/railway/template.yaml | 4 +++- deploy/render/README.md | 7 ++++--- docker-compose.self-host.yml | 3 ++- docker-compose.yml | 2 +- render.yaml | 7 ++++++- 10 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/docs/brain.mdx b/apps/docs/brain.mdx index 57a4533d3..a9771462d 100644 --- a/apps/docs/brain.mdx +++ b/apps/docs/brain.mdx @@ -88,13 +88,13 @@ Three settings pick the Brain's models: | ----------------------------- | ----------------- | --------------------------------- | --------------------- | | `R_BRAIN_MODEL` | Sourced synthesis | your provider's naming | any time | | `R_BRAIN_EMBEDDING_MODEL` | Semantic recall | a plain model id | before the first boot | -| `GBRAIN_RERANKER_MODEL` | Search precision | `openrouter:/` | after a restart | +| `R_BRAIN_RERANKER_MODEL` | Search precision | `openrouter:/` | after a restart | Leave the first two unset and the Brain uses OpenAI's `gpt-5.6-luna` and `text-embedding-3-small` through whichever provider you configured. The reranker defaults to OpenRouter's `voyageai/rerank-2.5-lite`. Set -`GBRAIN_RERANKER_MODEL` on the gbrain service to choose another model from +`R_BRAIN_RERANKER_MODEL` to choose another model from OpenRouter's reranker catalog. Reranking requires an OpenRouter key; with only OpenAI configured, gbrain keeps the unreranked results instead of failing the search. diff --git a/deploy/compose/docker-compose.prod.yml b/deploy/compose/docker-compose.prod.yml index e63ca2e44..e966b1aa7 100644 --- a/deploy/compose/docker-compose.prod.yml +++ b/deploy/compose/docker-compose.prod.yml @@ -24,6 +24,7 @@ x-roomote-base-env: &roomote-base-env R_BRAIN_MODEL: ${R_BRAIN_MODEL:-} R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-} R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-} + R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} R_GBRAIN_URL: ${R_GBRAIN_URL:-http://gbrain:8931} R_GBRAIN_ADMIN_TOKEN_FILE: /gbrain-data/admin-bootstrap-token # Written by the Brain on first boot when no token was supplied, so a stack @@ -534,7 +535,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/coolify/README.md b/deploy/coolify/README.md index 3986a4b90..cb52f0c6c 100644 --- a/deploy/coolify/README.md +++ b/deploy/coolify/README.md @@ -278,9 +278,10 @@ Two operational notes: it. `gbrain_data` only holds service configuration and generated bootstrap credentials. - **Model choice is a variable, not a rebuild.** `R_BRAIN_MODEL` selects the - synthesis model and `R_BRAIN_EMBEDDING_MODEL` the embedding model, both in - your provider's own naming, both set on the app services. Leave them empty for the - defaults. The synthesis model can change at any time; the embedding model + synthesis model, `R_BRAIN_EMBEDDING_MODEL` the embedding model, and + `R_BRAIN_RERANKER_MODEL` the reranker. Set them on the app services. Leave + them empty for the defaults. The synthesis model can change at any time; + the reranker changes after a gbrain restart; the embedding model sizes the Brain's vector storage when it is first created, so set it (with `R_BRAIN_EMBEDDING_DIMENSIONS`) before first boot or not at all. A later change is ignored and reported in the Brain's logs rather than silently diff --git a/deploy/coolify/docker-compose.yaml b/deploy/coolify/docker-compose.yaml index 649fae577..5d629eba9 100644 --- a/deploy/coolify/docker-compose.yaml +++ b/deploy/coolify/docker-compose.yaml @@ -82,6 +82,7 @@ x-roomote-shared-env: &roomote-shared-env R_BRAIN_MODEL: ${R_BRAIN_MODEL:-} R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-} R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-} + R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} R_GBRAIN_URL: http://gbrain:8931 # Roomote uses this only to register its own scoped clients against the # Brain. Coolify's SERVICE_PASSWORD_64_* generates an @@ -165,7 +166,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${SERVICE_PASSWORD_64_BRAINGATEWAY} - GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/railway/README.md b/deploy/railway/README.md index d081d726f..c51f65d5d 100644 --- a/deploy/railway/README.md +++ b/deploy/railway/README.md @@ -429,9 +429,10 @@ Two operational notes: re-registers its clients automatically when the Brain no longer recognizes them — but the deployment starts cold until that finishes. - **Model choice is a variable, not a rebuild.** `R_BRAIN_MODEL` selects the - synthesis model and `R_BRAIN_EMBEDDING_MODEL` the embedding model, both in - your provider's own naming, both set on **api**. Leave them empty for the - defaults. The synthesis model can change at any time; the embedding model + synthesis model, `R_BRAIN_EMBEDDING_MODEL` the embedding model, and + `R_BRAIN_RERANKER_MODEL` the reranker, all set on **api**. Leave them empty + for the defaults. The synthesis model can change at any time; the reranker + changes after a gbrain restart; the embedding model sizes the Brain's vector storage when it is first created, so set it (with `R_BRAIN_EMBEDDING_DIMENSIONS`) before first boot or not at all. A later change is ignored and reported in the Brain's logs rather than silently diff --git a/deploy/railway/template.yaml b/deploy/railway/template.yaml index 8e40a5d7f..278c0a22d 100644 --- a/deploy/railway/template.yaml +++ b/deploy/railway/template.yaml @@ -143,7 +143,7 @@ services: # goes over the public origin, exactly as TRPC_URL already does. OPENAI_BASE_URL: https://${{api.RAILWAY_PUBLIC_DOMAIN}}/api/brain/inference OPENAI_API_KEY: ${{api.R_BRAIN_GATEWAY_TOKEN}} - GBRAIN_RERANKER_MODEL: openrouter:voyageai/rerank-2.5-lite + GBRAIN_RERANKER_MODEL: ${{api.R_BRAIN_RERANKER_MODEL}} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. @@ -213,6 +213,7 @@ services: R_BRAIN_MODEL: '' R_BRAIN_EMBEDDING_MODEL: '' R_BRAIN_EMBEDDING_DIMENSIONS: '' + R_BRAIN_RERANKER_MODEL: openrouter:voyageai/rerank-2.5-lite # Railway's private network is IPv6-only and never leaves the project, # so the Brain is unreachable from the internet by construction. R_GBRAIN_URL: http://${{gbrain.RAILWAY_PRIVATE_DOMAIN}}:8931 @@ -260,6 +261,7 @@ services: R_BRAIN_GATEWAY_TOKEN: ${{api.R_BRAIN_GATEWAY_TOKEN}} R_BRAIN_MODEL: ${{api.R_BRAIN_MODEL}} R_BRAIN_EMBEDDING_MODEL: ${{api.R_BRAIN_EMBEDDING_MODEL}} + R_BRAIN_RERANKER_MODEL: ${{api.R_BRAIN_RERANKER_MODEL}} R_GBRAIN_URL: ${{api.R_GBRAIN_URL}} R_GBRAIN_ADMIN_TOKEN: ${{api.R_GBRAIN_ADMIN_TOKEN}} R_APP_ENV: ${{api.R_APP_ENV}} diff --git a/deploy/render/README.md b/deploy/render/README.md index bd480330a..936b9295d 100644 --- a/deploy/render/README.md +++ b/deploy/render/README.md @@ -392,9 +392,10 @@ Two operational notes: longer recognizes them — but the deployment starts cold until that finishes. - **Model choice is a variable, not a rebuild.** `R_BRAIN_MODEL` selects the - synthesis model and `R_BRAIN_EMBEDDING_MODEL` the embedding model, both in - your provider's own naming, both set on the app services. Leave them empty for the - defaults. The synthesis model can change at any time; the embedding model + synthesis model, `R_BRAIN_EMBEDDING_MODEL` the embedding model, and + `R_BRAIN_RERANKER_MODEL` the reranker. Set them on the api service. Leave + them empty for the defaults. The synthesis model can change at any time; + the reranker changes after a gbrain restart; the embedding model sizes the Brain's vector storage when it is first created, so set it (with `R_BRAIN_EMBEDDING_DIMENSIONS`) before first boot or not at all. A later change is ignored and reported in the Brain's logs rather than silently diff --git a/docker-compose.self-host.yml b/docker-compose.self-host.yml index 405ef3d11..1b404cae3 100644 --- a/docker-compose.self-host.yml +++ b/docker-compose.self-host.yml @@ -36,6 +36,7 @@ x-roomote-env: &roomote-env R_BRAIN_MODEL: ${R_BRAIN_MODEL:-} R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-} R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-} + R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} R_GBRAIN_URL: ${R_GBRAIN_URL:-http://gbrain:8931} # Roomote reads the brain's bootstrap token once to register its own # scoped clients; api and bullmq mount the brain volume read-only. @@ -297,7 +298,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/docker-compose.yml b/docker-compose.yml index 2fe4bd1f4..3b7b679a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -105,7 +105,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://host.docker.internal:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: ${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/render.yaml b/render.yaml index 88eb2075e..3be359658 100644 --- a/render.yaml +++ b/render.yaml @@ -163,7 +163,10 @@ services: name: roomote-api envVarKey: R_BRAIN_GATEWAY_TOKEN - key: GBRAIN_RERANKER_MODEL - value: openrouter:voyageai/rerank-2.5-lite + fromService: + type: web + name: roomote-api + envVarKey: R_BRAIN_RERANKER_MODEL # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. @@ -250,6 +253,8 @@ services: sync: false - key: R_BRAIN_EMBEDDING_MODEL sync: false + - key: R_BRAIN_RERANKER_MODEL + value: openrouter:voyageai/rerank-2.5-lite - key: ROOMOTE_GBRAIN_HOSTPORT fromService: type: pserv From 5944fba77f8ebbd5b2cf9385c9ea15ee48e8d8e6 Mon Sep 17 00:00:00 2001 From: Matt Rubens <2600+mrubens@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:32:49 -0400 Subject: [PATCH 5/5] Restore the default Brain reranker when unset --- .docker/gbrain/entrypoint.sh | 50 +++++++++++++------------- deploy/compose/docker-compose.prod.yml | 4 +-- deploy/coolify/docker-compose.yaml | 4 +-- deploy/railway/template.yaml | 2 +- docker-compose.self-host.yml | 4 +-- docker-compose.yml | 2 +- render.yaml | 2 +- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.docker/gbrain/entrypoint.sh b/.docker/gbrain/entrypoint.sh index 0a49bcf82..e0ecba2e7 100644 --- a/.docker/gbrain/entrypoint.sh +++ b/.docker/gbrain/entrypoint.sh @@ -221,32 +221,32 @@ fi # Route gbrain's OpenRouter reranker through the same Roomote credential # gateway as embeddings and chat. Do this after initialization so exposing an # OpenRouter-compatible endpoint does not change which provider gbrain chooses -# when it creates the Brain. -if [ -n "${GBRAIN_RERANKER_MODEL:-}" ]; then - case "$GBRAIN_RERANKER_MODEL" in - openrouter:*) - if [ -z "${OPENROUTER_BASE_URL:-}" ] && [ -n "${OPENAI_BASE_URL:-}" ]; then - OPENROUTER_BASE_URL="${OPENAI_BASE_URL%/}" - case "$OPENROUTER_BASE_URL" in - */v1) ;; - *) OPENROUTER_BASE_URL="$OPENROUTER_BASE_URL/v1" ;; - esac - export OPENROUTER_BASE_URL - fi - if [ -z "${OPENROUTER_API_KEY:-}" ] && [ -n "${OPENAI_API_KEY:-}" ]; then - OPENROUTER_API_KEY="$OPENAI_API_KEY" - export OPENROUTER_API_KEY - fi - if [ -z "${OPENROUTER_BASE_URL:-}" ] || [ -z "${OPENROUTER_API_KEY:-}" ]; then - echo "[gbrain-entrypoint] WARNING: $GBRAIN_RERANKER_MODEL needs OPENROUTER_BASE_URL and OPENROUTER_API_KEY." - echo "[gbrain-entrypoint] WARNING: reranking will remain fail-open until the gateway is configured." - fi - ;; - esac +# when it creates the Brain. An empty forwarded setting restores the default, +# including after a deployment previously selected another reranker. +GBRAIN_RERANKER_MODEL="${GBRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite}" +case "$GBRAIN_RERANKER_MODEL" in + openrouter:*) + if [ -z "${OPENROUTER_BASE_URL:-}" ] && [ -n "${OPENAI_BASE_URL:-}" ]; then + OPENROUTER_BASE_URL="${OPENAI_BASE_URL%/}" + case "$OPENROUTER_BASE_URL" in + */v1) ;; + *) OPENROUTER_BASE_URL="$OPENROUTER_BASE_URL/v1" ;; + esac + export OPENROUTER_BASE_URL + fi + if [ -z "${OPENROUTER_API_KEY:-}" ] && [ -n "${OPENAI_API_KEY:-}" ]; then + OPENROUTER_API_KEY="$OPENAI_API_KEY" + export OPENROUTER_API_KEY + fi + if [ -z "${OPENROUTER_BASE_URL:-}" ] || [ -z "${OPENROUTER_API_KEY:-}" ]; then + echo "[gbrain-entrypoint] WARNING: $GBRAIN_RERANKER_MODEL needs OPENROUTER_BASE_URL and OPENROUTER_API_KEY." + echo "[gbrain-entrypoint] WARNING: reranking will remain fail-open until the gateway is configured." + fi + ;; +esac - gbrain config set search.reranker.model "$GBRAIN_RERANKER_MODEL" >/dev/null - echo "[gbrain-entrypoint] reranker: $GBRAIN_RERANKER_MODEL" -fi +gbrain config set search.reranker.model "$GBRAIN_RERANKER_MODEL" >/dev/null +echo "[gbrain-entrypoint] reranker: $GBRAIN_RERANKER_MODEL" # Adding a key to a brain created without one is a first-class flow rather # than an edge case: on hosts whose compose parser ignores `profiles` the diff --git a/deploy/compose/docker-compose.prod.yml b/deploy/compose/docker-compose.prod.yml index e966b1aa7..cabdcba9f 100644 --- a/deploy/compose/docker-compose.prod.yml +++ b/deploy/compose/docker-compose.prod.yml @@ -24,7 +24,7 @@ x-roomote-base-env: &roomote-base-env R_BRAIN_MODEL: ${R_BRAIN_MODEL:-} R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-} R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-} - R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} R_GBRAIN_URL: ${R_GBRAIN_URL:-http://gbrain:8931} R_GBRAIN_ADMIN_TOKEN_FILE: /gbrain-data/admin-bootstrap-token # Written by the Brain on first boot when no token was supplied, so a stack @@ -535,7 +535,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/coolify/docker-compose.yaml b/deploy/coolify/docker-compose.yaml index 5d629eba9..75592f21b 100644 --- a/deploy/coolify/docker-compose.yaml +++ b/deploy/coolify/docker-compose.yaml @@ -82,7 +82,7 @@ x-roomote-shared-env: &roomote-shared-env R_BRAIN_MODEL: ${R_BRAIN_MODEL:-} R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-} R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-} - R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} R_GBRAIN_URL: http://gbrain:8931 # Roomote uses this only to register its own scoped clients against the # Brain. Coolify's SERVICE_PASSWORD_64_* generates an @@ -166,7 +166,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${SERVICE_PASSWORD_64_BRAINGATEWAY} - GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/deploy/railway/template.yaml b/deploy/railway/template.yaml index 278c0a22d..f1a54afc3 100644 --- a/deploy/railway/template.yaml +++ b/deploy/railway/template.yaml @@ -213,7 +213,7 @@ services: R_BRAIN_MODEL: '' R_BRAIN_EMBEDDING_MODEL: '' R_BRAIN_EMBEDDING_DIMENSIONS: '' - R_BRAIN_RERANKER_MODEL: openrouter:voyageai/rerank-2.5-lite + R_BRAIN_RERANKER_MODEL: '' # Railway's private network is IPv6-only and never leaves the project, # so the Brain is unreachable from the internet by construction. R_GBRAIN_URL: http://${{gbrain.RAILWAY_PRIVATE_DOMAIN}}:8931 diff --git a/docker-compose.self-host.yml b/docker-compose.self-host.yml index 1b404cae3..59691603d 100644 --- a/docker-compose.self-host.yml +++ b/docker-compose.self-host.yml @@ -36,7 +36,7 @@ x-roomote-env: &roomote-env R_BRAIN_MODEL: ${R_BRAIN_MODEL:-} R_BRAIN_EMBEDDING_MODEL: ${R_BRAIN_EMBEDDING_MODEL:-} R_BRAIN_EMBEDDING_DIMENSIONS: ${R_BRAIN_EMBEDDING_DIMENSIONS:-} - R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + R_BRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} R_GBRAIN_URL: ${R_GBRAIN_URL:-http://gbrain:8931} # Roomote reads the brain's bootstrap token once to register its own # scoped clients; api and bullmq mount the brain volume read-only. @@ -298,7 +298,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://api:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/docker-compose.yml b/docker-compose.yml index 3b7b679a9..74509c7cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -105,7 +105,7 @@ services: # provider key below instead makes the Brain call the provider directly. OPENAI_BASE_URL: ${GBRAIN_OPENAI_BASE_URL:-http://host.docker.internal:3001/api/brain/inference} OPENAI_API_KEY: ${R_BRAIN_GATEWAY_TOKEN:-} - GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-openrouter:voyageai/rerank-2.5-lite} + GBRAIN_RERANKER_MODEL: ${R_BRAIN_RERANKER_MODEL:-} # Create-time, and it has to be the container that receives these: the # embedding model and its width are decided when the Brain is created, # and the gateway must never substitute a different one afterwards. diff --git a/render.yaml b/render.yaml index 3be359658..ff7ab12db 100644 --- a/render.yaml +++ b/render.yaml @@ -254,7 +254,7 @@ services: - key: R_BRAIN_EMBEDDING_MODEL sync: false - key: R_BRAIN_RERANKER_MODEL - value: openrouter:voyageai/rerank-2.5-lite + value: '' - key: ROOMOTE_GBRAIN_HOSTPORT fromService: type: pserv