Skip to content

refactor(agentic): make agentic retrieval a self-contained module - #2407

Open
Reza-esfandiarpoor wants to merge 4 commits into
mainfrom
agent_refactor
Open

refactor(agentic): make agentic retrieval a self-contained module#2407
Reza-esfandiarpoor wants to merge 4 commits into
mainfrom
agent_refactor

Conversation

@Reza-esfandiarpoor

Copy link
Copy Markdown

Description

Replaces the inline ReAct/selection loops in the graph operators with a
self-contained nemo_agent library (src/nemo_retriever/_agentic/nemo_agent/),
and reconciles it with main's CLI/config layer and in-process local-vLLM support.

What's new

  • nemo_agent library: Agent (ReAct) + SelectionAgent, retrieve/end/think
    tools, Jinja prompts, and an LLM backend layer behind one _completion_impl
    seam.
  • Backends: litellm (remote) and callable — the latter adapts any
    OpenAI-compatible completion callable, used to wrap main's in-process vLLM
    (VLLMAgentChatLLM).
  • Config: two axes — llm_backend (transport: openai_compatible/in_process,
    inferred from --agentic-invoke-url, similar to main, not changed) and llm_client (implementation), resolved
    in AgenticRetrievalConfig.__post_init__ (detected automatically if not set).
  • The operators are now thin adapters.

CLI

  • Removed --agentic-backend-top-k (query.agentic_backend_top_k) — the agent now
    sets retrieval breadth per tool-call.
  • Added --agentic-llm-client (callable local default / litellm remote default) - Optional.
  • --agentic-temperature now defaults to unset (endpoint/model default) instead of 0.0.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Signed-off-by: Reza Esfandiarpoor <resfandiarpo@nvidia.com>
@Reza-esfandiarpoor
Reza-esfandiarpoor requested review from a team as code owners July 24, 2026 23:47
@Reza-esfandiarpoor
Reza-esfandiarpoor requested a review from edknv July 24, 2026 23:47
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the two inline ReAct/selection loops in ReActAgentOperator and SelectionAgentOperator with a self-contained nemo_agent library under _agentic/nemo_agent/, reconciling it with the existing CLI/config layer and in-process local-vLLM support. The operators become thin adapters, and the agent loop, prompts, tool specs, LLM backends, and usage tracking are now consolidated in the new library.

  • New nemo_agent library: Agent (ReAct) + SelectionAgent, three LLM backends (openai_http, litellm, callable), Jinja prompts, and a backend registry — all with full SPDX headers and docstrings.
  • Config updates: AgenticRetrievalConfig gains llm_client and removes backend_top_k; AGENTIC_TEMPERATURE and AGENTIC_PARALLEL_TOOL_CALLS change to None (endpoint defaults); the __post_init__ resolver auto-selects callable for in-process and openai_http for remote runs.
  • Operator cleanup: both operators now build agents lazily, expose close() for resource teardown, and are properly destroyed in a finally block inside AgenticRetriever._retrieve.

Confidence Score: 4/5

Safe to merge once AgenticRetrievalConfig.backend_top_k removal is handled via a deprecation shim, since any existing caller passing that field will get a silent TypeError.

The refactor is well-structured and the new nemo_agent library is clean, but AgenticRetrievalConfig.backend_top_k was removed outright from a public dataclass without a migration path. Code that constructs AgenticRetrievalConfig(backend_top_k=N) will fail at runtime with no warning.

Files Needing Attention: nemo_retriever/src/nemo_retriever/query/agentic.py — removal of backend_top_k from the public AgenticRetrievalConfig dataclass and the parallel_tool_calls default change both need attention before merge.

Important Files Changed

