mirror of
https://github.com/we-promise/sure.git
synced 2026-07-27 20:22:16 +00:00
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>
This commit is contained in:
54
test/models/assistant/function_tool_caller_test.rb
Normal file
54
test/models/assistant/function_tool_caller_test.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
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
|
||||
Reference in New Issue
Block a user