Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion go/adk/pkg/models/bedrock.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,10 +647,22 @@ func convertGenaiContentsToBedrockMessages(contents []*genai.Content, nameMap ma
if sanitized, ok := nameMap[callName]; ok {
callName = sanitized
}
// Bedrock requires toolUse.input to be a JSON object. A tool call
// with no arguments arrives here with a nil Args map (genai's
// FunctionCall.Args is `json:"args,omitempty"`, so an empty map is
// dropped when the event is persisted to the session store and
// reloaded as nil). NewLazyDocument(nil) serializes to `null`, which
// Bedrock rejects with "ValidationException: Malformed input request"
// ("The value at messages.N.content.M.toolUse.input is empty").
// Coerce nil to an empty object so no-argument tool calls round-trip.
args := part.FunctionCall.Args
if args == nil {
args = map[string]any{}
}
toolUse := types.ToolUseBlock{
ToolUseId: aws.String(sanitizeBedrockToolID(part.FunctionCall.ID, idMap, &idCounter)),
Name: aws.String(callName),
Input: document.NewLazyDocument(part.FunctionCall.Args),
Input: document.NewLazyDocument(args),
}
contentBlocks = append(contentBlocks, &types.ContentBlockMemberToolUse{
Value: toolUse,
Expand Down
38 changes: 38 additions & 0 deletions go/adk/pkg/models/bedrock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,44 @@ func TestConvertGenaiContentsToBedrockMessages(t *testing.T) {
}
}

// TestConvertGenaiContentsNoArgToolInput verifies that a tool call with no
// arguments serializes toolUse.input as an empty JSON object rather than null.
// A no-argument FunctionCall arrives here with a nil Args map because genai's
// FunctionCall.Args is `json:"args,omitempty"`, so the empty map is dropped when
// the event is persisted to the session store and reloaded. Bedrock Converse
// rejects a null input with "ValidationException: Malformed input request"
// ("The value at messages.N.content.M.toolUse.input is empty").
func TestConvertGenaiContentsNoArgToolInput(t *testing.T) {
for _, args := range []map[string]any{nil, {}} {
contents := []*genai.Content{
{
Role: "model",
Parts: []*genai.Part{
{FunctionCall: &genai.FunctionCall{ID: "call_noargs", Name: "datetime_get_current_time", Args: args}},
},
},
}
msgs, _ := convertGenaiContentsToBedrockMessages(contents, nil, nil)
if len(msgs) != 1 || len(msgs[0].Content) != 1 {
t.Fatalf("args=%v: expected 1 message with 1 content block, got %d messages", args, len(msgs))
}
tu, ok := msgs[0].Content[0].(*types.ContentBlockMemberToolUse)
if !ok {
t.Fatalf("args=%v: want *ContentBlockMemberToolUse, got %T", args, msgs[0].Content[0])
}
if tu.Value.Input == nil {
t.Fatalf("args=%v: toolUse.Input is nil; Bedrock requires a JSON object", args)
}
got := documentToMap(tu.Value.Input)
if got == nil {
t.Fatalf("args=%v: toolUse.Input serialized to null; want an empty JSON object {}", args)
}
if len(got) != 0 {
t.Errorf("args=%v: expected empty input object, got %v", args, got)
}
}
}

// TestConvertGenaiToolsToBedrock verifies schema conversion for all three tool
// sources: genai.Schema (declaration-based), map[string]any (MCP), and
// *jsonschema.Schema (functiontool.New).
Expand Down
Loading