Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .docker/gbrain/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,36 @@ 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. 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"

# 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
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 19 additions & 4 deletions apps/api/src/handlers/brain-inference/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]);
Expand Down Expand Up @@ -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) {
Expand Down
25 changes: 16 additions & 9 deletions apps/docs/brain.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 |
| `R_BRAIN_RERANKER_MODEL` | Search precision | `openrouter:<provider>/<model-id>` | 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
`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.

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
Expand Down
2 changes: 2 additions & 0 deletions deploy/compose/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}
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
Expand Down Expand Up @@ -534,6 +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:-}
# 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.
Expand Down
7 changes: 4 additions & 3 deletions deploy/coolify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions deploy/coolify/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}
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
Expand Down Expand Up @@ -165,6 +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:-}
# 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.
Expand Down
7 changes: 4 additions & 3 deletions deploy/railway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions deploy/railway/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ${{api.R_BRAIN_RERANKER_MODEL}}
Comment thread
roomote-community[bot] marked this conversation as resolved.
# 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.
Expand Down Expand Up @@ -212,6 +213,7 @@ services:
R_BRAIN_MODEL: ''
R_BRAIN_EMBEDDING_MODEL: ''
R_BRAIN_EMBEDDING_DIMENSIONS: ''
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
Expand Down Expand Up @@ -259,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}}
Expand Down
7 changes: 4 additions & 3 deletions deploy/render/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.self-host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:-}
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.
Expand Down Expand Up @@ -297,6 +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:-}
# 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.
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ${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.
Expand Down
7 changes: 7 additions & 0 deletions render.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ services:
type: web
name: roomote-api
envVarKey: R_BRAIN_GATEWAY_TOKEN
- key: GBRAIN_RERANKER_MODEL
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.
Expand Down Expand Up @@ -248,6 +253,8 @@ services:
sync: false
- key: R_BRAIN_EMBEDDING_MODEL
sync: false
- key: R_BRAIN_RERANKER_MODEL
value: ''
- key: ROOMOTE_GBRAIN_HOSTPORT
fromService:
type: pserv
Expand Down
Loading