Skip to content

fix: clarifier searches before asking clarification questions#245

Merged
cdgamarose-nv merged 15 commits into
NVIDIA-AI-Blueprints:developfrom
torkian:fix/clarifier-search-before-questions
Jul 7, 2026
Merged

fix: clarifier searches before asking clarification questions#245
cdgamarose-nv merged 15 commits into
NVIDIA-AI-Blueprints:developfrom
torkian:fix/clarifier-search-before-questions

Conversation

@torkian

@torkian torkian commented May 19, 2026

Copy link
Copy Markdown
Contributor

Closes #234.

Summary

The clarifier agent already had tool-calling bound to a search loop, but weak or non-frontier models routinely skipped the search and asked the user a clarification question immediately. The expected behavior — and what the issue asks for — is to gather context with the available tools first, and only ask the user once a genuine ambiguity remains.

This fixes the bug at the agent level (the issue explicitly says "this should not be overly prompt engineered for one model"), with a small prompt update as a supporting hint.

Changes

  • Inline search-before-clarify retry in agent_node. On the first turn, if the model returns a clarification request without using its bound search tools, the node nudges it once with FORCE_SEARCH_GUIDANCE and re-invokes the LLM inline, returning both the original response and the retry. No extra graph node or state field is needed.
  • One-shot by construction. The retry fires only when iteration == 0 (so it never fires after the user has replied) and not _has_tool_invocations(state.messages) (so once any tool call is in history — e.g. after a successful forced search, even while iteration is still 0 — it never re-nudges). A model that stubbornly refuses to search after the single nudge simply falls through to asking the user; no loop.
  • Guidance stays ephemeral. FORCE_SEARCH_GUIDANCE is sent only in the local retry_messages and is never returned to state, so it cannot leak into get_latest_user_query or other history lookups (which would otherwise surface internal scaffolding in fallback text).
  • prompts/research_clarification.j2 Tool Usage section rewritten as "Search first, ask second" so frontier models follow this voluntarily and skip the nudge entirely.

Design note: an earlier revision used a dedicated force_search graph node plus a ClarifierAgentState.force_search_used flag. Per review (thanks @cdgamarose-nv), this was simplified to the inline retry above — same behavior, ~70 fewer source lines, no added graph state.

How I verified (end-to-end, against a real LLM + web search)

I exercised the full clarifier graph against a real NVIDIA NIM-hosted LLM and a real Tavily web search tool, with model nvidia/nemotron-3-nano-30b-a3b (the repo default in configs/config_cli_default.yml) and the query "Research the latest NVIDIA AI announcements from 2026":

Branch tool_calls emitted user prompted?
develop 0 (bug — asked the user immediately) yes
this PR 1 (searched first) no

Deterministic across three runs. (The throwaway reproducer script is not included in the PR, per review — these are its results.)

Tests

150 clarifier tests pass. Key additions in tests/aiq_agent/agents/clarifier/test_agent.py:

  • TestHasToolInvocations — the helper that detects prior tool calls in the message history.
  • TestClarifierForceSearch:
    • test_force_search_fires_when_llm_skips_tools — the bug path: model clarifies → nudge + inline retry → model emits a tool call → completes; user never prompted.
    • test_force_search_skipped_when_no_tools — no behavior change when no tools are configured.
    • test_force_search_fires_at_most_once — no loop if the model stubbornly refuses tools after the nudge.
    • test_force_search_not_triggered_when_llm_searches_first — no extra detour for models that already search.
    • test_force_search_guidance_not_in_state_messages — regression test: the internal guidance must never appear in user-facing fallback text.
    • test_force_search_guidance_not_injected_after_user_reply — regression test: the nudge does not re-fire after the user has answered.
    • test_forced_retry_does_not_emit_consecutive_assistant_messages — regression test for the message-ordering fix below.
  • TestClarifierSkipMessageOrdering — regression tests for the skip-path ordering fix (persisted history + planner sequence under enable_plan_approval).

Message-ordering correctness (from review)

Per @cdgamarose-nv's review the implementation was simplified to an inline retry; follow-up review feedback then flagged that returning both the skipped and retried responses produced two consecutive assistant-role messages once the retry carried a tool call, which the OpenAI/Anthropic APIs reject with a 400 (NVIDIA NIM tolerates it). Fixed by returning only the retry response. Auditing the same invariant across the graph surfaced a separate pre-existing instance on the skip-clarification path (skip emitted a completion without persisting the user reply, and graph re-entry appended a duplicate completion) — also fixed, with regression tests for both.

Test plan

  • pytest tests/aiq_agent/agents/clarifier/ — 150/150 passing
  • ruff check and ruff format clean
  • Reproduces the bug on develop against the repo's default model
  • Verifies the fix on this branch against the same model + query
  • Maintainer smoke test via the CLI / Web UI workflows

Closes NVIDIA-AI-Blueprints#234.

The clarifier agent had tool-calling bound to a search loop, but weak or
non-frontier models routinely skipped the search and asked the user a
clarification question immediately. The expected behavior is to gather
context with the available tools first and only ask once a genuine
ambiguity remains.

