Files
sure/test/models/assistant/function_tool_caller_test.rb
kai392 6e2cbb727a fix(ai): prevent Anthropic chat crash on no-argument tool calls (#2755)
* fix: prevent Anthropic chat crash on no-argument tool calls

When the model streams a tool_use block with no arguments (e.g.
get_categories), the accumulated input arrives as an empty string.
The Anthropic chat parser passed that empty string straight through as
function_args, and Assistant::FunctionToolCaller then called
JSON.parse("") — which raises "unexpected end of input", surfaces as a
Provider::Anthropic::Error, and kills the whole assistant turn.

Fix at the source by normalizing empty/nil tool input to an empty JSON
object in the parser, plus a defensive guard in FunctionToolCaller so
any provider that emits blank arguments cannot crash a turn.

Fixes #2722

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: assert nil-args result as Hash to match jsonb function_result

Addresses Codex P1 / CodeRabbit review: EchoFunction returns the parsed
params Hash and ToolCall::Function stores it in a jsonb column, so the
nil-arguments test must assert the Hash directly instead of JSON.parse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 04:47:15 +02:00

55 lines
1.6 KiB
Ruby

require "test_helper"
class Assistant::FunctionToolCallerTest < ActiveSupport::TestCase
# Minimal stub function that echoes back whatever args it receives.
class EchoFunction < Assistant::Function
def self.name = "echo"
def self.description = "Echoes the received arguments"
def call(params = {}) = params
end
FunctionRequest = Provider::LlmConcept::ChatFunctionRequest
setup do
@caller = Assistant::FunctionToolCaller.new([ EchoFunction.new(nil) ])
end
test "parses JSON arguments and forwards them to the function" do
request = FunctionRequest.new(
id: "call_1", call_id: "call_1", function_name: "echo",
function_args: { "foo" => "bar" }.to_json
)
result = @caller.fulfill_requests([ request ]).first
assert_equal({ "foo" => "bar" }, result.function_result)
end
test "treats empty-string arguments as an empty argument set" do
request = FunctionRequest.new(
id: "call_2", call_id: "call_2", function_name: "echo",
function_args: ""
)
# Regression for #2722: JSON.parse("") used to raise, killing the turn.
result = assert_nothing_raised do
@caller.fulfill_requests([ request ]).first
end
assert_equal({}, result.function_result)
end
test "treats nil arguments as an empty argument set" do
request = FunctionRequest.new(
id: "call_3", call_id: "call_3", function_name: "echo",
function_args: nil
)
result = assert_nothing_raised do
@caller.fulfill_requests([ request ]).first
end
assert_equal({}, result.function_result)
end
end