Class: Ask::Agent::ToolCallRepair

Inherits:
Object
  • Object
show all
Defined in:
lib/ask/agent/tool_call_repair.rb

Overview

Repairs malformed tool calls before they execute.

When a model emits a tool call with unparseable arguments or an unknown tool name, execution currently fails and the model burns a turn seeing the error. Repair intercepts those calls before execution, asks the model to re-emit them corrected (one internal LLM round-trip), and executes the corrected versions — remapped to the original call ids so tool results stay consistent with the conversation history.

Enable with Session.new(tool_call_repair: true) for the built-in repair prompt, or pass a callable for full control:

Session.new(tool_call_repair: ->(chat, calls, tools) {
{ calls.keys.first => ToolCallInfo.new(
    id: calls.keys.first, name: "bash", arguments: "{}"
  ) }
})

The callable receives the chat, the malformed calls hash (id => ToolCallInfo), and the available tools; it returns a hash of corrections keyed by the ORIGINAL call ids. Calls without a correction are dropped — the model saw them in the prompt and declined to fix them.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(callable = nil) ⇒ ToolCallRepair

Returns a new instance of ToolCallRepair.

Parameters:

  • callable (Proc, nil) (defaults to: nil)

    custom repair function; nil uses the built-in repair prompt



59
60
61
# File 'lib/ask/agent/tool_call_repair.rb', line 59

def initialize(callable = nil)
  @callable = callable
end

Class Method Details

.repair_info(tool_call, tools) ⇒ String?

Why a call is invalid, in a form the model can act on. This is schema-aware: the tool's own validation names what was expected ("unknown parameters: :titile — expected: :project_id, :title"), so the model corrects the call instead of guessing.

Parameters:

Returns:

  • (String, nil)

    why the call is repairable, or nil when it is well-formed and executable



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/ask/agent/tool_call_repair.rb', line 39

def self.repair_info(tool_call, tools)
  tool = tools.find { |t| t.respond_to?(:name) && t.name == tool_call.name }
  return "unknown tool '#{tool_call.name}'" unless tool

  parsed =
    if tool_call.arguments.is_a?(Hash)
      tool_call.arguments
    else
      JSON.parse(tool_call.arguments.to_s)
    end
  return "arguments must be a JSON object, got #{parsed.class}" unless parsed.is_a?(Hash)
  return nil unless tool.respond_to?(:validate)

  tool.validate(parsed.transform_keys(&:to_sym))
rescue JSON::ParserError => e
  "arguments are not valid JSON: #{e.message}"
end

Instance Method Details

#call(chat:, calls:, tools:) ⇒ Hash{String => ToolCallInfo}

Ask the model to correct the malformed calls.

Parameters:

  • chat (Ask::Agent::Chat)

    the session chat (history is restored after the internal round-trip)

  • calls (Hash{String => ToolCallInfo})

    malformed calls by id

  • tools (Array<Object>)

Returns:

  • (Hash{String => ToolCallInfo})

    corrections keyed by the original call ids



71
72
73
74
75
76
77
# File 'lib/ask/agent/tool_call_repair.rb', line 71

def call(chat:, calls:, tools:)
  if @callable
    normalize(@callable.call(chat, calls, tools))
  else
    built_in(chat, calls, tools)
  end
end