This is fixed at the graph level so it works model-agnostically rather
than relying on prompt engineering for any one model:

- New force_search node in the LangGraph workflow. When the LLM tries
  to ask for clarification on the first turn, tools are configured, and
  no tool call has happened yet, decide_route sends the run through
  force_search before ask_for_clarification.
- Guidance is injected ephemerally in agent_node (same pattern as the
  existing JSON_REMINDER_AFTER_TOOLS) rather than written into
  state.messages, so it cannot leak into helpers like
  get_latest_user_query that surface text back to the user.
- A force_search_used boolean on ClarifierAgentState makes the nudge
  fire at most once per run, so a model that ignores the nudge falls
  through to ask_for_clarification instead of looping.
- prompts/research_clarification.j2 is updated so the Tool Usage
  section instructs the model to "Search first, ask second" for any
  unfamiliar entity, acronym, or term. Frontier models follow this
  voluntarily and skip the structural nudge entirely.

End-to-end reproducer added at scripts/e2e_clarifier_search_test.py.
Against nvidia/nemotron-3-nano-30b-a3b (the project's default
clarifier model) with a real Tavily web search tool:

  develop:  0 tool calls, user prompted immediately   (bug)
  fix:      1 tool call,  user never prompted          (fixed)

The mock-based unit suite covers the new helper, the four routing
branches, and the regression flagged during review where the guidance
message could otherwise leak into the invalid-format fallback text.
All 148 clarifier tests pass.

Signed-off-by: Torkian <torkian@mac.com>
@greptile-apps

greptile-apps Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the bug where the clarifier agent would prompt the user for clarification before using its bound search tools, by injecting an inline force-search retry inside agent_node on the first turn only. It also fixes two adjacent API-contract bugs: the skip-command branch now persists the user's HumanMessage before emitting AIMessage(complete), and a re-entry guard prevents a duplicate completion message from being appended when the clarification budget is already exhausted.

  • Force-search nudge: when iteration == 0 and no prior tool invocations exist and the model returns a clarification without tool calls, FORCE_SEARCH_GUIDANCE is injected ephemerally (not stored in state) and the LLM is retried once; only retry_response is returned to avoid consecutive assistant messages in history.
  • Skip-command fix: ask_clarification now returns [HumanMessage(user_reply), AIMessage(complete)] so the two assistant-role turns are always interleaved with a human turn.
  • Budget-exhaustion guard: agent_node detects when the last message is already a completion and returns {} instead of appending a second AIMessage(complete).

Confidence Score: 5/5

Safe to merge — the inline retry is tightly guarded by iteration and tool-invocation checks, the two API-contract bugs from prior review rounds are both addressed, and 147 tests including dedicated regression tests for each fix all pass.

The force-search nudge fires exactly once per conversation (gated on iteration==0 and absence of prior tool calls), is ephemeral (FORCE_SEARCH_GUIDANCE never enters state), and only retry_response is persisted — eliminating the consecutive-assistant-message violation. The skip-command HumanMessage interleaving and the budget-exhaustion early-exit guard close the other two previously identified issues. No new execution paths are left unguarded.

No files require special attention; the one note is a test-assertion coverage gap that does not affect production behavior.

Important Files Changed

Filename Overview
src/aiq_agent/agents/clarifier/agent.py Adds inline force-search retry in agent_node (iteration==0 guard + _has_tool_invocations check), fixes skip-command to persist HumanMessage before AIMessage(complete), and adds early-exit guard for budget-exhausted re-entry. Both previously-reported API-violation bugs are addressed.
src/aiq_agent/agents/clarifier/prompts/research_clarification.j2 Tool Usage section rewritten to 'Search first, ask second' with explicit rules for search-before-clarify; no logic changes.
tests/aiq_agent/agents/clarifier/test_agent.py Adds TestHasToolInvocations, TestClarifierForceSearch (6 scenarios), and TestClarifierSkipMessageOrdering (2 scenarios). Minor: the completion_ais >= 1 assertion in test_skip_persists_reply_and_no_consecutive_assistants is too broad and would not catch a missing completion message.

Reviews (5): Last reviewed commit: "Fix consecutive assistant messages on th..." | Re-trigger Greptile

Comment thread src/aiq_agent/agents/clarifier/agent.py Outdated
Comment thread scripts/e2e_clarifier_search_test.py Outdated
@cdgamarose-nv

Copy link
Copy Markdown
Collaborator

@torkian Thank you for your contribution. We will review soon.

torkian added 2 commits May 27, 2026 20:28
ruff (project config: force-single-line=true) flagged the combined
`from langchain_core.messages import HumanMessage, ToolMessage` line in
scripts/e2e_clarifier_search_test.py. Split it into two single-line
imports to match the rest of the codebase, and re-run ruff format.

Signed-off-by: Torkian <torkian@mac.com>
Greptile review on PR NVIDIA-AI-Blueprints#245 flagged two real issues:

P1 (agent.py): the ephemeral force_search guidance injection in
agent_node was missing the iteration==0 guard that decide_route already
applies. After force_search fired once and a stubborn model still
refused to call a tool, ask_for_clarification ran, the user replied,
and iteration advanced to 1 - but the next agent_node call still saw
force_search_used=True with no tool invocations in state.messages and
re-injected the nudge. The model then received "issue one focused tool
call now" immediately after the user's clarifying answer, causing a
gratuitous search instead of synthesizing the reply. Fixed by adding
state.iteration == 0 to the injection condition.

P2 (e2e script): the PASS verdict gated on tool_calls >= 1 only,
without verifying len(user_prompts) == 0. A run where the LLM both
searched AND prompted the user would have falsely printed "before any
user prompt" and exited 0. Now we require both conditions and print
distinct FAIL messages for the two failure modes.

Also cleaned up an unused mock side_effect in
test_force_search_guidance_not_in_state_messages (the user replies
"skip" which forces completion, so only two LLM calls actually happen),
and added test_force_search_guidance_not_injected_after_user_reply as a
regression test for the P1 fix. 149 clarifier tests pass.

Signed-off-by: Torkian <torkian@mac.com>
@torkian

torkian commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Addressed both findings in 8f18f80:

  • P1 (agent.py): added state.iteration == 0 to the agent_node ephemeral-injection condition so the nudge can't be re-applied after the user has already replied. Added test_force_search_guidance_not_injected_after_user_reply as a regression test.
  • P2 (e2e script): PASS verdict now requires both tool_calls >= 1 AND len(user_prompts) == 0, with distinct FAIL messages for the two failure modes.

Also cleaned up an unused mock side_effect in test_force_search_guidance_not_in_state_messages that the review flagged. 149 clarifier tests pass.

@cdgamarose-nv cdgamarose-nv left a comment

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.

Thanks for your PR! Left some comments

Comment thread scripts/e2e_clarifier_search_test.py Outdated
Comment thread src/aiq_agent/agents/clarifier/agent.py Outdated
Address review feedback on NVIDIA-AI-Blueprints#245: replace the dedicated force_search
graph node and the ClarifierAgentState.force_search_used flag with a
single inline retry inside agent_node.

When, on the first turn, the model asks for clarification without first
using its bound search tools, agent_node now nudges it once with
FORCE_SEARCH_GUIDANCE and re-invokes inline, returning both the original
response and the retry. This keeps the behavior model-agnostic without
adding graph state or an extra node hop.

The one-shot guarantee holds from two conditions alone, so the
force_search_used flag is no longer needed:
- iteration == 0: only fires on the first turn; once the user replies,
  iteration advances and it never fires again.
- not _has_tool_invocations(state.messages): once any tool call is in
  history (e.g. after a successful forced search, even while iteration
  is still 0), it never re-nudges.

FORCE_SEARCH_GUIDANCE is sent only in the local retry_messages and is
never returned to state, so it cannot leak into get_latest_user_query
or other history lookups.

Also remove scripts/e2e_clarifier_search_test.py from the PR per review;
the end-to-end test approach and results are documented in the PR
description instead.

Behavior is unchanged (same LLM call sequence and final message
history); all 147 clarifier tests pass, including the force-search
behavioral and ephemeral-guidance regression tests.

Signed-off-by: Torkian <torkian@mac.com>
@copy-pr-bot

copy-pr-bot Bot commented Jun 2, 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.

Comment thread src/aiq_agent/agents/clarifier/agent.py Outdated
torkian added 2 commits June 1, 2026 22:38
Greptile review on NVIDIA-AI-Blueprints#245: the inline search-before-clarify retry returned
both the skipped clarification response and the retry response,
{"messages": [response, retry_response]}. When the retry carries a tool
call (the happy path), the persisted history became
[..., AIMessage(clarification), AIMessage(tool_call), ToolMessage, ...]
— two consecutive assistant-role messages, which the OpenAI Chat
Completions and Anthropic Messages APIs reject with a 400. NVIDIA NIM
tolerates it, so it did not surface in the NIM-backed e2e run, and the
mocked unit tests did not enforce message ordering.

Return only the retry response. The first attempt was already shown to
the model inside the local retry_messages (ephemeral, never persisted),
so dropping it from state loses nothing and keeps a valid sequence
regardless of whether the retry is a tool call or another clarification.

Add a regression test asserting no two consecutive AIMessages are ever
sent to the LLM across a forced-search run; it fails against the old
[response, retry_response] return and passes with the fix.

Signed-off-by: Torkian <torkian@mac.com>
While auditing the message ordering for the forced-search fix, a
separate pre-existing bug surfaced in the skip-command branch.

When a user types a skip command, ask_clarification returned an
AIMessage(complete) without persisting the user's reply, leaving it
directly after the prior AIMessage(clarification) — two consecutive
assistant messages. The unconditional ask_for_clarification -> agent
edge then re-entered agent_node with the clarification budget exhausted,
which appended a third AIMessage(complete). With enable_plan_approval
enabled this corrupted history flowed into the planner's ainvoke,
producing a 400 from the OpenAI/Anthropic APIs (NVIDIA NIM tolerates it).

Two coordinated fixes:
- The skip branch now persists the user's reply as a HumanMessage before
  the completion AIMessage, so the prior and new assistant turns are
  separated by a human message.
- agent_node's budget-exhausted branch no longer emits a completion when
  the last message is already a completion AIMessage (the skip branch's),
  avoiding the duplicate on graph re-entry.

Add regression tests covering both the persisted history ordering and
the planner ainvoke sequence under enable_plan_approval; both fail
against the prior behavior and pass with the fix.

Signed-off-by: Torkian <torkian@mac.com>
@torkian torkian changed the title Force clarifier to search before asking clarification questions (#234) fix: clarifier searches before asking clarification questions Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

ClarifierAgent now forces a search-first retry on the first clarification turn, preserves skip replies as human messages, avoids duplicate assistant completions, and updates prompt/test coverage for the new message-ordering rules.

Changes

Clarifier search-before-clarify flow

Layer / File(s) Summary
Search-first contract
src/aiq_agent/agents/clarifier/agent.py, src/aiq_agent/agents/clarifier/prompts/research_clarification.j2
The prompt requires search-first handling for unfamiliar terms, and the agent adds helpers to detect prior assistant tool invocations and scope them to the latest user turn.
Inline first-turn retry
src/aiq_agent/agents/clarifier/agent.py
FORCE_SEARCH_GUIDANCE is injected on the first clarification turn when no recent tool calls exist, the model is retried inline once, duplicate completion messages are suppressed, and skip replies are persisted as HumanMessage before completion.
Force-search behavior tests
tests/aiq_agent/agents/clarifier/test_agent.py
Tests cover _has_tool_invocations(), _searched_since_last_user_turn(), the force-search branches, one-shot retry limits, immediate tool-call cases, and ephemeral guidance handling.
Message-ordering regressions
tests/aiq_agent/agents/clarifier/test_agent.py
Regression tests assert that forced retries and skip handling never produce consecutive assistant messages, preserve tool-call adjacency, avoid blank human messages, and keep planner inputs ordered when plan approval is enabled.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds a first-turn search-before-clarify retry and prompt update, matching issue #234's requirement.
Out of Scope Changes check ✅ Passed The message-ordering and skip-path fixes support the clarifier flow and are not unrelated scope creep.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title uses valid Conventional Commits syntax and accurately summarizes the clarifier search-before-clarify fix.
Description check ✅ Passed The description covers overview, changes, tests, and related issue, but lacks an explicit reviewer-start section and full checklist/template items.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiq_agent/agents/clarifier/agent.py`:
- Around line 665-669: Normalize blank skip replies in the clarifier flow before
creating the return payload in the agent logic, since _is_skip_command() already
treats empty and whitespace-only input as skip. Update the branch that appends
HumanMessage so it uses a non-empty sentinel instead of persisting an empty user
turn, and add a regression test covering user_prompt_callback == "" with
enable_plan_approval=True that verifies the planner history does not contain an
empty HumanMessage.

In `@tests/aiq_agent/agents/clarifier/test_agent.py`:
- Around line 1300-1304: The completion-message check in the clarifier test is
too loose and does not enforce the intended invariant. Update the assertion
around `completion_ais` in `test_agent.py` to match only the terminal completion
`AIMessage` (not any non-tool `AIMessage`), and require exactly one match
instead of allowing `>= 1`. Use `ClarifierAgent._has_tool_invocations` and the
`completion_ais` filter to locate and tighten this logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e74d3d1c-9f3a-410a-b4ed-c0cfabe38091

📥 Commits

Reviewing files that changed from the base of the PR and between 6fa5390 and 24e6e84.

📒 Files selected for processing (3)
  • src/aiq_agent/agents/clarifier/agent.py
  • src/aiq_agent/agents/clarifier/prompts/research_clarification.j2
  • tests/aiq_agent/agents/clarifier/test_agent.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • src/aiq_agent/agents/clarifier/prompts/research_clarification.j2
  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/clarifier/prompts/research_clarification.j2
  • src/aiq_agent/agents/clarifier/agent.py
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/clarifier/agent.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/clarifier/test_agent.py
🔇 Additional comments (2)
src/aiq_agent/agents/clarifier/prompts/research_clarification.j2 (1)

32-36: LGTM!

src/aiq_agent/agents/clarifier/agent.py (1)

107-115: LGTM!

Also applies to: 496-513, 533-547, 561-630, 702-706

Comment thread src/aiq_agent/agents/clarifier/agent.py
Comment thread tests/aiq_agent/agents/clarifier/test_agent.py Outdated
Addresses two CodeRabbit findings on NVIDIA-AI-Blueprints#245:

- agent_node skip branch: `_is_skip_command` treats "" and whitespace-only
  replies as skip, so the branch could persist an empty HumanMessage which
  then flows into plan generation; some chat APIs reject empty message
  content. Substitute a non-empty "[skipped clarification]" sentinel when the
  reply is blank. Adds a regression test that, with enable_plan_approval, the
  planner ainvoke never receives an empty HumanMessage.

- Tighten the completion-message assertion in the skip-ordering test: match
  only the terminal completion AIMessage (needs_clarification=false via
  _is_complete) and require exactly one, instead of matching any non-tool
  AIMessage with len >= 1.

151 clarifier tests pass; ruff clean.

Signed-off-by: Torkian <torkian@mac.com>
@torkian

torkian commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Did a deep pre-merge review pass to clear remaining edge cases in one go. Pushed in 6fef9e3:

Fixed (in scope of this change):

  • Search guard scoping_searched_since_last_user_turn replaces the whole-history _has_tool_invocations check, so a tool call from an earlier conversation turn no longer suppresses the search-before-clarify nudge for a fresh query. Unit + behavioral regression tests added.
  • Exhausted-completion hardeningagent_node's budget-exhausted branch never emits two consecutive assistant messages now; when the last turn is a non-complete AIMessage at exhaustion it interleaves a sentinel user turn before the completion (previously it could append an adjacent completion that 400s the planner under plan approval). Regression test added.
  • Sentinel constant — promoted the blank-skip placeholder to SKIPPED_CLARIFICATION_SENTINEL (fixes the casing drift with the log literal), reused by the interleave above.
  • Test polish — tightened two planner call_count asserts to == 1, made the fallback-query assertion require both tokens (and), removed an unused dict, and added docstrings to the graph-node functions and new fixtures/helpers (ruff + format clean, 157 tests pass).

Intentionally out of scope (pre-existing, not touched by this PR — flagging rather than expanding scope):

  • ClarifierAgent.max_turns vs state.max_turns precedence in run().
  • _get_fallback_clarification using the latest HumanMessage rather than the original query on multi-turn fallbacks.
  • Blank plan-approval replies counting as empty feedback in plan_preview_node.

Happy to file follow-ups for those if you'd like them addressed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/aiq_agent/agents/clarifier/agent.py (1)

633-669: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Enforce search-before-HITL instead of relying on the retry prompt.

Line 669 returns retry_response even when it has no tool_calls; if that response still asks for clarification, decide_route routes straight to ask_for_clarification, so non-compliant models can still prompt the user before any search. That contradicts the model-agnostic “search first, ask second” objective. Please enforce an actual tool/search path before HITL, or explicitly change the contract/tests to best-effort prompting. As per path instructions, src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts, HITL behavior, and failure handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiq_agent/agents/clarifier/agent.py` around lines 633 - 669, The
search-before-clarify retry in clarifier/agent.py is still allowing a
clarification-only retry to flow into HITL. Update the logic around the one-shot
retry so Clarifier only returns a retry result when it actually triggers a
tool/search path, and otherwise continues to the normal clarification route or
explicitly falls back per the intended contract. Use the existing symbols
_searched_since_last_user_turn, decide_route, and FORCE_SEARCH_GUIDANCE to
locate the control flow and make the search-first behavior enforced rather than
prompt-only.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/aiq_agent/agents/clarifier/agent.py`:
- Around line 633-669: The search-before-clarify retry in clarifier/agent.py is
still allowing a clarification-only retry to flow into HITL. Update the logic
around the one-shot retry so Clarifier only returns a retry result when it
actually triggers a tool/search path, and otherwise continues to the normal
clarification route or explicitly falls back per the intended contract. Use the
existing symbols _searched_since_last_user_turn, decide_route, and
FORCE_SEARCH_GUIDANCE to locate the control flow and make the search-first
behavior enforced rather than prompt-only.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: a706f4fd-ab4f-402b-980e-af4d62609eef

📥 Commits

Reviewing files that changed from the base of the PR and between 8dda5c0 and e0740ed.

📒 Files selected for processing (2)
  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/clarifier/agent.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/clarifier/agent.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/clarifier/test_agent.py
🔇 Additional comments (2)
src/aiq_agent/agents/clarifier/agent.py (1)

116-118: LGTM!

Also applies to: 518-538, 558-611, 673-759

tests/aiq_agent/agents/clarifier/test_agent.py (1)

911-939: LGTM!

Also applies to: 941-1031, 1034-1107, 1109-1201, 1203-1262, 1265-1388, 1391-1435, 1438-1544

Pre-merge review surfaced several edge cases in the search-before-clarify
and skip handling. Fixes:

- Scope the search-before-clarify guard to the current request. It used
  _has_tool_invocations over the whole history, so a tool call from an
  earlier conversation turn could suppress the nudge for a fresh user
  query. New _searched_since_last_user_turn only counts tool calls after
  the latest HumanMessage. Adds unit + behavioral regression tests.

- Harden the budget-exhausted branch in agent_node so it never produces
  two consecutive assistant messages. When the last turn is a non-complete
  AIMessage at exhaustion, interleave a sentinel user turn before the
  completion (previously it appended an adjacent completion that could
  400 the planner under plan approval). Adds a regression test.

- Promote the blank-skip sentinel to a module constant
  (SKIPPED_CLARIFICATION_SENTINEL), reused by the exhausted-completion
  interleave; fixes the casing drift with the log literal.

Test polish: tighten two planner call_count asserts to == 1, make the
fallback-query assertion require both tokens (and, not or), drop an unused
captured dict, and add docstrings to the graph-node functions and the new
test fixtures/helpers.

157 clarifier tests pass; ruff check + format clean.

Signed-off-by: Torkian <torkian@mac.com>
@torkian torkian force-pushed the fix/clarifier-search-before-questions branch from e0740ed to 6fef9e3 Compare June 29, 2026 04:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiq_agent/agents/clarifier/agent.py`:
- Around line 596-611: The completion path in ClarifierAgent currently treats
any last AIMessage the same, but pending tool-call messages must be handled
separately before inserting SKIPPED_CLARIFICATION_SENTINEL. Update the logic
around the last_message check in the clarification completion branch so that
AIMessage instances with tool_calls are either drained/handled first or routed
through the tool-call path, instead of directly returning the sentinel plus
completion. Keep the existing non-tool-call behavior for plain clarification
turns, but avoid emitting a history that leaves unresolved tool calls adjacent
to the completion message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b2ffc1fe-7cb5-438c-bf7c-f5c9e2fe69a9

📥 Commits

Reviewing files that changed from the base of the PR and between e0740ed and 6fef9e3.

📒 Files selected for processing (2)
  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/clarifier/agent.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/clarifier/agent.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/clarifier/test_agent.py
🔇 Additional comments (5)
tests/aiq_agent/agents/clarifier/test_agent.py (1)

25-31: LGTM!

Also applies to: 911-939, 959-1031, 1034-1106, 1109-1200, 1203-1262, 1265-1388, 1391-1544

src/aiq_agent/agents/clarifier/agent.py (4)

116-119: LGTM!


518-538: LGTM!


659-671: LGTM!


704-723: LGTM!

Comment thread src/aiq_agent/agents/clarifier/agent.py
Comment thread src/aiq_agent/agents/clarifier/agent.py
Comment thread src/aiq_agent/agents/clarifier/agent.py Outdated
Comment thread tests/aiq_agent/agents/clarifier/test_agent.py Outdated
cdgamarose-nv and others added 3 commits June 30, 2026 16:09
…dedupe helper

Apply reviewer feedback on NVIDIA-AI-Blueprints#245:

- Inject the search-before-clarify nudge as a trailing SystemMessage
  instead of a HumanMessage. A SystemMessage reads as scaffolding, so
  models don't weight it as the real user query; guidance is still
  ephemeral (never persisted to state).

- Trim the oversized rationale comment on the retry block from ~26 lines
  to ~6; the detail was longer than the code and duplicated elsewhere.

- Hoist the thrice-duplicated _adjacent_assistant_pairs test helper to a
  single module-level function and reuse it.

157 clarifier tests pass; ruff check + format clean.

Signed-off-by: Torkian <torkian@mac.com>
At clarification-budget exhaustion, if the last message is an AIMessage
with pending tool_calls, agent_node no longer stacks a completion on top
of it (which would leave the tool call unresolved and produce a history
the OpenAI/Anthropic APIs reject). It now returns no new message so
decide_route sends the pending call to the tools node; the tool result
then re-enters and completion proceeds validly. Adds a regression test.

Signed-off-by: Torkian <torkian@mac.com>
@torkian

torkian commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review @cdgamarose-nv — addressed all of it:

  • Nudge as SystemMessage (82fdab9): switched the search-before-clarify nudge from a trailing HumanMessage to a SystemMessage so the model reads it as scaffolding, not the real query. It stays ephemeral (never persisted to state). HumanMessage wasn't deliberate — good call.
  • Trimmed the oversized comment (82fdab9): the retry-block rationale is down from ~26 lines to ~6.
  • Deduped the test helper (82fdab9): hoisted _adjacent_assistant_pairs to a single module-level function.
  • Pending tool call at exhaustion (62151d8): the exhausted-completion branch no longer stacks a completion on top of an AIMessage with pending tool_calls (which would leave the call unresolved); it defers so the tools node resolves it first. Added a regression test.

158 clarifier tests pass; ruff check + format clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/aiq_agent/agents/clarifier/test_agent.py (2)

1085-1116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Negative-guidance check misses SystemMessage.

The loop at lines 1114-1116 only inspects HumanMessage instances for FORCE_SEARCH_GUIDANCE, but production code now injects the guidance via a trailing SystemMessage (per agent.py: SystemMessage(content=FORCE_SEARCH_GUIDANCE)). This check would pass even if a stray guidance SystemMessage were present.

🔧 Proposed fix
-        for m in second_call_messages:
-            if isinstance(m, HumanMessage):
-                assert FORCE_SEARCH_GUIDANCE not in m.content
+        for m in second_call_messages:
+            if isinstance(m, (HumanMessage, SystemMessage)):
+                assert FORCE_SEARCH_GUIDANCE not in m.content
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/aiq_agent/agents/clarifier/test_agent.py` around lines 1085 - 1116, The
negative-guidance assertion in
test_force_search_not_triggered_when_llm_searches_first only checks HumanMessage
entries, so it can miss FORCE_SEARCH_GUIDANCE if it is injected as a
SystemMessage. Update the check to inspect all messages passed to
mock_llm.ainvoke on the second call, especially the trailing SystemMessage added
by ClarifierAgent/agent.py, and assert that none of them contain the guidance
text.

1165-1211: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Regression test no longer covers the bug it targets.

This test's docstring explicitly states it's a regression test for re-injection of FORCE_SEARCH_GUIDANCE after the user replies (Greptile P1 finding), but the check at lines 1206-1210 only scans HumanMessage instances. Since guidance is now injected as a SystemMessage, a reintroduced bug where guidance leaks into a SystemMessage on the post-reply turn would go undetected — defeating the stated purpose of this regression test.

🔧 Proposed fix
-        third_call_messages = mock_llm.ainvoke.call_args_list[2].args[0]
-        for m in third_call_messages:
-            if isinstance(m, HumanMessage):
-                assert FORCE_SEARCH_GUIDANCE not in m.content, (
-                    "force_search guidance must not be re-injected after the user replies"
-                )
+        third_call_messages = mock_llm.ainvoke.call_args_list[2].args[0]
+        for m in third_call_messages:
+            if isinstance(m, (HumanMessage, SystemMessage)):
+                assert FORCE_SEARCH_GUIDANCE not in m.content, (
+                    "force_search guidance must not be re-injected after the user replies"
+                )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/aiq_agent/agents/clarifier/test_agent.py` around lines 1165 - 1211, The
regression test in ClarifierAgent no longer verifies the actual bug path because
it only inspects HumanMessage entries, while FORCE_SEARCH_GUIDANCE is injected
via SystemMessage on the post-reply turn. Update
test_force_search_guidance_not_injected_after_user_reply to examine the third
mock_llm.ainvoke call’s full message list and assert the guidance is absent from
any message content, especially SystemMessage, so the test still protects the
ClarifierAgent.run flow after the user reply.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiq_agent/agents/clarifier/agent.py`:
- Around line 648-649: The retry prompt in clarifier agent logic is appending a
trailing SystemMessage, which can break Anthropic-backed retries. Update the
retry path in the agent flow around retry_messages and retry_response so
FORCE_SEARCH_GUIDANCE is merged into the initial system prompt content instead
of being added after response, and keep any later retry messages as non-system
messages only.

---

Outside diff comments:
In `@tests/aiq_agent/agents/clarifier/test_agent.py`:
- Around line 1085-1116: The negative-guidance assertion in
test_force_search_not_triggered_when_llm_searches_first only checks HumanMessage
entries, so it can miss FORCE_SEARCH_GUIDANCE if it is injected as a
SystemMessage. Update the check to inspect all messages passed to
mock_llm.ainvoke on the second call, especially the trailing SystemMessage added
by ClarifierAgent/agent.py, and assert that none of them contain the guidance
text.
- Around line 1165-1211: The regression test in ClarifierAgent no longer
verifies the actual bug path because it only inspects HumanMessage entries,
while FORCE_SEARCH_GUIDANCE is injected via SystemMessage on the post-reply
turn. Update test_force_search_guidance_not_injected_after_user_reply to examine
the third mock_llm.ainvoke call’s full message list and assert the guidance is
absent from any message content, especially SystemMessage, so the test still
protects the ClarifierAgent.run flow after the user reply.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 08e7b9e8-a1b4-487f-9cf8-57bead036337

📥 Commits

Reviewing files that changed from the base of the PR and between 6fef9e3 and 82fdab9.

📒 Files selected for processing (2)
  • src/aiq_agent/agents/clarifier/agent.py
  • tests/aiq_agent/agents/clarifier/test_agent.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run ruff check and ruff format validation for Python code changes

**/*.py: Python code must be linted and formatted with Ruff using line length 120, target Python 3.11, rule sets E,F,W,I,PL,UP, and isort force-single-line configuration
Never commit secrets, tokens, or environment-specific hostnames; use environment variables and SecretStr instead, resolving API keys at runtime
Never print or log secret values, including in tool output or error messages
Missing-secret paths must degrade gracefully (stub/skip), not crash or leak
Do not hand-reformat unrelated code when making changes; match the existing import and formatting style

Files:

  • tests/aiq_agent/agents/clarifier/test_agent.py
  • src/aiq_agent/agents/clarifier/agent.py
**/*test*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run pytest for all behavior changes in Python code

Files:

  • tests/aiq_agent/agents/clarifier/test_agent.py
**

⚙️ CodeRabbit configuration file

**:

AI-Q Agent Guidance

Repository-global instructions for coding agents and for humans reviewing
agent-authored changes. These rules apply to every task in this repository.
Task-specific runbooks live in .agents/skills/ — load the
relevant skill before starting a workflow it covers.

Project overview

AI-Q is an NVIDIA AI Blueprint: an enterprise research agent built on the
NeMo Agent Toolkit (NAT). The deployed product is a research blueprint, not
a general skill runtime. New retrieval sources and tools are NAT functions;
agent behavior is driven by workflow YAML, Jinja2 prompts, and a data-source
registry — not by hard-coded logic.

Primary boundaries:

  • Backend Python package: src/aiq_agent/.
  • Data-source and tool packages: sources/ (each is its own package).
  • Frontends and tooling: frontends/ (web UI in frontends/ui/, eval harnesses
    in frontends/benchmarks/).
  • Configs, deployment, docs: configs/, deploy/, docs/.

Stay inside this repository. If your workspace also contains adjacent repos
(for example a sibling NeMo-Relay checkout), do not edit them as part of an AI-Q
change. Treat sources/* as independent packages: prefer the smallest change
scoped to the package you are touching.

Repository structure

Path Purpose
src/aiq_agent/ Backend agent, FastAPI extensions, auth, observability, knowledge
sources/ Data-source / tool packages (e.g. tavily_web_search, google_scholar_paper_search)
configs/ Workflow YAML configs (e.g. config_cli_default.yml)
frontends/ui/ Next.js / React / TypeScript / Tailwind / KUI web UI
frontends/benchmarks/ Eval harnesses: freshqa, deepsearch_qa, deepresearch_bench
deploy/ Docker Compose and Helm/Kubernetes assets; deploy/.env for secrets
docs/source/ ...

Files:

  • tests/aiq_agent/agents/clarifier/test_agent.py
  • src/aiq_agent/agents/clarifier/agent.py
src/aiq_agent/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/aiq_agent/**/*.py: Respect authenticated data sources by honoring requires_auth, per-user token pass-through, and backend token validators; apply owner guardrails before loading protected report or artifact context into an agent
Do not weaken or bypass AuthMiddleware, validators, or auth gating without a prior design discussion

Files:

  • src/aiq_agent/agents/clarifier/agent.py
src/aiq_agent/agents/**/*

⚙️ CodeRabbit configuration file

src/aiq_agent/agents/**/*: Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts,
HITL behavior, and failure handling. Flag changes that weaken source attribution, report generation,
async cancellation, checkpointing, or data-source selection without focused tests and docs.

Files:

  • src/aiq_agent/agents/clarifier/agent.py
🔇 Additional comments (7)
tests/aiq_agent/agents/clarifier/test_agent.py (5)

25-32: LGTM!

Also applies to: 47-55


968-1012: LGTM! Assertion correctly matches the trailing SystemMessage scaffolding per the updated implementation.


1213-1264: LGTM! Consecutive-assistant-message invariant and tool-call adjacency are both verified against every mocked LLM call.


1267-1428: LGTM! Skip-branch ordering, HumanMessage persistence, and blank-reply sentinel behavior are all exercised correctly, including under plan approval.


1431-1528: LGTM! Both the prior-turn-tool-call scoping and budget-exhaustion sentinel-interleaving regressions correctly assert against the module-level _adjacent_assistant_pairs helper and the SystemMessage-based guidance check.

src/aiq_agent/agents/clarifier/agent.py (2)

606-610: Already flagged: drain pending tool calls before completing.

This branch still treats any AIMessage as a plain clarification turn; if it carries tool_calls, the sentinel + completion leaves unresolved tool calls in history. As per path instructions, src/aiq_agent/agents/**/*: “Review agent changes for research workflow correctness, graph state transitions, prompt/tool contracts, HITL behavior, and failure handling.”

Source: Path instructions


500-537: LGTM!

Also applies to: 558-572, 681-704, 713-740

Comment thread src/aiq_agent/agents/clarifier/agent.py Outdated
torkian added 2 commits July 4, 2026 21:54
A trailing SystemMessage in the forced-search retry is rejected by
providers that only accept a leading system message (langchain-anthropic
raises on non-consecutive system messages before the request is even
sent). Merge FORCE_SEARCH_GUIDANCE into the leading system prompt for the
retry instead — it stays system-level scaffolding rather than a fake user
turn, and the message list remains valid for every supported wrapper.

Test updates:
- Assert the retry carries exactly one system message, in the leading
  position, containing the guidance.
- Replace three negative assertions that only scanned HumanMessages
  (vacuous after the guidance moved to the system prompt) with checks
  over all messages, and verify the guidance never persists into
  state.messages via the final graph state.
- Harden the pending-tool-call node test to require a dict result
  instead of silently passing on other shapes.

158 clarifier tests pass; ruff check + format clean.

Signed-off-by: Torkian <torkian@mac.com>
@torkian

torkian commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@cdgamarose-nv branch is up to date with develop and all review feedback is addressed (per-thread replies above) — ready for re-review whenever convenient. 158 clarifier tests pass; lint clean.

@AjayThorve AjayThorve added this to the v2.2 milestone Jul 7, 2026
@AjayThorve

Copy link
Copy Markdown
Collaborator

/ok to test bd866f4

@AjayThorve

Copy link
Copy Markdown
Collaborator

/ok to test 7dcdeef

@cdgamarose-nv cdgamarose-nv self-requested a review July 7, 2026 18:15

@cdgamarose-nv cdgamarose-nv left a comment

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.

lgtm

@cdgamarose-nv cdgamarose-nv merged commit 364e128 into NVIDIA-AI-Blueprints:develop Jul 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Force clarifier agent to trigger searches before asking questions

3 participants