Skip to content

fix(chat): resolve the default chat LLM from the catalog default endpoint - #755

Merged
Ahmath-Gadji merged 2 commits into
developfrom
fix/754-chat-llm-default-endpoint
Jul 22, 2026
Merged

fix(chat): resolve the default chat LLM from the catalog default endpoint#755
Ahmath-Gadji merged 2 commits into
developfrom
fix/754-chat-llm-default-endpoint

Conversation

@Ahmath-Gadji

Copy link
Copy Markdown
Collaborator

Closes #754.

Problem

When a partition has no chat_llm preset ("Default (from retrieval config)"), QueryService._resolve_llm() returned the static self._llm — a client built once at startup from settings.llm (the .env LLM_ENDPOINT/LLM_MODEL) in di/container.py. It never consulted the model-endpoint catalog, so promoting a new default endpoint in the catalog had no effect on the default chat path — chat kept answering with the .env model. Only setting a partition's chat_llm explicitly worked (that path goes through llm_factory(name)).

Fix

services/orchestrators/query_service.py:

  • The default path now resolves the live catalog default via llm_factory("default") (the is_default=True endpoint, kept in sync by ModelEndpointService.load_all() and cache-invalidated on every default change). Falls back to the static self._llm only when no factory is wired (unit tests) or the catalog has no default yet.
  • A stale/unknown partition chat_llm now also falls through to the catalog default (was the static .env client).
  • Split resolution into small helpers: _agreed_partition_chat_llm(), _default_llm(), _default_llm_name().
  • Logging (issue ask): every path now logs the resolved preset name at debug — including the default (Answering with the default chat_llm preset · chat_llm=<endpoint>) — so the answering chat LLM is identifiable from the logs.

Map-reduce relevancy (_infer_relevancy) stays deliberately pinned to the static default — pre-existing documented behavior, unchanged here.

Live verification

Two endpoints in the catalog (same host, distinct model_name). Partition test left on chat_llm=null the whole time; only the catalog default was flipped:

Catalog default _resolve_llm log Actual answer
Mistral (is_default) Answering with the default chat_llm preset · chat_llm=Mistral-Small-3.2-24B-Instruct-2506-FP8 · partitions=['test'] "Pong"
Luciole (after set-default) Answering with the default chat_llm preset · chat_llm=Luciole · partitions=['test'] "Je ne comprends pas la demande…"

The resolved LLM followed the catalog default (Mistral → Luciole) without touching the partition; the two different answers confirm a different endpoint actually served each request. Before this fix, the second row would still have resolved to the .env Mistral client.

Tests

tests/unit/services/orchestrators/test_query_service.py — updated the default-path assertions to the corrected contract and added coverage for _default_llm/_default_llm_name. Suite green (45 in the file, 394 across orchestrators). ruff check clean.

@Ahmath-Gadji Ahmath-Gadji added this to the v2.0.1 milestone Jul 22, 2026
@Ahmath-Gadji Ahmath-Gadji added the bug Something isn't working label Jul 22, 2026
@Ahmath-Gadji Ahmath-Gadji self-assigned this Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Ahmath-Gadji, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5185b205-fb24-4fe7-a271-efdc18bd0118

📥 Commits

Reviewing files that changed from the base of the PR and between 45a8853 and 26352e1.

📒 Files selected for processing (2)
  • openrag/services/orchestrators/query_service.py
  • tests/unit/services/orchestrators/test_query_service.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/754-chat-llm-default-endpoint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Ahmath-Gadji
Ahmath-Gadji requested review from andyne13 and hedhoud July 22, 2026 13:19
Comment thread openrag/services/orchestrators/query_service.py Outdated
…oint

_resolve_llm's default branches returned the static self._llm — a client
built once at startup from settings.llm (the .env LLM_ENDPOINT/LLM_MODEL)
— so promoting a new default endpoint in the model-endpoint catalog had no
effect on the default chat path. Route the default through
llm_factory("default") (the live is_default endpoint, cache-invalidated on
every default change), falling back to the static client only when no
factory is wired (tests) or the catalog has no default yet.

Also log the resolved preset name on every path, including the default, so
the answering chat LLM is identifiable from the logs.