Filename Overview
nemo_retriever/src/nemo_retriever/_agentic/nemo_agent/agent.py New Agent class implementing the ReAct loop with retrieval-specific bookkeeping; well-structured and documented.
nemo_retriever/src/nemo_retriever/_agentic/nemo_agent/loop.py Shared agent loop engine; bare except Exception: in tool-argument parsing silently swallows non-JSON exceptions without logging.
nemo_retriever/src/nemo_retriever/query/agentic.py Removes backend_top_k field from the public AgenticRetrievalConfig dataclass without a deprecation cycle; changes AGENTIC_PARALLEL_TOOL_CALLS from False to None, silently altering parallel-tool-call behavior.
nemo_retriever/src/nemo_retriever/operators/graph_ops/react_agent_operator.py Greatly simplified to a thin adapter over the new Agent; lazy init, proper close(), and batch ordering are all correct.
nemo_retriever/src/nemo_retriever/operators/graph_ops/selection_agent_operator.py Replaced inline LLM loop with SelectionAgent; three-tier gate (final_results → selection_agent → rrf) is clearly structured with proper resource cleanup.
nemo_retriever/src/nemo_retriever/cli/query/options.py Removes AgenticBackendTopKOption (CLI argument removal flagged in prior review) and adds AgenticLlmClientOption.
nemo_retriever/src/nemo_retriever/_agentic/nemo_agent/llm/init.py Clean backend registry with create_llm, create_llm_config, and get_available_backends; unknown-backend errors are informative.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant AgenticRetriever
    participant ReActAgentOperator
    participant Agent
    participant BaseLLMBackend
    participant RetrieveTool
    participant SelectionAgentOperator
    participant SelectionAgent

    Caller->>AgenticRetriever: retrieve(query_texts, query_ids)
    AgenticRetriever->>ReActAgentOperator: process(DataFrame)
    loop Per query (ThreadPoolExecutor)
        ReActAgentOperator->>Agent: run_sync(query, query_id)
        loop ReAct steps (max_steps)
            Agent->>BaseLLMBackend: acompletion(messages, tools)
            BaseLLMBackend-->>Agent: CompletionResult
            alt "tool_calls = retrieve"
                Agent->>RetrieveTool: acall(query, top_k)
                RetrieveTool-->>Agent: "[{id, score, text}]"
            else "tool_calls = final_results"
                Agent-->>Agent: record end_payload, break
            end
        end
        Agent-->>ReActAgentOperator: AgentRunResult
    end
    ReActAgentOperator-->>AgenticRetriever: exploded DataFrame
    AgenticRetriever->>SelectionAgentOperator: process(rrf_ranked DataFrame)
    loop Per query_id group
        alt has react final_results
            SelectionAgentOperator-->>SelectionAgentOperator: pass through final_doc_ids
        else run selection agent
            SelectionAgentOperator->>SelectionAgent: select_sync(query, docs)
            SelectionAgent->>BaseLLMBackend: acompletion(messages, tools)
            BaseLLMBackend-->>SelectionAgent: CompletionResult
            SelectionAgent-->>SelectionAgentOperator: AgentRunResult
        else fallback
            SelectionAgentOperator-->>SelectionAgentOperator: top RRF-ranked docs
        end
    end
    SelectionAgentOperator-->>AgenticRetriever: ranked DataFrame
    AgenticRetriever-->>Caller: AgenticResult
Loading

Reviews (2): Last reviewed commit: "remove nim stub" | Re-trigger Greptile

Comment thread nemo_retriever/src/nemo_retriever/_agentic/nemo_agent/llm/base_backend.py Outdated
extended_relevance: bool = False,
max_steps: int = 10,
max_steps: int = 200,
num_concurrent: int = 8,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 max_steps default inconsistent with config-layer default

The operator defaults max_steps=200, but AgenticRetrievalConfig defaults to react_max_steps=50 and always passes its value explicitly. A caller constructing ReActAgentOperator directly (e.g., in tests or custom pipelines) will get 200 steps when the documentation elsewhere suggests 50. These two defaults should be reconciled — either expose a shared constant or document the discrepancy explicitly in the docstring so integrators are not surprised.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemo_retriever/src/nemo_retriever/operators/graph_ops/react_agent_operator.py
Line: 148

Comment:
**`max_steps` default inconsistent with config-layer default**

The operator defaults `max_steps=200`, but `AgenticRetrievalConfig` defaults to `react_max_steps=50` and always passes its value explicitly. A caller constructing `ReActAgentOperator` directly (e.g., in tests or custom pipelines) will get 200 steps when the documentation elsewhere suggests 50. These two defaults should be reconciled — either expose a shared constant or document the discrepancy explicitly in the docstring so integrators are not surprised.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread nemo_retriever/src/nemo_retriever/query/agentic_options.py Outdated
Comment thread nemo_retriever/src/nemo_retriever/_agentic/nemo_agent/agent.py Outdated
kwargs = {"completion_fn": self._chat_completion_fn} if self._chat_completion_fn is not None else {}
return create_llm(config, **kwargs)

def _retrieve_adapter(self, query: str, top_k: int) -> List[Dict[str, Any]]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we avoid get away without these dual keys for id across operator/agent core boundary. this way we can avoid adding this rename shim in the operators permanently.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same check from before this PR. I prefer to keep NRL-specific details the same as before and just focus on the agent implementation.

@Reza-esfandiarpoor

Copy link
Copy Markdown
Author

Evaluation results with changes in this PR. The reported numbers are average across three runs.

