From 7be49209ea76a53a1b47a4138c39c7770b76c13c Mon Sep 17 00:00:00 2001 From: wizhu Date: Fri, 14 Aug 2026 01:19:44 +0800 Subject: [PATCH 1/2] Fix Helm reranker endpoint configuration Signed-off-by: wizhu --- nemo_retriever/helm/README.md | 2 + nemo_retriever/helm/templates/configmap.yaml | 2 + nemo_retriever/helm/values.yaml | 2 + .../tests/test_helm_rerank_endpoint.py | 37 +++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 nemo_retriever/tests/test_helm_rerank_endpoint.py diff --git a/nemo_retriever/helm/README.md b/nemo_retriever/helm/README.md index 0cbf88db3..6449c05bd 100644 --- a/nemo_retriever/helm/README.md +++ b/nemo_retriever/helm/README.md @@ -325,6 +325,8 @@ The retriever service picks up the in-cluster ASR endpoint when `nimOperator.aud | `serviceConfig.pipeline.batchWorkers` | `48` | Per-pod batch worker count. Refer to [Timeouts and alleviating ingest failures](#timeouts-and-alleviating-ingest-failures) if embed or pool errors appear under load. | | `serviceConfig.resources.maxUploadBytes` | `500000000` | Maximum upload file size in bytes; requests exceeding the limit are rejected before buffering. | | `serviceConfig.nimEndpoints.*InvokeUrl` | `""` | Override the auto-resolved NIM Operator URL. Available knobs: `pageElementsInvokeUrl`, `tableStructureInvokeUrl`, `ocrInvokeUrl`, `embedInvokeUrl`, and `captionInvokeUrl` (refer to [Image captioning (Omni 30B)](#image-captioning-omni-30b)). | +| `serviceConfig.nimEndpoints.rerankInvokeUrl` | `""` | `/v1/ranking` endpoint used by `POST /v1/query` when `rerank=true`. This value is not auto-resolved from `nimOperator.rerankqa`. | +| `serviceConfig.nimEndpoints.rerankModelName` | `""` | Model id sent to `rerankInvokeUrl`. | | `serviceConfig.nimEndpoints.captionModelName` | `""` | Model id sent to the remote VLM. Auto-set to `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning` whenever a caption URL is resolved. | | `serviceConfig.llm.enabled` | `false` | Enables `POST /v1/answer`. Auto-flips to true when `nimOperator.answer_llm` is enabled and the operator URL resolves. | | `serviceConfig.llm.apiBase` | `""` | OpenAI-compatible LLM base URL. Explicit value wins; otherwise `answer_llm` opt-in resolves to `http://answer-llm:8000/v1` by default. | diff --git a/nemo_retriever/helm/templates/configmap.yaml b/nemo_retriever/helm/templates/configmap.yaml index 4a170089b..063767dd3 100644 --- a/nemo_retriever/helm/templates/configmap.yaml +++ b/nemo_retriever/helm/templates/configmap.yaml @@ -109,6 +109,8 @@ nim_endpoints: {{- else }} embed_model_provider_prefix: null {{- end }} + rerank_invoke_url: {{ if .Values.serviceConfig.nimEndpoints.rerankInvokeUrl }}{{ .Values.serviceConfig.nimEndpoints.rerankInvokeUrl | quote }}{{ else }}null{{ end }} + rerank_model_name: {{ if .Values.serviceConfig.nimEndpoints.rerankModelName }}{{ .Values.serviceConfig.nimEndpoints.rerankModelName | quote }}{{ else }}null{{ end }} caption_invoke_url: {{ if .captionURL }}{{ .captionURL | quote }}{{ else }}null{{ end }} caption_model_name: {{ if .captionModelName }}{{ .captionModelName | quote }}{{ else }}null{{ end }} audio_grpc_endpoint: {{ if .audioGrpcEndpoint }}{{ .audioGrpcEndpoint | quote }}{{ else }}null{{ end }} diff --git a/nemo_retriever/helm/values.yaml b/nemo_retriever/helm/values.yaml index 52c12b4cd..71528def1 100644 --- a/nemo_retriever/helm/values.yaml +++ b/nemo_retriever/helm/values.yaml @@ -552,6 +552,8 @@ serviceConfig: # nvidia/nemotron-parse or self-hosted v1.2 contract from the URL. nemotronParseModel: "" embedInvokeUrl: "" + rerankInvokeUrl: "" + rerankModelName: "" # Optional remote VLM endpoint for image captioning (Nemotron 3 Nano # Omni). Auto-wired from the in-cluster Service when # `nimOperator.nemotron_3_nano_omni_30b_a3b_reasoning.enabled=true` diff --git a/nemo_retriever/tests/test_helm_rerank_endpoint.py b/nemo_retriever/tests/test_helm_rerank_endpoint.py new file mode 100644 index 000000000..a370abad0 --- /dev/null +++ b/nemo_retriever/tests/test_helm_rerank_endpoint.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Helm wiring for remote reranker configuration.""" + +import pytest +import yaml + +from tests.test_helm_shared_results import _render + + +@pytest.mark.parametrize( + ("topology_args", "expected_modes"), + (((), {"standalone"}), (("--set", "topology.mode=split"), {"gateway", "realtime", "batch"})), +) +def test_rerank_endpoint_is_rendered_in_each_service_config(topology_args, expected_modes) -> None: + url = "http://reranker.example:8000/v1/ranking" + model = "nvidia/llama-nemotron-rerank-vl-1b-v2" + documents = _render( + *topology_args, + "--set-string", + f"serviceConfig.nimEndpoints.rerankInvokeUrl={url}", + "--set-string", + f"serviceConfig.nimEndpoints.rerankModelName={model}", + ) + + configs = [ + yaml.safe_load(document["data"]["retriever-service.yaml"]) + for document in documents + if document.get("kind") == "ConfigMap" and "retriever-service.yaml" in document.get("data", {}) + ] + + assert {config["mode"] for config in configs} == expected_modes + for config in configs: + assert config["nim_endpoints"]["rerank_invoke_url"] == url + assert config["nim_endpoints"]["rerank_model_name"] == model From a99c7569d1b7d46ac7073e5465fbeba0e28d0b1f Mon Sep 17 00:00:00 2001 From: wizhu Date: Fri, 14 Aug 2026 01:56:12 +0800 Subject: [PATCH 2/2] Validate Helm reranker model configuration --- nemo_retriever/helm/README.md | 2 +- nemo_retriever/helm/templates/configmap.yaml | 5 +++ .../tests/test_helm_rerank_endpoint.py | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/nemo_retriever/helm/README.md b/nemo_retriever/helm/README.md index 6449c05bd..9c16d0592 100644 --- a/nemo_retriever/helm/README.md +++ b/nemo_retriever/helm/README.md @@ -326,7 +326,7 @@ The retriever service picks up the in-cluster ASR endpoint when `nimOperator.aud | `serviceConfig.resources.maxUploadBytes` | `500000000` | Maximum upload file size in bytes; requests exceeding the limit are rejected before buffering. | | `serviceConfig.nimEndpoints.*InvokeUrl` | `""` | Override the auto-resolved NIM Operator URL. Available knobs: `pageElementsInvokeUrl`, `tableStructureInvokeUrl`, `ocrInvokeUrl`, `embedInvokeUrl`, and `captionInvokeUrl` (refer to [Image captioning (Omni 30B)](#image-captioning-omni-30b)). | | `serviceConfig.nimEndpoints.rerankInvokeUrl` | `""` | `/v1/ranking` endpoint used by `POST /v1/query` when `rerank=true`. This value is not auto-resolved from `nimOperator.rerankqa`. | -| `serviceConfig.nimEndpoints.rerankModelName` | `""` | Model id sent to `rerankInvokeUrl`. | +| `serviceConfig.nimEndpoints.rerankModelName` | `""` | Model id sent to `rerankInvokeUrl`; setting a model requires a non-empty reranker URL. | | `serviceConfig.nimEndpoints.captionModelName` | `""` | Model id sent to the remote VLM. Auto-set to `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning` whenever a caption URL is resolved. | | `serviceConfig.llm.enabled` | `false` | Enables `POST /v1/answer`. Auto-flips to true when `nimOperator.answer_llm` is enabled and the operator URL resolves. | | `serviceConfig.llm.apiBase` | `""` | OpenAI-compatible LLM base URL. Explicit value wins; otherwise `answer_llm` opt-in resolves to `http://answer-llm:8000/v1` by default. | diff --git a/nemo_retriever/helm/templates/configmap.yaml b/nemo_retriever/helm/templates/configmap.yaml index 063767dd3..582bd1311 100644 --- a/nemo_retriever/helm/templates/configmap.yaml +++ b/nemo_retriever/helm/templates/configmap.yaml @@ -20,6 +20,11 @@ inherits the NIMService resource name, so the mapping is fixed: {{- $auth := $ctx.Values.serviceConfig.auth -}} {{- $scopeTokenSecret := $auth.scopeTokenSecret -}} {{- $internalAuth := $ctx.Values.serviceConfig.vectordb.internalAuth -}} +{{- $rerankInvokeURL := $ctx.Values.serviceConfig.nimEndpoints.rerankInvokeUrl | default "" | trim -}} +{{- $rerankModelName := $ctx.Values.serviceConfig.nimEndpoints.rerankModelName | default "" | trim -}} +{{- if and $rerankModelName (not $rerankInvokeURL) -}} +{{- fail "serviceConfig.nimEndpoints.rerankModelName requires serviceConfig.nimEndpoints.rerankInvokeUrl." -}} +{{- end -}} {{- if and $auth.enabled (not $auth.apiToken) (not $scopeTokenSecret.name) -}} {{- fail "serviceConfig.auth.enabled=true requires serviceConfig.auth.scopeTokenSecret.name or serviceConfig.auth.apiToken." -}} {{- end -}} diff --git a/nemo_retriever/tests/test_helm_rerank_endpoint.py b/nemo_retriever/tests/test_helm_rerank_endpoint.py index a370abad0..ddc4c3a43 100644 --- a/nemo_retriever/tests/test_helm_rerank_endpoint.py +++ b/nemo_retriever/tests/test_helm_rerank_endpoint.py @@ -4,6 +4,8 @@ """Helm wiring for remote reranker configuration.""" +import subprocess + import pytest import yaml @@ -35,3 +37,36 @@ def test_rerank_endpoint_is_rendered_in_each_service_config(topology_args, expec for config in configs: assert config["nim_endpoints"]["rerank_invoke_url"] == url assert config["nim_endpoints"]["rerank_model_name"] == model + + +def test_rerank_endpoint_allows_url_without_model() -> None: + url = "http://reranker.example:8000/v1/ranking" + documents = _render( + "--set-string", + f"serviceConfig.nimEndpoints.rerankInvokeUrl={url}", + ) + + config = next( + yaml.safe_load(document["data"]["retriever-service.yaml"]) + for document in documents + if document.get("kind") == "ConfigMap" and "retriever-service.yaml" in document.get("data", {}) + ) + + assert config["nim_endpoints"]["rerank_invoke_url"] == url + assert config["nim_endpoints"]["rerank_model_name"] is None + + +@pytest.mark.parametrize("endpoint", (None, " ")) +def test_rerank_model_without_endpoint_fails_rendering(endpoint: str | None) -> None: + args = [] if endpoint is None else ["--set-string", f"serviceConfig.nimEndpoints.rerankInvokeUrl={endpoint}"] + with pytest.raises(subprocess.CalledProcessError) as error: + _render( + *args, + "--set-string", + "serviceConfig.nimEndpoints.rerankModelName=nvidia/llama-nemotron-rerank-vl-1b-v2", + ) + + assert ( + "serviceConfig.nimEndpoints.rerankModelName requires serviceConfig.nimEndpoints.rerankInvokeUrl" + in error.value.stderr + )