Class: Brute::Middleware::ToolUseGuard
- Inherits:
-
Object
- Object
- Brute::Middleware::ToolUseGuard
- Defined in:
- lib/brute/middleware/tool_use_guard.rb
Overview
Guards against tool-only LLM responses where the assistant message is dropped from the context buffer.
When the LLM responds with only tool_use blocks (no text), llm.rb’s response adapter produces empty choices. The assistant message carrying tool_use blocks may be lost. This causes “unexpected tool_use_id” on the next call because tool_result references a tool_use that’s missing from the message history.
This middleware runs post-call and ensures every pending tool_use ID is covered by an assistant message in env. It handles three cases:
1. pending_functions is non-empty and the assistant message exists → no-op
2. pending_functions is non-empty but the assistant message is missing
(or has different IDs) → inject synthetic message
3. pending_functions is empty (nil-choice bug) but the stream recorded
tool calls → inject synthetic message using stream metadata
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(app) ⇒ ToolUseGuard
constructor
A new instance of ToolUseGuard.
Constructor Details
#initialize(app) ⇒ ToolUseGuard
Returns a new instance of ToolUseGuard.
28 29 30 |
# File 'lib/brute/middleware/tool_use_guard.rb', line 28 def initialize(app) @app = app end |
Instance Method Details
#call(env) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/brute/middleware/tool_use_guard.rb', line 32 def call(env) response = @app.call(env) # Collect pending tool data from env[:pending_functions] (primary) # or the stream's recorded metadata (fallback for nil-choice bug). tool_data = collect_tool_data(env) return response if tool_data.empty? # Find all tool_use IDs already covered by assistant messages. covered_ids = covered_tool_ids(env[:messages]) # Inject a synthetic assistant message for any uncovered tool calls. uncovered = tool_data.reject { |td| covered_ids.include?(td[:id]) } inject_synthetic!(env[:messages], uncovered) unless uncovered.empty? response end |