From e2870f4a6e0e03fe8ac1f75829a4cd36694a2223 Mon Sep 17 00:00:00 2001 From: yqz <2678785492@qq.com> Date: Wed, 29 Jul 2026 17:16:25 +0800 Subject: [PATCH] Preserve tool schema property order in Converse API ConverseApiUtils#convertMapToDocument collected map entries with a plain Collectors.toMap, which returns a HashMap and discards the insertion order of the source map. As a result, the properties of a tool's inputSchema were sent to Bedrock in an arbitrary order instead of the declared order. Collect into a LinkedHashMap to preserve the declaration order. Fixes #6696 Signed-off-by: yqz <2678785492@qq.com> --- .../converse/api/ConverseApiUtils.java | 4 +- .../converse/api/ConverseApiUtilsTests.java | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtilsTests.java diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java index 4b998422a8..96d21f818d 100644 --- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java +++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtils.java @@ -19,6 +19,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -100,7 +101,8 @@ public static Map getRequestMetadata(Map metadat private static Document convertMapToDocument(Map value) { Map attr = value.entrySet() .stream() - .collect(Collectors.toMap(e -> e.getKey(), e -> convertObjectToDocument(e.getValue()))); + .collect(Collectors.toMap(Map.Entry::getKey, e -> convertObjectToDocument(e.getValue()), (a, b) -> b, + LinkedHashMap::new)); return Document.fromMap(attr); } diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtilsTests.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtilsTests.java new file mode 100644 index 0000000000..38ffd1d88a --- /dev/null +++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/api/ConverseApiUtilsTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.bedrock.converse.api; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.document.Document; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConverseApiUtils}. + * + * @author yqz + */ +class ConverseApiUtilsTests { + + @Test + void convertMapPreservesKeyOrder() { + Map source = new LinkedHashMap<>(); + source.put("evidenceText", "text"); + source.put("justification", "reason"); + source.put("valid", true); + + Document document = ConverseApiUtils.convertObjectToDocument(source); + + assertThat(document.asMap().keySet()).containsExactly("evidenceText", "justification", "valid"); + } + + @Test + void convertNestedMapPreservesKeyOrder() { + Map properties = new LinkedHashMap<>(); + properties.put("zebra", "z"); + properties.put("apple", "a"); + properties.put("mango", "m"); + + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", properties); + + Document document = ConverseApiUtils.convertObjectToDocument(schema); + + assertThat(document.asMap().keySet()).containsExactly("type", "properties"); + assertThat(document.asMap().get("properties").asMap().keySet()).containsExactly("zebra", "apple", "mango"); + } + +}