Class: Brute::Middleware::ToolUseGuard

Inherits:
Object
  • Object
show all
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. Context#talk appends nil, BufferNilGuard strips it, and the assistant message carrying tool_use blocks is lost. This causes “unexpected tool_use_id” on the next call because tool_result references a tool_use that’s missing from the buffer.

This middleware runs post-call and ensures every pending tool_use ID is covered by an assistant message in the buffer. It handles three cases:

1. ctx.functions is non-empty and the assistant message exists → no-op
2. ctx.functions is non-empty but the assistant message is missing
   (or has different IDs) → inject synthetic message
3. ctx.functions is empty (nil-choice bug) but the stream recorded
   tool calls → inject synthetic message using stream metadata

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ ToolUseGuard

Returns a new instance of ToolUseGuard.



25
26
27
# File 'lib/brute/middleware/tool_use_guard.rb', line 25

def initialize(app)
  @app = app
end

Instance Method Details

#call(env) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/brute/middleware/tool_use_guard.rb', line 29

def call(env)
  response = @app.call(env)

  ctx = env[:context]

  # Collect pending tool data from ctx.functions (primary) or the
  # stream's recorded metadata (fallback for nil-choice bug).
  tool_data = collect_tool_data(ctx, env)
  return response if tool_data.empty?

  # Find all tool_use IDs already covered by assistant messages.
  covered_ids = covered_tool_ids(ctx)

  # Inject a synthetic assistant message for any uncovered tool calls.
  uncovered = tool_data.reject { |td| covered_ids.include?(td[:id]) }
  inject_synthetic!(ctx, uncovered) unless uncovered.empty?

  response
end