Closes #754
The default-path change makes chat answering use the catalog default while
_infer_relevancy stays on the static self._llm, so the two diverge. The
docstring claimed map-reduce "stays on the catalog default" (wrong) and the
_infer_relevancy comment ("pinned to the default LLM") was now ambiguous.
State the divergence explicitly in both.
@Ahmath-Gadji
Ahmath-Gadji force-pushed the fix/754-chat-llm-default-endpoint branch from 3832c36 to 26352e1 Compare July 22, 2026 13:42
@Ahmath-Gadji
Ahmath-Gadji merged commit 4b596a8 into develop Jul 22, 2026
6 checks passed
@Ahmath-Gadji
Ahmath-Gadji deleted the fix/754-chat-llm-default-endpoint branch July 22, 2026 13:51
Ahmath-Gadji added a commit that referenced this pull request Jul 22, 2026
check_llm_model_availability ran as a Depends() on /v1/chat/completions
and /v1/completions, probing config.llm's /v1/models before every request.
It only ever validated the default (.env) endpoint, so once a partition's
chat_llm preset — or a non-.env catalog default (#755) — answers a request,
it was checking the wrong endpoint: a partition on a healthy custom LLM got
falsely rejected (503/404) whenever the default endpoint happened to be
down, and its "Validating model" log misattributed the answering model,
contradicting _resolve_llm's own "which chat_llm answered" log.

The liveness it emulated already exists against the actually-resolved
endpoint: the generation call wraps upstream failures into
InferenceConnectionError / InferenceTimeoutError / InferenceError
(503/504/502), surfaced by the global exception handler (non-streaming)
and the streaming handler's SSE error path. Removing the preflight loses
no error contract (no test pinned the 404/503 behaviour) and drops a
per-request /v1/models round-trip on the healthy path.

get_openai_models stays — the per-endpoint token preflight (_fetch_max_model_tokens) still uses it.
Ahmath-Gadji added a commit that referenced this pull request Jul 23, 2026
check_llm_model_availability ran as a Depends() on /v1/chat/completions
and /v1/completions, probing config.llm's /v1/models before every request.
It only ever validated the default (.env) endpoint, so once a partition's
chat_llm preset — or a non-.env catalog default (#755) — answers a request,
it was checking the wrong endpoint: a partition on a healthy custom LLM got
falsely rejected (503/404) whenever the default endpoint happened to be
down, and its "Validating model" log misattributed the answering model,
contradicting _resolve_llm's own "which chat_llm answered" log.

The liveness it emulated already exists against the actually-resolved
endpoint: the generation call wraps upstream failures into
InferenceConnectionError / InferenceTimeoutError / InferenceError
(503/504/502), surfaced by the global exception handler (non-streaming)
and the streaming handler's SSE error path. Removing the preflight loses
no error contract (no test pinned the 404/503 behaviour) and drops a
per-request /v1/models round-trip on the healthy path.

get_openai_models stays — the per-endpoint token preflight (_fetch_max_model_tokens) still uses it.
Ahmath-Gadji added a commit that referenced this pull request Jul 24, 2026
Mirrors QueryService._resolve_llm/_default_llm (#755): log (debug) which
reranker resolved for a partition — its own preset, the catalog default, or
the static startup reranker — so "which reranker ran?" is answerable from
logs the same way chat_llm already is.

Also closes the same gap #755 fixed for chat_llm, on the reranker side: a
partition's `reranker` preset has no create/PATCH-time validation, so a
renamed/deleted endpoint reaching `_reranker_factory` raised an unhandled
KeyError instead of falling back to the catalog default (and then the static
reranker if that's missing too).
Ahmath-Gadji added a commit that referenced this pull request Jul 30, 2026
Mirrors QueryService._resolve_llm/_default_llm (#755): log (debug) which
reranker resolved for a partition — its own preset, the catalog default, or
the static startup reranker — so "which reranker ran?" is answerable from
logs the same way chat_llm already is.

Also closes the same gap #755 fixed for chat_llm, on the reranker side: a
partition's `reranker` preset has no create/PATCH-time validation, so a
renamed/deleted endpoint reaching `_reranker_factory` raised an unhandled
KeyError instead of falling back to the catalog default (and then the static
reranker if that's missing too).
Ahmath-Gadji added a commit that referenced this pull request Jul 30, 2026
RetrievalService now resolves a partition's reranker the same way
QueryService._resolve_llm/_default_llm (#755) resolves chat_llm: the
partition's configured preset first, falling back to the catalog
default, then the static startup reranker, with the resolved endpoint
logged at debug — so "which reranker ran?" is answerable from logs the
way chat_llm already is.

This also closes the gap #755 fixed for chat_llm, on the reranker side:
a partition's `reranker` preset has no create/PATCH-time validation, so
a renamed/deleted endpoint reaching `_reranker_factory` raised an
unhandled KeyError instead of falling back to the catalog default (and
then the static reranker if that's missing too).

The fallback warning binds reranker/partition as structured Loguru
context via `.bind()` rather than passing them as message-format kwargs
— passed directly to `logger.warning(msg, key=val)`, they were silently
dropped since the message has no `{}` placeholders to substitute into,
so the log never actually recorded which reranker/partition triggered
the fallback.
Ahmath-Gadji added a commit that referenced this pull request Jul 30, 2026
RetrievalService now resolves a partition's reranker the same way
QueryService._resolve_llm/_default_llm (#755) resolves chat_llm: the
partition's configured preset first, falling back to the catalog
default, then the static startup reranker, with the resolved endpoint
logged at debug — so "which reranker ran?" is answerable from logs the
way chat_llm already is.

This also closes the gap #755 fixed for chat_llm, on the reranker side:
a partition's `reranker` preset has no create/PATCH-time validation, so
a renamed/deleted endpoint reaching `_reranker_factory` raised an
unhandled KeyError instead of falling back to the catalog default (and
then the static reranker if that's missing too).

The fallback warning binds reranker/partition as structured Loguru
context via `.bind()` rather than passing them as message-format kwargs
— passed directly to `logger.warning(msg, key=val)`, they were silently
dropped since the message has no `{}` placeholders to substitute into,
so the log never actually recorded which reranker/partition triggered
the fallback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default chat LLM ignores the catalog default endpoint — pinned to the .env (settings.llm) client

2 participants