LLM Based Task#2283
Conversation
Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
Greptile SummaryThis PR introduces a reusable
|
| Filename | Overview |
|---|---|
| nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py | New RagAnswerTask with prompt validation that permits zero-placeholder templates, silently discarding context and query at inference time. |
| nemo_retriever/src/nemo_retriever/models/llm/tasks/base.py | New TextGenerationTask/execute lifecycle; well-structured phase separation and error codes. Bare except-blocks in invoke() without exc_info were flagged in prior review cycles. |
| nemo_retriever/src/nemo_retriever/operators/generation/base.py | New TextGenerationOperator base; positional row tracking, overwrite guards, and graph-safe constructor kwarg validation look solid. |
| nemo_retriever/src/nemo_retriever/common/params/models.py | Adds TextGenerationParams, LLMSamplingOverrides, environment-reference handling, and secret-redaction helpers. Extra-params protection and _auto_resolve_unset_api_keys split look correct. |
| nemo_retriever/src/nemo_retriever/models/llm/clients/litellm.py | Refactored to delegate RAG logic to RagAnswerTask and the new TextCompletionClient contract. Response validation is improved. Redundant extra_params validation on transport at call time is minor overhead. |
| nemo_retriever/src/nemo_retriever/graph/graph_pipeline_registry.py | Significant expansion of graph serialization with credential-aware encoding, env-reference handling, and lossless Pydantic round-tripping. The secret-redaction heuristics look thorough. |
| nemo_retriever/src/nemo_retriever/tools/evaluation/generation.py | QAGenerationOperator migrated to TextGenerationOperator base with legacy-client adapter; reasoning_enabled now forwarded correctly to legacy generate() callers. |
| nemo_retriever/tests/test_generation_tasks.py | Comprehensive tests covering lifecycle phases, error codes, prompt validation, and backward compatibility. No test for zero-placeholder RAG prompt behavior. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant Op as TextGenerationOperator
participant Task as TextGenerationTask
participant Client as TextCompletionClient
Caller->>Op: process(DataFrame)
Op->>Op: _validate_and_resolve_dataframe()
loop per row (ThreadPoolExecutor)
Op->>Op: _execute_row(position, inputs)
Op->>Task: "invoke(client, **inputs)"
Task->>Task: "_preflight_error(**inputs)"
Task->>Task: "build_request(**inputs) → GenerationRequest"
Task->>Client: complete(messages, max_tokens, extra_params)
Client-->>Task: (raw_text, latency_s)
Task->>Task: parse(raw_text) → text
Task-->>Op: GeneratedTextResult
end
Op-->>Caller: DataFrame with output/latency/model/error columns
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller
participant Op as TextGenerationOperator
participant Task as TextGenerationTask
participant Client as TextCompletionClient
Caller->>Op: process(DataFrame)
Op->>Op: _validate_and_resolve_dataframe()
loop per row (ThreadPoolExecutor)
Op->>Op: _execute_row(position, inputs)
Op->>Task: "invoke(client, **inputs)"
Task->>Task: "_preflight_error(**inputs)"
Task->>Task: "build_request(**inputs) → GenerationRequest"
Task->>Client: complete(messages, max_tokens, extra_params)
Client-->>Task: (raw_text, latency_s)
Task->>Task: parse(raw_text) → text
Task-->>Op: GeneratedTextResult
end
Op-->>Caller: DataFrame with output/latency/model/error columns
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py:43-47
`_validate_rag_prompt` validates that only `{context}` and `{query}` appear, but doesn't require either to be present. A prompt like `"Here is my answer."` passes validation, and `build_request` then calls `self.prompt.format(context=..., query=...)` which silently returns the static string — both the retrieved context and the user query are discarded. Compare with `GenericPromptTask._validate_prompt_template`, which explicitly raises if `fields` is empty. The fix is to require at least one allowed placeholder.
```suggestion
used_fields: set[str] = set()
for _, field_name, format_spec, conversion in parsed_fields:
if field_name is None:
continue
if field_name not in {"context", "query"} or format_spec or conversion:
raise ValueError("RAG prompt may only use simple {context} and {query} placeholders")
used_fields.add(field_name)
if not used_fields:
raise ValueError("RAG prompt must contain at least one {context} or {query} placeholder")
```
Reviews (5): Last reviewed commit: "Merge branch 'main' into feature/llm-tas..." | Re-trigger Greptile
Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
| model_config = ConfigDict(extra="forbid") | ||
| model_config = ConfigDict(extra="forbid", hide_input_in_errors=True) | ||
|
|
||
| # Keep the explicit no-auth intent after NO_API_KEY is normalized to |
There was a problem hiding this comment.
nit: do we want to update it here too for the api_key=None behaviour and gen error codes nemo_retriever/src/nemo_retriever/tools/evaluation/README.md
| _SAMPLING_UNSET = object() | ||
|
|
||
|
|
||
| class TextGenerationParams(_ParamsModel): |
There was a problem hiding this comment.
another nit: do you think it might be useful for non-secret generation config like temperature, top_p, max_tokens, and reasoning_enabled be emitted into eval metadata/traces so runs are easier to compare (if that's the goal with this infra)?
| @@ -0,0 +1,206 @@ | |||
| # SPDX-FileCopyrightText: Copyright (c) 2024-26, NVIDIA CORPORATION & AFFILIATES. | |||
| # All rights reserved. | |||
There was a problem hiding this comment.
if this is envisioned to be generation specific base let's rename it to something more reflective like TextGenerationTask
mahikaw
left a comment
There was a problem hiding this comment.
looks good, added some comments that might lead to minor refactoring.
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
a111c3f to
2899a87
Compare
| for _, field_name, format_spec, conversion in parsed_fields: | ||
| if field_name is None: | ||
| continue | ||
| if field_name not in {"context", "query"} or format_spec or conversion: | ||
| raise ValueError("RAG prompt may only use simple {context} and {query} placeholders") |
There was a problem hiding this comment.
_validate_rag_prompt validates that only {context} and {query} appear, but doesn't require either to be present. A prompt like "Here is my answer." passes validation, and build_request then calls self.prompt.format(context=..., query=...) which silently returns the static string — both the retrieved context and the user query are discarded. Compare with GenericPromptTask._validate_prompt_template, which explicitly raises if fields is empty. The fix is to require at least one allowed placeholder.
| for _, field_name, format_spec, conversion in parsed_fields: | |
| if field_name is None: | |
| continue | |
| if field_name not in {"context", "query"} or format_spec or conversion: | |
| raise ValueError("RAG prompt may only use simple {context} and {query} placeholders") | |
| used_fields: set[str] = set() | |
| for _, field_name, format_spec, conversion in parsed_fields: | |
| if field_name is None: | |
| continue | |
| if field_name not in {"context", "query"} or format_spec or conversion: | |
| raise ValueError("RAG prompt may only use simple {context} and {query} placeholders") | |
| used_fields.add(field_name) | |
| if not used_fields: | |
| raise ValueError("RAG prompt must contain at least one {context} or {query} placeholder") |
Prompt To Fix With AI
This is a comment left during a code review.
Path: nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py
Line: 43-47
Comment:
`_validate_rag_prompt` validates that only `{context}` and `{query}` appear, but doesn't require either to be present. A prompt like `"Here is my answer."` passes validation, and `build_request` then calls `self.prompt.format(context=..., query=...)` which silently returns the static string — both the retrieved context and the user query are discarded. Compare with `GenericPromptTask._validate_prompt_template`, which explicitly raises if `fields` is empty. The fix is to require at least one allowed placeholder.
```suggestion
used_fields: set[str] = set()
for _, field_name, format_spec, conversion in parsed_fields:
if field_name is None:
continue
if field_name not in {"context", "query"} or format_spec or conversion:
raise ValueError("RAG prompt may only use simple {context} and {query} placeholders")
used_fields.add(field_name)
if not used_fields:
raise ValueError("RAG prompt must contain at least one {context} or {query} placeholder")
```
How can I resolve this? If you propose a fix, please make it concise.
Description
Checklist