fix: Preserve tool calls for LLMRails tool rails - #2073
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR fixes tool-call guardrails for agentic workflows where the caller — not
|
| Filename | Overview |
|---|---|
| nemoguardrails/rails/llm/llmrails.py | Preserves assistant messages with tool_calls in message history when dialog rails are disabled, so they reach _get_events_for_messages and become BotToolCalls events; the guard is a clean, minimal condition addition. |
| nemoguardrails/actions/llm/utils.py | Updated extract_tool_calls_from_events to prefer the post-rail StartToolCallBotAction event and fall back to the last BotToolCalls; the early-return-on-first-match for StartToolCallBotAction is safe given each flow produces at most one such event. |
| nemoguardrails/logging/processing_log.py | Adds StartToolOutputRail/StartToolInputRail start events and ToolOutputRailFinished/ToolInputRailFinished finish events to the state machine, following the exact same pattern as input/output rails; also adds the four tool flow names to ignored_flows. |
| tests/test_tool_output_rails.py | Adds _tool_arguments helper to handle both dict and JSON-string arguments, fixes the existing validate_tool_parameters action, and adds two new integration tests covering the blocked and approved tool-call paths with dialog disabled. |
| tests/test_tool_calls_event_extraction.py | Adds two unit tests for extract_tool_calls_from_events; both tests use async def with @pytest.mark.asyncio but contain no async operations, and the local validate_tool_parameters fixture still uses raw func.get("arguments", {}) without the JSON-string-safe helper. |
| tests/test_logging.py | Adds a focused synchronous unit test for the new tool-rail entries in compute_generation_log, covering both tool_output and tool_input types, names, and durations. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant LLMRails
participant _get_events_for_messages
participant Runtime
participant ToolOutputRail
Caller->>LLMRails: "generate_async(messages=[user, assistant(tool_calls)], dialog=False)"
Note over LLMRails: New guard: tool_calls present? YES keep assistant message
LLMRails->>_get_events_for_messages: messages (assistant msg preserved)
_get_events_for_messages-->>LLMRails: [..., BotToolCalls event]
LLMRails->>Runtime: generate_events([..., BotToolCalls])
Runtime->>ToolOutputRail: StartToolOutputRail
ToolOutputRail-->>Runtime: ToolOutputRailFinished / abort
Runtime-->>LLMRails: new_events (incl. StartToolCallBotAction or block)
LLMRails->>LLMRails: extract_tool_calls_from_events prefers StartToolCallBotAction
LLMRails-->>Caller: GenerationResponse(tool_calls or blocked message)
%%{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 LLMRails
participant _get_events_for_messages
participant Runtime
participant ToolOutputRail
Caller->>LLMRails: "generate_async(messages=[user, assistant(tool_calls)], dialog=False)"
Note over LLMRails: New guard: tool_calls present? YES keep assistant message
LLMRails->>_get_events_for_messages: messages (assistant msg preserved)
_get_events_for_messages-->>LLMRails: [..., BotToolCalls event]
LLMRails->>Runtime: generate_events([..., BotToolCalls])
Runtime->>ToolOutputRail: StartToolOutputRail
ToolOutputRail-->>Runtime: ToolOutputRailFinished / abort
Runtime-->>LLMRails: new_events (incl. StartToolCallBotAction or block)
LLMRails->>LLMRails: extract_tool_calls_from_events prefers StartToolCallBotAction
LLMRails-->>Caller: GenerationResponse(tool_calls or blocked message)
Reviews (3): Last reviewed commit: "Fix tests; address PR comments" | Re-trigger Greptile
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughExtends ChangesTool Rails Logging and History Fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Possibly related issues
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
e828b3f to
fc66c41
Compare
Description
This PR enables tool call guardrails to work when checking a tool-call message directly (as opposed to when the library itself generates the tool call).
tool_callswhenLLMRailsruns with dialog rails disabled, so they are converted intoBotToolCallsevents and evaluated by tool output rails.GenerationLog, so activated tool rails are reported with the correct type, name, duration, and stop handling.Desired use case:
Agentic workflows may not rely on
generate_asyncto orchestrate LLM calls end-to-end. Instead, the caller might invoke specific rails at different points in the request life cycle. Example:tool_calls,Current issue:
In
LLMRails, assistant messages were treated as plain text output and removed from the message history. If that assistant message containedtool_calls, the tool calls never became BotToolCalls events, so tool output rails could be skipped.Related Issue(s)
#2096
Verification
AI Assistance
Checklist
Summary by CodeRabbit
New Features
Bug Fixes