Dataset nDCG@10
HR 71.3
Finance En 73.4
Industrial 62.3
Pharma 73.8
CS 82.8
Energy 73.6
Physics 46.9
Finance FR 54.1
AVG 67.3

Command to reproduce these results:

retriever harness run "${DS}_beir" \
    --set query.agentic=true \
    --set query.agentic_llm_model=openai/nvidia/nvidia/nemotron-3-ultra \
    --set query.agentic_invoke_url=${AGENTIC_INVOKE_URL} \
    --set query.agentic_num_concurrent=8 \
    --set query.agentic_reasoning_effort=high \
    --set query.agentic_react_max_steps=200 \
    --set query.agentic_text_truncation=0 \
    --set ingest.embed.embed_model_name=nvidia/nvidia/nemotron-3-embed-1b \
    --set ingest.embed.embed_invoke_url=${EMBED_INVOKE_URL} \
    --set ingest.embed.embed_modality=text \
    --set ingest.embed.embed_granularity=page \
    --set query.embed_model_name=nvidia/nvidia/nemotron-3-embed-1b \
    --set query.embed_invoke_url=${EMBED_INVOKE_URL} \
    --set dataset.path=${CORPUS_PATH} \
    --set evaluation.query_language=english \
    --set ingest.extract.method=pdfium_hybrid \
    --set ingest.extract.ocr_lang=multi \
    --set ingest.extract.use_table_structure=true

@sahel-sh

Copy link
Copy Markdown
Contributor

Evaluation results with changes in this PR. The reported numbers are average across three runs.

Dataset nDCG@10
HR 71.3
Finance En 73.4
Industrial 62.3
Pharma 73.8
CS 82.8
Energy 73.6
Physics 46.9
Finance FR 54.1
AVG 67.3
Command to reproduce these results:

retriever harness run "${DS}_beir" \
    --set query.agentic=true \
    --set query.agentic_llm_model=openai/nvidia/nvidia/nemotron-3-ultra \
    --set query.agentic_invoke_url=${AGENTIC_INVOKE_URL} \
    --set query.agentic_num_concurrent=8 \
    --set query.agentic_reasoning_effort=high \
    --set query.agentic_react_max_steps=200 \
    --set query.agentic_text_truncation=0 \
    --set ingest.embed.embed_model_name=nvidia/nvidia/nemotron-3-embed-1b \
    --set ingest.embed.embed_invoke_url=${EMBED_INVOKE_URL} \
    --set ingest.embed.embed_modality=text \
    --set ingest.embed.embed_granularity=page \
    --set query.embed_model_name=nvidia/nvidia/nemotron-3-embed-1b \
    --set query.embed_invoke_url=${EMBED_INVOKE_URL} \
    --set dataset.path=${CORPUS_PATH} \
    --set evaluation.query_language=english \
    --set ingest.extract.method=pdfium_hybrid \
    --set ingest.extract.ocr_lang=multi \
    --set ingest.extract.use_table_structure=true

Thank you @Reza-esfandiarpoor this result summary is really helpful to confirm consistency with what we currently have in https://docs.google.com/spreadsheets/d/1TPcVZa_1dxnwrNltkMq63lmgGtOE_cij7TTjkX2te8w/edit?gid=0#gid=0 and reported in the release, just a couple of questions:
1- IIRC, we agreed on switching the ultera nvfp4 model, these runs are using the nemotron-3-ultra?
2- would it be possible to run a case with vllm reranking to make sure it still works after this MR is merged?
3- the ingestion here is using ocr? would our ingestion pipeline still work for embedding the documents and queries?
4- Does the vllm adaptor for retriever still work? This is needed for running the nemotron nvfp4 embeding model

@Reza-esfandiarpoor

Copy link
Copy Markdown
Author

@sahel-sh

Model used for the runs

Thanks for pointing it out. Yes, this is not the nvfp4 version. But it should be okay in this case. This run is to just show that the refactor in this PR is correct.

Ingestion + implementation of retrieval and reranking

The scope of this PR is limited to the implementation of the agent itself. Ingestion and the implementation of retrieval methods are provided by NRL (this code just wraps whatever retriever function it gets from NRL in an agent tool). How and where different reranking and embedding models are implemented and using what backend (e.g., vLLM) is outside of the scope of this PR. We can discuss that for future changes.

Signed-off-by: Reza Esfandiarpoor <resfandiarpo@nvidia.com>
Signed-off-by: Reza Esfandiarpoor <resfandiarpo@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: Reza Esfandiarpoor <resfandiarpo@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants