feat(server): allow tool calls outside passthrough mode without dialog flows - #2188
Conversation
PR merge guidance@christinaexyou thanks for the PR. GitHub is currently blocking merge for one or more repository requirements:
Relevant guide: |
… flows Signed-off-by: Christina Xu <chrxu@redhat.com>
aff6a45 to
9c8f260
Compare
Greptile SummaryThis PR removes the
|
| Filename | Overview |
|---|---|
| nemoguardrails/server/api.py | Splits the tool-request guard into separate streaming and dialog-flow checks. The dialog-flow check (bool(user_messages) or single_call.enabled) is overly broad — configs with single_call.enabled=True but no user_messages would actually surface tool calls via _emit_general_bot_turn, but the server incorrectly returns 422 for them. |
| nemoguardrails/actions/llm/generation.py | Removes the passthrough guard around get_and_clear_tool_calls_contextvar() so tool calls surface from any config that reaches _emit_general_bot_turn. The logic is correct: the dialog-flow paths never call this helper, so they naturally drop tool calls. |
| tests/test_tool_calling_passthrough_integration.py | Adds a large TestToolCallingNonPassthroughIntegration class. Most tests are solid, but test_tool_calls_fail_in_dialog_flows_non_passthrough uses an unrecognized YAML key (dialog:) and never populates tool_calls_var, so it does not actually test dialog-flow suppression of tool calls. |
| tests/integrations/langchain/test_tool_calling_passthrough.py | Renamed from test_tool_calling_passthrough_only.py; adds TestToolCallingNonPassthrough tests confirming tool calls surface without passthrough. Patch targets updated to nemoguardrails.actions.llm.generation. Previous threads flagged a bound-mock issue and stale docstrings. |
| tests/test_generation_equivalence.py | Adds well-structured tests for the new behavior: test_non_passthrough_tool_calls verifies tool calls surface from the general non-passthrough path; test_tools_not_supported_when_dialog_flows_are_enabled and test_single_call_tools_not_supported verify they are dropped in dialog-flow configs. These are the strongest tests in the PR. |
| tests/server/test_api.py | Updates server tests to match the new behavior — tools accepted without passthrough for no-dialog-flow configs, rejected for dialog-flow and streaming configs. Tests correctly stub user_messages and single_call.enabled on the mock config. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[API Request with tools] --> B{body.stream?}
B -- yes --> C[422: non-streaming only]
B -- no --> D{has_dialog_flows?}
D -- "bool(user_messages) OR single_call.enabled" --> E[422: dialog flows]
D -- false --> F[generate_async pipeline]
F --> G[generate_user_intent]
G --> H{single_call.enabled?}
H -- yes --> I[generate_intent_steps_message]
I --> J{user_messages?}
J -- yes --> K[Dialog flow path\nBotMessage — tool calls dropped]
J -- no --> L[_emit_general_bot_turn]
H -- no --> M{user_messages?}
M -- yes --> N[_detect_user_intent\nDialog flow path — tool calls dropped]
M -- no --> L
L --> O[get_and_clear_tool_calls_contextvar]
O --> P{tool_calls?}
P -- yes --> Q[BotToolCalls event]
P -- no --> R[BotMessage event]
style E fill:#f99,stroke:#c00
style C fill:#f99,stroke:#c00
style K fill:#ffd,stroke:#aa0
style N fill:#ffd,stroke:#aa0
style Q fill:#9f9,stroke:#090
style R fill:#9f9,stroke:#090
Prompt To Fix All With AI
### Issue 1
nemoguardrails/server/api.py:558
**`single_call.enabled` without `user_messages` incorrectly blocks tools**
The guard rejects tool requests whenever `single_call.enabled` is `True`, but the generation code only drops tool calls when **both** `single_call.enabled` is `True` and `user_messages` is non-empty. When `single_call.enabled=True && user_messages={}`, `generate_intent_steps_message` falls through to `_emit_general_bot_turn` (the else-branch at line 1509 of `generation.py`), which calls `get_and_clear_tool_calls_contextvar()` and emits a `BotToolCalls` event as normal. The `test_single_call_general_no_user_messages` test in `test_generation_equivalence.py` already documents this path. A user with that config will receive a spurious 422 "dialog flows" error even though tools would work.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (2): Last reviewed commit: "docs(tool-calling): passthrough is not r..." | Re-trigger Greptile
| ), | ||
| ) | ||
| try: |
There was a problem hiding this comment.
Missing blank line between the
if tools_requested: block and the try: block causes a minor PEP 8 violation and makes the guard clause less visually distinct from the next logical section.
| ), | |
| ) | |
| try: | |
| ), | |
| ) | |
| try: |
Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/server/api.py
Line: 566-568
Comment:
Missing blank line between the `if tools_requested:` block and the `try:` block causes a minor PEP 8 violation and makes the guard clause less visually distinct from the next logical section.
```suggestion
),
)
try:
```
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!
…are configured Signed-off-by: Christina Xu <chrxu@redhat.com>
9c8f260 to
f47da75
Compare
|
|
||
| # Dialog-flow configs (canonical-form user_messages or single_call dialog | ||
| # rails) never surface tool calls to the request, so reject explicitly | ||
| has_dialog_flows = bool(llm_rails.config.user_messages) or llm_rails.config.rails.dialog.single_call.enabled |
There was a problem hiding this comment.
single_call.enabled without user_messages incorrectly blocks tools
The guard rejects tool requests whenever single_call.enabled is True, but the generation code only drops tool calls when both single_call.enabled is True and user_messages is non-empty. When single_call.enabled=True && user_messages={}, generate_intent_steps_message falls through to _emit_general_bot_turn (the else-branch at line 1509 of generation.py), which calls get_and_clear_tool_calls_contextvar() and emits a BotToolCalls event as normal. The test_single_call_general_no_user_messages test in test_generation_equivalence.py already documents this path. A user with that config will receive a spurious 422 "dialog flows" error even though tools would work.
Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/server/api.py
Line: 558
Comment:
**`single_call.enabled` without `user_messages` incorrectly blocks tools**
The guard rejects tool requests whenever `single_call.enabled` is `True`, but the generation code only drops tool calls when **both** `single_call.enabled` is `True` and `user_messages` is non-empty. When `single_call.enabled=True && user_messages={}`, `generate_intent_steps_message` falls through to `_emit_general_bot_turn` (the else-branch at line 1509 of `generation.py`), which calls `get_and_clear_tool_calls_contextvar()` and emits a `BotToolCalls` event as normal. The `test_single_call_general_no_user_messages` test in `test_generation_equivalence.py` already documents this path. A user with that config will receive a spurious 422 "dialog flows" error even though tools would work.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
… flows
Description
Tool calls were previously gated on passthrough mode. They are now allowed
in any config as long as no dialog flows are defined (
user_messagesorsingle_call dialog rails). Streaming requests still rejecttools/tool_choice/parallel_tool_calls. We explicitly reject tool call requests if dialog flows or streaming is enabled in the config.Related Issue(s)
Verification
AI Assistance
Checklist