From 98a8dd4d4671cd777b04ba2224c89e90c7633e72 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Mon, 19 May 2025 13:44:20 +0100
Subject: [PATCH 001/165] Next development version for 1.0.x

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 advisors/spring-ai-advisors-vector-store/pom.xml                | 2 +-
 .../common/spring-ai-autoconfigure-retry/pom.xml                | 2 +-
 .../mcp/spring-ai-autoconfigure-mcp-client/pom.xml              | 2 +-
 .../mcp/spring-ai-autoconfigure-mcp-server/pom.xml              | 2 +-
 .../client/spring-ai-autoconfigure-model-chat-client/pom.xml    | 2 +-
 .../pom.xml                                                     | 2 +-
 .../pom.xml                                                     | 2 +-
 .../pom.xml                                                     | 2 +-
 .../memory/spring-ai-autoconfigure-model-chat-memory/pom.xml    | 2 +-
 .../spring-ai-autoconfigure-model-chat-observation/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-model-embedding-observation/pom.xml | 2 +-
 .../spring-ai-autoconfigure-model-image-observation/pom.xml     | 2 +-
 .../models/spring-ai-autoconfigure-model-anthropic/pom.xml      | 2 +-
 .../models/spring-ai-autoconfigure-model-azure-openai/pom.xml   | 2 +-
 .../models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml     | 2 +-
 .../models/spring-ai-autoconfigure-model-deepseek/pom.xml       | 2 +-
 .../models/spring-ai-autoconfigure-model-huggingface/pom.xml    | 2 +-
 .../models/spring-ai-autoconfigure-model-minimax/pom.xml        | 2 +-
 .../models/spring-ai-autoconfigure-model-mistral-ai/pom.xml     | 2 +-
 .../models/spring-ai-autoconfigure-model-oci-genai/pom.xml      | 2 +-
 .../models/spring-ai-autoconfigure-model-ollama/pom.xml         | 2 +-
 .../models/spring-ai-autoconfigure-model-openai/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-model-postgresml-embedding/pom.xml  | 2 +-
 .../models/spring-ai-autoconfigure-model-stability-ai/pom.xml   | 2 +-
 .../models/spring-ai-autoconfigure-model-transformers/pom.xml   | 2 +-
 .../models/spring-ai-autoconfigure-model-vertex-ai/pom.xml      | 2 +-
 .../models/spring-ai-autoconfigure-model-zhipuai/pom.xml        | 2 +-
 .../models/tool/spring-ai-autoconfigure-model-tool/pom.xml      | 2 +-
 .../pom.xml                                                     | 2 +-
 .../spring-ai-autoconfigure-vector-store-azure/pom.xml          | 2 +-
 .../spring-ai-autoconfigure-vector-store-cassandra/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-vector-store-chroma/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-couchbase/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml  | 2 +-
 .../spring-ai-autoconfigure-vector-store-gemfire/pom.xml        | 2 +-
 .../spring-ai-autoconfigure-vector-store-mariadb/pom.xml        | 2 +-
 .../spring-ai-autoconfigure-vector-store-milvus/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml  | 2 +-
 .../spring-ai-autoconfigure-vector-store-neo4j/pom.xml          | 2 +-
 .../spring-ai-autoconfigure-vector-store-observation/pom.xml    | 2 +-
 .../spring-ai-autoconfigure-vector-store-opensearch/pom.xml     | 2 +-
 .../spring-ai-autoconfigure-vector-store-oracle/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-pgvector/pom.xml       | 2 +-
 .../spring-ai-autoconfigure-vector-store-pinecone/pom.xml       | 2 +-
 .../spring-ai-autoconfigure-vector-store-qdrant/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-redis/pom.xml          | 2 +-
 .../spring-ai-autoconfigure-vector-store-typesense/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-vector-store-weaviate/pom.xml       | 2 +-
 document-readers/jsoup-reader/pom.xml                           | 2 +-
 document-readers/markdown-reader/pom.xml                        | 2 +-
 document-readers/pdf-reader/pom.xml                             | 2 +-
 document-readers/tika-reader/pom.xml                            | 2 +-
 mcp/common/pom.xml                                              | 2 +-
 .../spring-ai-model-chat-memory-repository-cassandra/pom.xml    | 2 +-
 .../spring-ai-model-chat-memory-repository-jdbc/pom.xml         | 2 +-
 .../spring-ai-model-chat-memory-repository-neo4j/pom.xml        | 2 +-
 models/spring-ai-anthropic/pom.xml                              | 2 +-
 models/spring-ai-azure-openai/pom.xml                           | 2 +-
 models/spring-ai-bedrock-converse/pom.xml                       | 2 +-
 models/spring-ai-bedrock/pom.xml                                | 2 +-
 models/spring-ai-deepseek/pom.xml                               | 2 +-
 models/spring-ai-huggingface/pom.xml                            | 2 +-
 models/spring-ai-minimax/pom.xml                                | 2 +-
 models/spring-ai-mistral-ai/pom.xml                             | 2 +-
 models/spring-ai-oci-genai/pom.xml                              | 2 +-
 models/spring-ai-ollama/pom.xml                                 | 2 +-
 models/spring-ai-openai/pom.xml                                 | 2 +-
 models/spring-ai-postgresml/pom.xml                             | 2 +-
 models/spring-ai-stability-ai/pom.xml                           | 2 +-
 models/spring-ai-transformers/pom.xml                           | 2 +-
 models/spring-ai-vertex-ai-embedding/pom.xml                    | 2 +-
 models/spring-ai-vertex-ai-gemini/pom.xml                       | 2 +-
 models/spring-ai-zhipuai/pom.xml                                | 2 +-
 pom.xml                                                         | 2 +-
 spring-ai-bom/pom.xml                                           | 2 +-
 spring-ai-client-chat/pom.xml                                   | 2 +-
 spring-ai-commons/pom.xml                                       | 2 +-
 spring-ai-docs/pom.xml                                          | 2 +-
 spring-ai-integration-tests/pom.xml                             | 2 +-
 spring-ai-model/pom.xml                                         | 2 +-
 spring-ai-rag/pom.xml                                           | 2 +-
 spring-ai-retry/pom.xml                                         | 2 +-
 spring-ai-spring-boot-docker-compose/pom.xml                    | 2 +-
 .../spring-ai-starter-mcp-client-webflux/pom.xml                | 2 +-
 .../spring-ai-starter-mcp-client/pom.xml                        | 2 +-
 .../spring-ai-starter-mcp-server-webflux/pom.xml                | 2 +-
 .../spring-ai-starter-mcp-server-webmvc/pom.xml                 | 2 +-
 .../spring-ai-starter-mcp-server/pom.xml                        | 2 +-
 .../spring-ai-starter-model-anthropic/pom.xml                   | 2 +-
 .../spring-ai-starter-model-azure-openai/pom.xml                | 2 +-
 .../spring-ai-starter-model-bedrock-converse/pom.xml            | 2 +-
 .../spring-ai-starter-model-bedrock/pom.xml                     | 2 +-
 .../pom.xml                                                     | 2 +-
 .../spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml | 2 +-
 .../pom.xml                                                     | 2 +-
 .../spring-ai-starter-model-chat-memory/pom.xml                 | 2 +-
 .../spring-ai-starter-model-deepseek/pom.xml                    | 2 +-
 .../spring-ai-starter-model-huggingface/pom.xml                 | 2 +-
 .../spring-ai-starter-model-minimax/pom.xml                     | 2 +-
 .../spring-ai-starter-model-mistral-ai/pom.xml                  | 2 +-
 .../spring-ai-starter-model-oci-genai/pom.xml                   | 2 +-
 .../spring-ai-starter-model-ollama/pom.xml                      | 2 +-
 .../spring-ai-starter-model-openai/pom.xml                      | 2 +-
 .../spring-ai-starter-model-postgresml-embedding/pom.xml        | 2 +-
 .../spring-ai-starter-model-stability-ai/pom.xml                | 2 +-
 .../spring-ai-starter-model-transformers/pom.xml                | 2 +-
 .../spring-ai-starter-model-vertex-ai-embedding/pom.xml         | 2 +-
 .../spring-ai-starter-model-vertex-ai-gemini/pom.xml            | 2 +-
 .../spring-ai-starter-model-zhipuai/pom.xml                     | 2 +-
 .../spring-ai-starter-vector-store-aws-opensearch/pom.xml       | 2 +-
 .../spring-ai-starter-vector-store-azure-cosmos-db/pom.xml      | 2 +-
 .../spring-ai-starter-vector-store-azure/pom.xml                | 2 +-
 .../spring-ai-starter-vector-store-cassandra/pom.xml            | 2 +-
 .../spring-ai-starter-vector-store-chroma/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-couchbase/pom.xml            | 2 +-
 .../spring-ai-starter-vector-store-elasticsearch/pom.xml        | 2 +-
 .../spring-ai-starter-vector-store-gemfire/pom.xml              | 2 +-
 .../spring-ai-starter-vector-store-mariadb/pom.xml              | 2 +-
 .../spring-ai-starter-vector-store-milvus/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-mongodb-atlas/pom.xml        | 2 +-
 .../spring-ai-starter-vector-store-neo4j/pom.xml                | 2 +-
 .../spring-ai-starter-vector-store-opensearch/pom.xml           | 2 +-
 .../spring-ai-starter-vector-store-oracle/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-pgvector/pom.xml             | 2 +-
 .../spring-ai-starter-vector-store-pinecone/pom.xml             | 2 +-
 .../spring-ai-starter-vector-store-qdrant/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-redis/pom.xml                | 2 +-
 .../spring-ai-starter-vector-store-typesense/pom.xml            | 2 +-
 .../spring-ai-starter-vector-store-weaviate/pom.xml             | 2 +-
 spring-ai-spring-boot-testcontainers/pom.xml                    | 2 +-
 spring-ai-spring-cloud-bindings/pom.xml                         | 2 +-
 spring-ai-template-st/pom.xml                                   | 2 +-
 spring-ai-test/pom.xml                                          | 2 +-
 spring-ai-vector-store/pom.xml                                  | 2 +-
 vector-stores/spring-ai-azure-cosmos-db-store/pom.xml           | 2 +-
 vector-stores/spring-ai-azure-store/pom.xml                     | 2 +-
 vector-stores/spring-ai-cassandra-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-chroma-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-coherence-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-couchbase-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-elasticsearch-store/pom.xml             | 2 +-
 vector-stores/spring-ai-gemfire-store/pom.xml                   | 2 +-
 vector-stores/spring-ai-hanadb-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-mariadb-store/pom.xml                   | 2 +-
 vector-stores/spring-ai-milvus-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-mongodb-atlas-store/pom.xml             | 2 +-
 vector-stores/spring-ai-neo4j-store/pom.xml                     | 2 +-
 vector-stores/spring-ai-opensearch-store/pom.xml                | 2 +-
 vector-stores/spring-ai-oracle-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-pgvector-store/pom.xml                  | 2 +-
 vector-stores/spring-ai-pinecone-store/pom.xml                  | 2 +-
 vector-stores/spring-ai-qdrant-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-redis-store/pom.xml                     | 2 +-
 vector-stores/spring-ai-typesense-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-weaviate-store/pom.xml                  | 2 +-
 155 files changed, 155 insertions(+), 155 deletions(-)

diff --git a/advisors/spring-ai-advisors-vector-store/pom.xml b/advisors/spring-ai-advisors-vector-store/pom.xml
index fda882c2290..f094c9df5ba 100644
--- a/advisors/spring-ai-advisors-vector-store/pom.xml
+++ b/advisors/spring-ai-advisors-vector-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-advisors-vector-store</artifactId>
diff --git a/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml b/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml
index 52bd6a0d099..ab878da5e05 100644
--- a/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml
+++ b/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-retry</artifactId>
diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
index bd8cbbeb0db..7787504d4bf 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-mcp-client</artifactId>
diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
index 30ef2430199..7020ff827c2 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-mcp-server</artifactId>
diff --git a/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
index 5bd73fbd15c..93780283062 100644
--- a/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
+++ b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml
index 3a9a9a781c9..cdab80a7697 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory-repository-cassandra</artifactId>
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml
index 15bca1709d9..84491eedaed 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory-repository-jdbc</artifactId>
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml
index 5e339564d1c..e584475c9b5 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory-repository-neo4j</artifactId>
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
index 00f7ce9fede..ea73c148a8a 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
index 72cff07eb3d..d760a6df5c0 100644
--- a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
index 772eb65deb8..50455e01ef0 100644
--- a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
index 983d09d77be..2eb5cc8e3e3 100644
--- a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
index df4f458475b..6c71b0a1309 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-anthropic</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
index 44045f9409c..78d8ad35f11 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-azure-openai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
index 3cb33b15aab..6a737d63508 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-bedrock-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml
index 55da0d2b083..5fc55ff2107 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-deepseek</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
index 75e4755ce6b..15b57fc893a 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-huggingface</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
index af836cb472a..18215041058 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-minimax</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
index 4a3855f29e2..e300f07a5fd 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-mistral-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
index cde01c136d2..d193038ac19 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-oci-genai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
index 7027a4bca37..3835b49709f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-ollama</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml
index 66b80907188..b60a19c411d 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-openai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
index ef28b5fae0e..412b343ffae 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-postgresml-embedding</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
index 31470a86484..46f5aae607e 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-stability-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
index e3654a5ad01..446d5d28119 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-transformers</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
index f511c9ed03c..6d21e817c1e 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-vertex-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
index f546b3ce832..5b52d6972d5 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-zhipuai</artifactId>
diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
index 0cb8b5f588d..5eba0822b0e 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml
index 240c0f8b44c..974c4cdf40e 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-azure-cosmos-db</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml
index 1f04be85c8a..d34dde6b534 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-azure</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml
index acd761f04f5..87dd31ded66 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-cassandra</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
index 5c125247a9e..bb852b30bfa 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-chroma</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml
index f2a16823dd7..0d55fd165ac 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-couchbase</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml
index 84146b7254e..99795e91067 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-elasticsearch</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml
index 124c2db9f5d..302e9050b49 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-gemfire</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml
index 26d5b8722dd..9668b56a9f4 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-mariadb</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml
index 9ac96971395..d73b264bcd8 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-milvus</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml
index 8d15ef0a36d..2effc4bc27b 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-mongodb-atlas</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml
index 881812f3b7b..77ecfa0db84 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-neo4j</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml
index b806ab6d09d..7172731631f 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-observation</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml
index ce06f0cc55a..df3001f492e 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-opensearch</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml
index 5aa358ea5f5..575ee6fbab7 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-oracle</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml
index 4c87fd74da9..e78c3e0a375 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-pgvector</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml
index ed9ee1f02d3..b615539d453 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-pinecone</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml
index cb82587c69f..21c90d36614 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-qdrant</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml
index 56c79ca4fe9..7db21f5218d 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-redis</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml
index d9a09d5a7f6..7bd3cf81edc 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-typesense</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml
index ee54615b089..3433194d1a4 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-weaviate</artifactId>
diff --git a/document-readers/jsoup-reader/pom.xml b/document-readers/jsoup-reader/pom.xml
index fc5dcec7574..07bb1ed8bca 100644
--- a/document-readers/jsoup-reader/pom.xml
+++ b/document-readers/jsoup-reader/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 
diff --git a/document-readers/markdown-reader/pom.xml b/document-readers/markdown-reader/pom.xml
index c9973ad184c..c12ef0525ad 100644
--- a/document-readers/markdown-reader/pom.xml
+++ b/document-readers/markdown-reader/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-markdown-document-reader</artifactId>
diff --git a/document-readers/pdf-reader/pom.xml b/document-readers/pdf-reader/pom.xml
index b4075898cf8..49798e78040 100644
--- a/document-readers/pdf-reader/pom.xml
+++ b/document-readers/pdf-reader/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-pdf-document-reader</artifactId>
diff --git a/document-readers/tika-reader/pom.xml b/document-readers/tika-reader/pom.xml
index 7bbca566336..b0e1cc7df42 100644
--- a/document-readers/tika-reader/pom.xml
+++ b/document-readers/tika-reader/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-tika-document-reader</artifactId>
diff --git a/mcp/common/pom.xml b/mcp/common/pom.xml
index 274060e8e14..37f816f5302 100644
--- a/mcp/common/pom.xml
+++ b/mcp/common/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-mcp</artifactId>
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml
index 1a8c92585df..16a009839ca 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
index c7b8cff636d..5be3bd8ca64 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml
index 3a128efd22e..eed467274cc 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/models/spring-ai-anthropic/pom.xml b/models/spring-ai-anthropic/pom.xml
index 80651f0121f..4467fe5e109 100644
--- a/models/spring-ai-anthropic/pom.xml
+++ b/models/spring-ai-anthropic/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-anthropic</artifactId>
diff --git a/models/spring-ai-azure-openai/pom.xml b/models/spring-ai-azure-openai/pom.xml
index d756172cb1a..ef46ac90713 100644
--- a/models/spring-ai-azure-openai/pom.xml
+++ b/models/spring-ai-azure-openai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-azure-openai</artifactId>
diff --git a/models/spring-ai-bedrock-converse/pom.xml b/models/spring-ai-bedrock-converse/pom.xml
index fe9574aea59..f1e003c666b 100644
--- a/models/spring-ai-bedrock-converse/pom.xml
+++ b/models/spring-ai-bedrock-converse/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-bedrock-converse</artifactId>
diff --git a/models/spring-ai-bedrock/pom.xml b/models/spring-ai-bedrock/pom.xml
index 7b6ea491384..7acbde4dd28 100644
--- a/models/spring-ai-bedrock/pom.xml
+++ b/models/spring-ai-bedrock/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-bedrock</artifactId>
diff --git a/models/spring-ai-deepseek/pom.xml b/models/spring-ai-deepseek/pom.xml
index 50f4ce4223e..491808c2c61 100644
--- a/models/spring-ai-deepseek/pom.xml
+++ b/models/spring-ai-deepseek/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-deepseek</artifactId>
diff --git a/models/spring-ai-huggingface/pom.xml b/models/spring-ai-huggingface/pom.xml
index fdbcdb900c8..0fa5852c9a8 100644
--- a/models/spring-ai-huggingface/pom.xml
+++ b/models/spring-ai-huggingface/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-huggingface</artifactId>
diff --git a/models/spring-ai-minimax/pom.xml b/models/spring-ai-minimax/pom.xml
index e69def3baf7..4ee50d7117c 100644
--- a/models/spring-ai-minimax/pom.xml
+++ b/models/spring-ai-minimax/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-minimax</artifactId>
diff --git a/models/spring-ai-mistral-ai/pom.xml b/models/spring-ai-mistral-ai/pom.xml
index 4a2c3faaf81..3381d23fbc1 100644
--- a/models/spring-ai-mistral-ai/pom.xml
+++ b/models/spring-ai-mistral-ai/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-mistral-ai</artifactId>
diff --git a/models/spring-ai-oci-genai/pom.xml b/models/spring-ai-oci-genai/pom.xml
index 31887a28440..0fe98c41dc6 100644
--- a/models/spring-ai-oci-genai/pom.xml
+++ b/models/spring-ai-oci-genai/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-oci-genai</artifactId>
diff --git a/models/spring-ai-ollama/pom.xml b/models/spring-ai-ollama/pom.xml
index 066e62dab16..c12e6acd68b 100644
--- a/models/spring-ai-ollama/pom.xml
+++ b/models/spring-ai-ollama/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/models/spring-ai-openai/pom.xml b/models/spring-ai-openai/pom.xml
index 409338840ab..3934f395017 100644
--- a/models/spring-ai-openai/pom.xml
+++ b/models/spring-ai-openai/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-openai</artifactId>
diff --git a/models/spring-ai-postgresml/pom.xml b/models/spring-ai-postgresml/pom.xml
index a3c457bd9c5..f5a4f65b268 100644
--- a/models/spring-ai-postgresml/pom.xml
+++ b/models/spring-ai-postgresml/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-postgresml</artifactId>
diff --git a/models/spring-ai-stability-ai/pom.xml b/models/spring-ai-stability-ai/pom.xml
index dde5b36aa56..71fbe1734ce 100644
--- a/models/spring-ai-stability-ai/pom.xml
+++ b/models/spring-ai-stability-ai/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-stability-ai</artifactId>
diff --git a/models/spring-ai-transformers/pom.xml b/models/spring-ai-transformers/pom.xml
index 06c03f82e8f..894c83e057f 100644
--- a/models/spring-ai-transformers/pom.xml
+++ b/models/spring-ai-transformers/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-transformers</artifactId>
diff --git a/models/spring-ai-vertex-ai-embedding/pom.xml b/models/spring-ai-vertex-ai-embedding/pom.xml
index 68ccf9fd23a..17c8acd5ab9 100644
--- a/models/spring-ai-vertex-ai-embedding/pom.xml
+++ b/models/spring-ai-vertex-ai-embedding/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-vertex-ai-embedding</artifactId>
diff --git a/models/spring-ai-vertex-ai-gemini/pom.xml b/models/spring-ai-vertex-ai-gemini/pom.xml
index 4bccd062699..7bcdfdfd3d1 100644
--- a/models/spring-ai-vertex-ai-gemini/pom.xml
+++ b/models/spring-ai-vertex-ai-gemini/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-vertex-ai-gemini</artifactId>
diff --git a/models/spring-ai-zhipuai/pom.xml b/models/spring-ai-zhipuai/pom.xml
index 7bff08ad3b6..2e894e41fbe 100644
--- a/models/spring-ai-zhipuai/pom.xml
+++ b/models/spring-ai-zhipuai/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-zhipuai</artifactId>
diff --git a/pom.xml b/pom.xml
index 57c03c1603d..83ca37920cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
 
 	<groupId>org.springframework.ai</groupId>
 	<artifactId>spring-ai-parent</artifactId>
-	<version>1.0.0</version>
+	<version>1.0.1-SNAPSHOT</version>
 
 	<packaging>pom</packaging>
 	<url>https://github.com/spring-projects/spring-ai</url>
diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml
index 5403344674d..b93c11b924d 100644
--- a/spring-ai-bom/pom.xml
+++ b/spring-ai-bom/pom.xml
@@ -21,7 +21,7 @@
 
 	<groupId>org.springframework.ai</groupId>
     <artifactId>spring-ai-bom</artifactId>
-	<version>1.0.0</version>
+	<version>1.0.1-SNAPSHOT</version>
 
     <packaging>pom</packaging>
 
diff --git a/spring-ai-client-chat/pom.xml b/spring-ai-client-chat/pom.xml
index 8c738c5888f..282991ac160 100644
--- a/spring-ai-client-chat/pom.xml
+++ b/spring-ai-client-chat/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-client-chat</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-commons/pom.xml b/spring-ai-commons/pom.xml
index f90b333b7f6..a1f324c63a0 100644
--- a/spring-ai-commons/pom.xml
+++ b/spring-ai-commons/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-commons</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-docs/pom.xml b/spring-ai-docs/pom.xml
index bb7252ffbde..d464f6d1169 100644
--- a/spring-ai-docs/pom.xml
+++ b/spring-ai-docs/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
     <artifactId>spring-ai-docs</artifactId>
     <name>Spring AI Docs</name>
diff --git a/spring-ai-integration-tests/pom.xml b/spring-ai-integration-tests/pom.xml
index f36e543ab8c..54dbd7cea92 100644
--- a/spring-ai-integration-tests/pom.xml
+++ b/spring-ai-integration-tests/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-integration-tests</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-model/pom.xml b/spring-ai-model/pom.xml
index eacd2d49485..a5333673b04 100644
--- a/spring-ai-model/pom.xml
+++ b/spring-ai-model/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-model</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-rag/pom.xml b/spring-ai-rag/pom.xml
index a42b42db5be..66b34ff4767 100644
--- a/spring-ai-rag/pom.xml
+++ b/spring-ai-rag/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-rag</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-retry/pom.xml b/spring-ai-retry/pom.xml
index daa6d3f0cb7..10e0468aa1c 100644
--- a/spring-ai-retry/pom.xml
+++ b/spring-ai-retry/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-retry</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml
index cef0d3e7293..b1c9a9184ee 100644
--- a/spring-ai-spring-boot-docker-compose/pom.xml
+++ b/spring-ai-spring-boot-docker-compose/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
     </parent>
     <artifactId>spring-ai-spring-boot-docker-compose</artifactId>
     <packaging>jar</packaging>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml
index 6facba46e82..f7c5796be8a 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml
index 71e99281d07..6645f0f2970 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-client</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml
index ede73a5920c..b94f718c1a6 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml
index d18f125f41d..16fa5fcd0ff 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml
index 4a29a11194a..976bea2ba80 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-server</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml
index 7a0ed3e54fe..1124debfeca 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-anthropic</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml
index fbc7dac318d..082bd729b5e 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-azure-openai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml
index a993adc69b9..e5aec20e6b4 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-bedrock-converse</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml
index a9aa40900d3..55867e4e989 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-bedrock</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml
index ea9167a98ca..dbdea050be5 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml
index 9c413b72c03..d5c6521a5c0 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml
index 472a56ee87f..3b29bc040a2 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml
index 960f8afae42..0f424b2de37 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml
index 2d1005c0352..2945d8480c7 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-deepseek</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml
index 1b5125b56f2..78a3f0ddde9 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-huggingface</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml
index ba328d156d2..dd4246f4ced 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-minimax</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml
index 7d95ebbff1f..8d7280848b1 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-mistral-ai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml
index 92b60fc92fe..ea4b1042a27 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-oci-genai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml
index 9fe74b1012c..78f17a61987 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-ollama</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml
index 585cf3f8515..bb6d4718ebd 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-openai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml
index 0f97c121fa4..9614ca2bf52 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-postgresml-embedding</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml
index 8d779bcf4d3..2235ec7bcf0 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-stability-ai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml
index bcdab23a237..64808f1617c 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-transformers</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml
index 157ebca8eaf..f957def38c9 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-vertex-ai-embedding</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml
index e7ea501f321..c2a22f1b169 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-vertex-ai-gemini</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml
index 463b856d486..615ae8b8500 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-zhipuai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml
index 494698bfa25..8fee162d08e 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-aws-opensearch</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml
index 88971cdd205..dc27d15123f 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-azure-cosmos-db</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml
index 2882102615f..ae9664081cd 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-azure</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml
index 6c5461b7fee..62574e2be66 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-cassandra</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml
index a5b7a5879ac..597a569805b 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-chroma</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml
index fc50911d334..2809074b13b 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-couchbase</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml
index 979e42b6a37..5f13b557f5a 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml
index a21d8e2ab12..51e1f7fd2fd 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-gemfire</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml
index e511d465d58..9b74ddaaf54 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-mariadb</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml
index fb2d1523a4f..0ab3ef296ed 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml
index 815a4c40ae4..e194c653054 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-mongodb-atlas</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml
index 1ddc07ef72a..43bcfd5661e 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-neo4j</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml
index a733c5b589f..39ad862d61f 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-opensearch</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml
index 9ac74605b2b..b2aba9a5907 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 	<artifactId>spring-ai-starter-vector-store-oracle</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml
index 31b8680c773..bdb5d6cbab1 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 	<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml
index 9f4170149eb..45da181b91e 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-pinecone</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml
index 3a7afb2aaa3..73efd8dfafa 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml
index f50a289a6fb..afa9086a94b 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-redis</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml
index af3cf87bbe7..164d9a11cb0 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-typesense</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml
index a40fa57bce0..2f12d1324a4 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-weaviate</artifactId>
diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml
index 92317d1cb56..0b81e6dd2e3 100644
--- a/spring-ai-spring-boot-testcontainers/pom.xml
+++ b/spring-ai-spring-boot-testcontainers/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-spring-boot-testcontainers</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-spring-cloud-bindings/pom.xml b/spring-ai-spring-cloud-bindings/pom.xml
index 28dbf6980f3..c685077f50e 100644
--- a/spring-ai-spring-cloud-bindings/pom.xml
+++ b/spring-ai-spring-cloud-bindings/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
     </parent>
     <artifactId>spring-ai-spring-cloud-bindings</artifactId>
     <packaging>jar</packaging>
diff --git a/spring-ai-template-st/pom.xml b/spring-ai-template-st/pom.xml
index 7314d0b286e..e0a63b66be3 100644
--- a/spring-ai-template-st/pom.xml
+++ b/spring-ai-template-st/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
     </parent>
     <artifactId>spring-ai-template-st</artifactId>
     <packaging>jar</packaging>
diff --git a/spring-ai-test/pom.xml b/spring-ai-test/pom.xml
index 9445cb8f944..c056c4a5d93 100644
--- a/spring-ai-test/pom.xml
+++ b/spring-ai-test/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-test</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-vector-store/pom.xml b/spring-ai-vector-store/pom.xml
index 27d96cc4bcc..2810dfd9648 100644
--- a/spring-ai-vector-store/pom.xml
+++ b/spring-ai-vector-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 	</parent>
 	<artifactId>spring-ai-vector-store</artifactId>
 	<packaging>jar</packaging>
diff --git a/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml b/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml
index 77f0693171e..4fc168b5125 100644
--- a/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml
+++ b/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-azure-cosmos-db-store</artifactId>
diff --git a/vector-stores/spring-ai-azure-store/pom.xml b/vector-stores/spring-ai-azure-store/pom.xml
index ac81e9ee740..5114ea5f003 100644
--- a/vector-stores/spring-ai-azure-store/pom.xml
+++ b/vector-stores/spring-ai-azure-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-azure-store</artifactId>
diff --git a/vector-stores/spring-ai-cassandra-store/pom.xml b/vector-stores/spring-ai-cassandra-store/pom.xml
index 026541f1eaa..b1c9e025dd0 100644
--- a/vector-stores/spring-ai-cassandra-store/pom.xml
+++ b/vector-stores/spring-ai-cassandra-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-cassandra-store</artifactId>
diff --git a/vector-stores/spring-ai-chroma-store/pom.xml b/vector-stores/spring-ai-chroma-store/pom.xml
index a0ae58fb98e..38eb2c6655f 100644
--- a/vector-stores/spring-ai-chroma-store/pom.xml
+++ b/vector-stores/spring-ai-chroma-store/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-chroma-store</artifactId>
diff --git a/vector-stores/spring-ai-coherence-store/pom.xml b/vector-stores/spring-ai-coherence-store/pom.xml
index 05accbd46ef..57497041f73 100644
--- a/vector-stores/spring-ai-coherence-store/pom.xml
+++ b/vector-stores/spring-ai-coherence-store/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-coherence-store</artifactId>
diff --git a/vector-stores/spring-ai-couchbase-store/pom.xml b/vector-stores/spring-ai-couchbase-store/pom.xml
index b268629e24b..9a7d99c1ddf 100644
--- a/vector-stores/spring-ai-couchbase-store/pom.xml
+++ b/vector-stores/spring-ai-couchbase-store/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-couchbase-store</artifactId>
diff --git a/vector-stores/spring-ai-elasticsearch-store/pom.xml b/vector-stores/spring-ai-elasticsearch-store/pom.xml
index 74dca3c4466..a75b4ad5518 100644
--- a/vector-stores/spring-ai-elasticsearch-store/pom.xml
+++ b/vector-stores/spring-ai-elasticsearch-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-elasticsearch-store</artifactId>
diff --git a/vector-stores/spring-ai-gemfire-store/pom.xml b/vector-stores/spring-ai-gemfire-store/pom.xml
index cbf2339925f..6bd940b5659 100644
--- a/vector-stores/spring-ai-gemfire-store/pom.xml
+++ b/vector-stores/spring-ai-gemfire-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-gemfire-store</artifactId>
diff --git a/vector-stores/spring-ai-hanadb-store/pom.xml b/vector-stores/spring-ai-hanadb-store/pom.xml
index c687a8536ee..231af602805 100644
--- a/vector-stores/spring-ai-hanadb-store/pom.xml
+++ b/vector-stores/spring-ai-hanadb-store/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-hanadb-store</artifactId>
diff --git a/vector-stores/spring-ai-mariadb-store/pom.xml b/vector-stores/spring-ai-mariadb-store/pom.xml
index 242de96bd85..0fba20cf107 100644
--- a/vector-stores/spring-ai-mariadb-store/pom.xml
+++ b/vector-stores/spring-ai-mariadb-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-mariadb-store</artifactId>
diff --git a/vector-stores/spring-ai-milvus-store/pom.xml b/vector-stores/spring-ai-milvus-store/pom.xml
index aed377cb01c..27c44fc6735 100644
--- a/vector-stores/spring-ai-milvus-store/pom.xml
+++ b/vector-stores/spring-ai-milvus-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-milvus-store</artifactId>
diff --git a/vector-stores/spring-ai-mongodb-atlas-store/pom.xml b/vector-stores/spring-ai-mongodb-atlas-store/pom.xml
index bde4f341d5f..872d91fc098 100644
--- a/vector-stores/spring-ai-mongodb-atlas-store/pom.xml
+++ b/vector-stores/spring-ai-mongodb-atlas-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-mongodb-atlas-store</artifactId>
diff --git a/vector-stores/spring-ai-neo4j-store/pom.xml b/vector-stores/spring-ai-neo4j-store/pom.xml
index 318c9004851..6c6ea72e5bb 100644
--- a/vector-stores/spring-ai-neo4j-store/pom.xml
+++ b/vector-stores/spring-ai-neo4j-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-neo4j-store</artifactId>
diff --git a/vector-stores/spring-ai-opensearch-store/pom.xml b/vector-stores/spring-ai-opensearch-store/pom.xml
index 495209c5c45..e7cd8a7dd60 100644
--- a/vector-stores/spring-ai-opensearch-store/pom.xml
+++ b/vector-stores/spring-ai-opensearch-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-opensearch-store</artifactId>
diff --git a/vector-stores/spring-ai-oracle-store/pom.xml b/vector-stores/spring-ai-oracle-store/pom.xml
index 01dbae39190..505abeac46f 100644
--- a/vector-stores/spring-ai-oracle-store/pom.xml
+++ b/vector-stores/spring-ai-oracle-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-oracle-store</artifactId>
diff --git a/vector-stores/spring-ai-pgvector-store/pom.xml b/vector-stores/spring-ai-pgvector-store/pom.xml
index 8ac47ab37a3..4adc860b230 100644
--- a/vector-stores/spring-ai-pgvector-store/pom.xml
+++ b/vector-stores/spring-ai-pgvector-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-pgvector-store</artifactId>
diff --git a/vector-stores/spring-ai-pinecone-store/pom.xml b/vector-stores/spring-ai-pinecone-store/pom.xml
index 5e2634cd625..9ebfa7210f3 100644
--- a/vector-stores/spring-ai-pinecone-store/pom.xml
+++ b/vector-stores/spring-ai-pinecone-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-pinecone-store</artifactId>
diff --git a/vector-stores/spring-ai-qdrant-store/pom.xml b/vector-stores/spring-ai-qdrant-store/pom.xml
index 6286ba58d4d..c24303a5a5d 100644
--- a/vector-stores/spring-ai-qdrant-store/pom.xml
+++ b/vector-stores/spring-ai-qdrant-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-qdrant-store</artifactId>
diff --git a/vector-stores/spring-ai-redis-store/pom.xml b/vector-stores/spring-ai-redis-store/pom.xml
index f845b2311c9..c856bc85903 100644
--- a/vector-stores/spring-ai-redis-store/pom.xml
+++ b/vector-stores/spring-ai-redis-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.0</version>
+		<version>1.0.1-SNAPSHOT</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-redis-store</artifactId>
diff --git a/vector-stores/spring-ai-typesense-store/pom.xml b/vector-stores/spring-ai-typesense-store/pom.xml
index 038904e6a61..d5c90214494 100644
--- a/vector-stores/spring-ai-typesense-store/pom.xml
+++ b/vector-stores/spring-ai-typesense-store/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/vector-stores/spring-ai-weaviate-store/pom.xml b/vector-stores/spring-ai-weaviate-store/pom.xml
index c2344329359..1f7aa0434b8 100644
--- a/vector-stores/spring-ai-weaviate-store/pom.xml
+++ b/vector-stores/spring-ai-weaviate-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.0</version>
+        <version>1.0.1-SNAPSHOT</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-weaviate-store</artifactId>

From fb106ab4e016069eb03eeab030859f0f729608d7 Mon Sep 17 00:00:00 2001
From: Dan Sarlo <81384153+dsarlo-viso@users.noreply.github.com>
Date: Wed, 21 May 2025 15:48:01 -0400
Subject: [PATCH 002/165] Fix datasource connection leak when using the
 JdbcChatMemoryRepository

Signed-off-by: Dan Sarlo <81384153+dsarlo-viso@users.noreply.github.com>
---
 .../repository/jdbc/JdbcChatMemoryRepositoryDialect.java     | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java
index 3d7032df4bd..526c0908c77 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryDialect.java
@@ -17,6 +17,7 @@
 package org.springframework.ai.chat.memory.repository.jdbc;
 
 import javax.sql.DataSource;
+import java.sql.Connection;
 
 /**
  * Abstraction for database-specific SQL for chat memory repository.
@@ -53,8 +54,8 @@ public interface JdbcChatMemoryRepositoryDialect {
 	 */
 	static JdbcChatMemoryRepositoryDialect from(DataSource dataSource) {
 		// Simple detection (could be improved)
-		try {
-			String url = dataSource.getConnection().getMetaData().getURL().toLowerCase();
+		try (Connection connection = dataSource.getConnection()) {
+			String url = connection.getMetaData().getURL().toLowerCase();
 			if (url.contains("postgresql")) {
 				return new PostgresChatMemoryRepositoryDialect();
 			}

From fd9fe798a127cbc5baaf58f2db71b108a6da0fb4 Mon Sep 17 00:00:00 2001
From: Soby Chacko <soby.chacko@broadcom.com>
Date: Wed, 21 May 2025 19:34:15 -0400
Subject: [PATCH 003/165] test: fix useAutoConfiguredJdbcChatMemoryRepository
 test after connection leak fix

Add schema initialization property after commit 560540f properly closed connections

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
---
 ...positoryPostgresqlAutoConfigurationIT.java | 31 ++++++++++---------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java
index db75aa22d5d..c6bde91fff4 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java
@@ -73,28 +73,29 @@ void initializeSchemaEmbeddedDefault() {
 
 	@Test
 	void useAutoConfiguredJdbcChatMemoryRepository() {
-		this.contextRunner.run(context -> {
-			var chatMemoryRepository = context.getBean(JdbcChatMemoryRepository.class);
-			var conversationId = UUID.randomUUID().toString();
-			var userMessage = new UserMessage("Message from the user");
+		this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
+			.run(context -> {
+				var chatMemoryRepository = context.getBean(JdbcChatMemoryRepository.class);
+				var conversationId = UUID.randomUUID().toString();
+				var userMessage = new UserMessage("Message from the user");
 
-			chatMemoryRepository.saveAll(conversationId, List.of(userMessage));
+				chatMemoryRepository.saveAll(conversationId, List.of(userMessage));
 
-			assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(1);
-			assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(List.of(userMessage));
+				assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(1);
+				assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(List.of(userMessage));
 
-			chatMemoryRepository.deleteByConversationId(conversationId);
+				chatMemoryRepository.deleteByConversationId(conversationId);
 
-			assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEmpty();
+				assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEmpty();
 
-			var multipleMessages = List.<Message>of(new UserMessage("Message from the user 1"),
-					new AssistantMessage("Message from the assistant 1"));
+				var multipleMessages = List.<Message>of(new UserMessage("Message from the user 1"),
+						new AssistantMessage("Message from the assistant 1"));
 
-			chatMemoryRepository.saveAll(conversationId, multipleMessages);
+				chatMemoryRepository.saveAll(conversationId, multipleMessages);
 
-			assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(multipleMessages.size());
-			assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(multipleMessages);
-		});
+				assertThat(chatMemoryRepository.findByConversationId(conversationId)).hasSize(multipleMessages.size());
+				assertThat(chatMemoryRepository.findByConversationId(conversationId)).isEqualTo(multipleMessages);
+			});
 	}
 
 	@Test

From 3068c04dca7bb66fe6fa064cd52e3f0aea3cab73 Mon Sep 17 00:00:00 2001
From: Christian Tzolov <christian.tzolov@broadcom.com>
Date: Wed, 21 May 2025 01:57:53 +0200
Subject: [PATCH 004/165] feat(tools): Add configurable exception handling for
 tool execution

Adds a new configuration property `spring.ai.tools.throw-exception-on-error` that controls how tool execution errors are handled:
- When false (default): errors are converted to messages and sent back to the AI model
- When true: errors are thrown as exceptions for the caller to handle

The implementation:
- Adds the property to ToolCallingProperties
- Updates ToolCallingAutoConfiguration to use the property
- Improves error handling in MCP tool callbacks to use ToolExecutionException
- Adds tests to verify both behaviors
- Updates documentation with the new property

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
---
 .../ToolCallingAutoConfiguration.java         |  4 +-
 .../autoconfigure/ToolCallingProperties.java  | 15 +++++
 .../ToolCallingAutoConfigurationTests.java    | 58 +++++++++++++++++++
 .../ai/mcp/AsyncMcpToolCallback.java          | 24 +++++---
 .../ai/mcp/SyncMcpToolCallback.java           | 24 ++++++--
 .../antora/modules/ROOT/pages/api/tools.adoc  | 13 ++++-
 6 files changed, 124 insertions(+), 14 deletions(-)

diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
index bdc647db8f7..5ab883df2b4 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
@@ -77,8 +77,8 @@ ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationC
 
 	@Bean
 	@ConditionalOnMissingBean
-	ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() {
-		return new DefaultToolExecutionExceptionProcessor(false);
+	ToolExecutionExceptionProcessor toolExecutionExceptionProcessor(ToolCallingProperties properties) {
+		return new DefaultToolExecutionExceptionProcessor(properties.isThrowExceptionOnError());
 	}
 
 	@Bean
diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java
index 8511baad413..9ac33eb620a 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java
@@ -31,6 +31,21 @@ public class ToolCallingProperties {
 
 	private final Observations observations = new Observations();
 
+	/**
+	 * If true, tool calling errors are thrown as exceptions for the caller to handle. If
+	 * false, errors are converted to messages and sent back to the AI model, allowing it
+	 * to process and respond to the error.
+	 */
+	private boolean throwExceptionOnError = false;
+
+	public boolean isThrowExceptionOnError() {
+		return this.throwExceptionOnError;
+	}
+
+	public void setThrowExceptionOnError(boolean throwExceptionOnError) {
+		this.throwExceptionOnError = throwExceptionOnError;
+	}
+
 	public static class Observations {
 
 		/**
diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java
index 0b7b638160f..af42d744158 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java
@@ -26,7 +26,9 @@
 import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.ToolCallbackProvider;
 import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.definition.ToolDefinition;
 import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
+import org.springframework.ai.tool.execution.ToolExecutionException;
 import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor;
 import org.springframework.ai.tool.function.FunctionToolCallback;
 import org.springframework.ai.tool.method.MethodToolCallback;
@@ -127,6 +129,62 @@ void observationFilterEnabled() {
 			.run(context -> assertThat(context).hasSingleBean(ToolCallingContentObservationFilter.class));
 	}
 
+	@Test
+	void throwExceptionOnErrorDefault() {
+		new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class))
+			.withUserConfiguration(Config.class)
+			.run(context -> {
+				var toolExecutionExceptionProcessor = context.getBean(ToolExecutionExceptionProcessor.class);
+				assertThat(toolExecutionExceptionProcessor).isInstanceOf(DefaultToolExecutionExceptionProcessor.class);
+
+				// Test behavior instead of accessing private field
+				// Create a mock tool definition and exception
+				var toolDefinition = ToolDefinition.builder()
+					.name("testTool")
+					.description("Test tool for exception handling")
+					.inputSchema("{\"type\":\"object\",\"properties\":{\"test\":{\"type\":\"string\"}}}")
+					.build();
+				var cause = new RuntimeException("Test error");
+				var exception = new ToolExecutionException(toolDefinition, cause);
+
+				// Default behavior should not throw exception
+				String result = toolExecutionExceptionProcessor.process(exception);
+				assertThat(result).isEqualTo("Test error");
+			});
+	}
+
+	@Test
+	void throwExceptionOnErrorEnabled() {
+		new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class))
+			.withPropertyValues("spring.ai.tools.throw-exception-on-error=true")
+			.withUserConfiguration(Config.class)
+			.run(context -> {
+				var toolExecutionExceptionProcessor = context.getBean(ToolExecutionExceptionProcessor.class);
+				assertThat(toolExecutionExceptionProcessor).isInstanceOf(DefaultToolExecutionExceptionProcessor.class);
+
+				// Test behavior instead of accessing private field
+				// Create a mock tool definition and exception
+				var toolDefinition = ToolDefinition.builder()
+					.name("testTool")
+					.description("Test tool for exception handling")
+					.inputSchema("{\"type\":\"object\",\"properties\":{\"test\":{\"type\":\"string\"}}}")
+					.build();
+				var cause = new RuntimeException("Test error");
+				var exception = new ToolExecutionException(toolDefinition, cause);
+
+				// When property is set to true, it should throw the exception
+				assertThat(toolExecutionExceptionProcessor).extracting(processor -> {
+					try {
+						processor.process(exception);
+						return "No exception thrown";
+					}
+					catch (ToolExecutionException e) {
+						return "Exception thrown";
+					}
+				}).isEqualTo("Exception thrown");
+			});
+	}
+
 	static class WeatherService {
 
 		@Tool(description = "Get the weather in location. Return temperature in 36°F or 36°C format.")
diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
index d54e1a8b12a..9d634b73999 100644
--- a/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
+++ b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
+import org.springframework.ai.tool.execution.ToolExecutionException;
 
 /**
  * Implementation of {@link ToolCallback} that adapts MCP tools to Spring AI's tool
@@ -41,7 +42,9 @@
  * <li>Manages JSON serialization/deserialization of tool inputs and outputs</li>
  * </ul>
  * <p>
- * Example usage: <pre>{@code
+ * Example usage:
+ *
+ * <pre>{@code
  * McpAsyncClient mcpClient = // obtain MCP client
  * Tool mcpTool = // obtain MCP tool definition
  * ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
@@ -109,12 +112,19 @@ public String call(String functionInput) {
 		Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
 		// Note that we use the original tool name here, not the adapted one from
 		// getToolDefinition
-		return this.asyncMcpClient.callTool(new CallToolRequest(this.tool.name(), arguments)).map(response -> {
-			if (response.isError() != null && response.isError()) {
-				throw new IllegalStateException("Error calling tool: " + response.content());
-			}
-			return ModelOptionsUtils.toJsonString(response.content());
-		}).block();
+		try {
+			return this.asyncMcpClient.callTool(new CallToolRequest(this.tool.name(), arguments)).map(response -> {
+				if (response.isError() != null && response.isError()) {
+					throw new ToolExecutionException(this.getToolDefinition(),
+							new IllegalStateException("Error calling tool: " + response.content()));
+				}
+				return ModelOptionsUtils.toJsonString(response.content());
+			}).block();
+		}
+		catch (Exception ex) {
+			throw new ToolExecutionException(this.getToolDefinition(), ex.getCause());
+		}
+
 	}
 
 	@Override
diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
index 441b3d4d508..442f21eb89a 100644
--- a/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
+++ b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
@@ -16,18 +16,23 @@
 
 package org.springframework.ai.mcp;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.Map;
 
 import io.modelcontextprotocol.client.McpSyncClient;
 import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
 import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
 import io.modelcontextprotocol.spec.McpSchema.Tool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import org.springframework.ai.chat.model.ToolContext;
 import org.springframework.ai.model.ModelOptionsUtils;
 import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
+import org.springframework.ai.tool.execution.ToolExecutionException;
+import org.springframework.core.log.LogAccessor;
 
 /**
  * Implementation of {@link ToolCallback} that adapts MCP tools to Spring AI's tool
@@ -61,6 +66,8 @@
  */
 public class SyncMcpToolCallback implements ToolCallback {
 
+	private static final Logger logger = LoggerFactory.getLogger(SyncMcpToolCallback.class);
+
 	private final McpSyncClient mcpClient;
 
 	private final Tool tool;
@@ -113,11 +120,20 @@ public String call(String functionInput) {
 		Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
 		// Note that we use the original tool name here, not the adapted one from
 		// getToolDefinition
-		CallToolResult response = this.mcpClient.callTool(new CallToolRequest(this.tool.name(), arguments));
-		if (response.isError() != null && response.isError()) {
-			throw new IllegalStateException("Error calling tool: " + response.content());
+		try {
+			CallToolResult response = this.mcpClient.callTool(new CallToolRequest(this.tool.name(), arguments));
+			if (response.isError() != null && response.isError()) {
+				logger.error("Error calling tool: {}", response.content());
+				throw new ToolExecutionException(this.getToolDefinition(),
+						new IllegalStateException("Error calling tool: " + response.content()));
+			}
+			return ModelOptionsUtils.toJsonString(response.content());
+		}
+		catch (Exception ex) {
+			logger.error("Exception while tool calling: ", ex);
+			throw new ToolExecutionException(this.getToolDefinition(), ex.getCause());
 		}
-		return ModelOptionsUtils.toJsonString(response.content());
+
 	}
 
 	@Override
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index d6982733536..e136e9a6e04 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -1180,7 +1180,8 @@ ChatResponse newResponse = chatModel.call(new Prompt(chatMemory.get(conversation
 
 === Exception Handling
 
-When a tool call fails, the exception is propagated as a `ToolExecutionException` which can be caught to handle the error. A `ToolExecutionExceptionProcessor` can be used to handle a `ToolExecutionException` with two outcomes: either producing an error message to be sent back to the AI model or throwing an exception to be handled by the caller.
+When a tool call fails, the exception is propagated as a `ToolExecutionException` which can be caught to handle the error. 
+A `ToolExecutionExceptionProcessor` can be used to handle a `ToolExecutionException` with two outcomes: either producing an error message to be sent back to the AI model or throwing an exception to be handled by the caller.
 
 [source,java]
 ----
@@ -1198,6 +1199,16 @@ public interface ToolExecutionExceptionProcessor {
 
 If you're using any of the Spring AI Spring Boot Starters, `DefaultToolExecutionExceptionProcessor` is the autoconfigured implementation of the `ToolExecutionExceptionProcessor` interface. By default, the error message is sent back to the model. The `DefaultToolExecutionExceptionProcessor` constructor lets you set the `alwaysThrow` attribute to `true` or `false`. If `true`, an exception will be thrown instead of sending an error message back to the model.
 
+You can use the ``spring.ai.tools.throw-exception-on-error` property to control the behavior of the `DefaultToolExecutionExceptionProcessor` bean:
+
+[cols="6,3,1", stripes=even]
+|====
+| Property | Description | Default
+
+| `spring.ai.tools.throw-exception-on-error` | If `true`, tool calling errors are thrown as exceptions for the caller to handle. If `false`, errors are converted to messages and sent back to the AI model, allowing it to process and respond to the error.| `false`
+|====
+
+
 [source,java]
 ----
 @Bean

From 0c9d0e49fa8de99eada7224982be580d8e1bbf2a Mon Sep 17 00:00:00 2001
From: Soby Chacko <soby.chacko@broadcom.com>
Date: Wed, 21 May 2025 12:27:22 -0400
Subject: [PATCH 005/165] Checkstyle fixes

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
---
 .../McpToolCallbackAutoConfiguration.java     |  1 -
 .../OpenSearchVectorStoreProperties.java      |  2 +-
 .../jdbc/JdbcChatMemoryRepository.java        |  3 +--
 .../AbstractJdbcChatMemoryRepositoryIT.java   | 15 +++--------
 .../converse/BedrockProxyChatModel.java       |  2 +-
 .../converse/BedrockProxyChatModelTest.java   |  4 +--
 .../ai/bedrock/api/AbstractBedrockApi.java    | 27 ++++++++++---------
 .../bedrock/api/AbstractBedrockApiTest.java   | 20 +++++++-------
 .../chat/evaluation/RelevancyEvaluator.java   |  8 +++---
 .../evaluation/RelevancyEvaluatorTests.java   |  1 +
 .../TranslationQueryTransformer.java          |  1 -
 .../ConcatenationDocumentJoinerTests.java     |  1 +
 .../PgVectorStoreWithChatMemoryAdvisorIT.java |  1 -
 13 files changed, 41 insertions(+), 45 deletions(-)

diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/McpToolCallbackAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/McpToolCallbackAutoConfiguration.java
index 4206fcbe8c4..53083e7620d 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/McpToolCallbackAutoConfiguration.java
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/McpToolCallbackAutoConfiguration.java
@@ -24,7 +24,6 @@
 import org.springframework.ai.mcp.AsyncMcpToolCallbackProvider;
 import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
 import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
-import org.springframework.ai.tool.ToolCallbackProvider;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreProperties.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreProperties.java
index 6f57e7f42b8..449304655cd 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreProperties.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/main/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreProperties.java
@@ -129,7 +129,7 @@ public void setReadTimeout(Duration readTimeout) {
 	}
 
 	public String getPathPrefix() {
-		return pathPrefix;
+		return this.pathPrefix;
 	}
 
 	public void setPathPrefix(String pathPrefix) {
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepository.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepository.java
index f7c4b96cd83..1f666e202ab 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepository.java
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepository.java
@@ -21,7 +21,6 @@
 import java.sql.SQLException;
 import java.sql.Timestamp;
 import java.time.Instant;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -78,7 +77,7 @@ private JdbcChatMemoryRepository(JdbcTemplate jdbcTemplate, JdbcChatMemoryReposi
 
 	@Override
 	public List<String> findConversationIds() {
-		return this.jdbcTemplate.queryForList(dialect.getSelectConversationIdsSql(), String.class);
+		return this.jdbcTemplate.queryForList(this.dialect.getSelectConversationIdsSql(), String.class);
 	}
 
 	@Override
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java
index 269d91c671b..c26dd69049d 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java
@@ -41,13 +41,6 @@
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.test.context.ContextConfiguration;
 
-import java.sql.Timestamp;
-import java.util.List;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import javax.sql.DataSource;
-
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
@@ -78,7 +71,7 @@ void saveMessagesSingleMessage(String content, MessageType messageType) {
 
 		this.chatMemoryRepository.saveAll(conversationId, List.of(message));
 
-		assertThat(chatMemoryRepository.findConversationIds()).contains(conversationId);
+		assertThat(this.chatMemoryRepository.findConversationIds()).contains(conversationId);
 
 		// Use dialect to get the appropriate SQL query
 		JdbcChatMemoryRepositoryDialect dialect = JdbcChatMemoryRepositoryDialect
@@ -103,7 +96,7 @@ void saveMessagesMultipleMessages() {
 
 		this.chatMemoryRepository.saveAll(conversationId, messages);
 
-		assertThat(chatMemoryRepository.findConversationIds()).contains(conversationId);
+		assertThat(this.chatMemoryRepository.findConversationIds()).contains(conversationId);
 
 		// Use dialect to get the appropriate SQL query
 		JdbcChatMemoryRepositoryDialect dialect = JdbcChatMemoryRepositoryDialect
@@ -179,10 +172,10 @@ void testMessageOrder() {
 
 		// Save messages in the expected order
 		List<Message> orderedMessages = List.of(firstMessage, secondMessage, thirdMessage, fourthMessage);
-		chatMemoryRepository.saveAll(conversationId, orderedMessages);
+		this.chatMemoryRepository.saveAll(conversationId, orderedMessages);
 
 		// Retrieve messages using the repository
-		List<Message> retrievedMessages = chatMemoryRepository.findByConversationId(conversationId);
+		List<Message> retrievedMessages = this.chatMemoryRepository.findByConversationId(conversationId);
 		assertThat(retrievedMessages).hasSize(4);
 
 		// Get the actual order from the retrieved messages
diff --git a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java
index 380951c7265..d30f2517756 100644
--- a/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java
+++ b/models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModel.java
@@ -791,7 +791,7 @@ public static final class Builder {
 
 		private Builder() {
 			try {
-				region = DefaultAwsRegionProviderChain.builder().build().getRegion();
+				this.region = DefaultAwsRegionProviderChain.builder().build().getRegion();
 			}
 			catch (SdkClientException e) {
 				logger.warn("Failed to load region from DefaultAwsRegionProviderChain, using US_EAST_1", e);
diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelTest.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelTest.java
index 4d52311280d..b2d725f3784 100644
--- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelTest.java
+++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockProxyChatModelTest.java
@@ -37,9 +37,9 @@ class BedrockProxyChatModelTest {
 	@Test
 	void shouldIgnoreExceptionAndUseDefault() {
 		try (MockedStatic<DefaultAwsRegionProviderChain> mocked = mockStatic(DefaultAwsRegionProviderChain.class)) {
-			when(awsRegionProviderBuilder.build().getRegion())
+			when(this.awsRegionProviderBuilder.build().getRegion())
 				.thenThrow(SdkClientException.builder().message("failed load").build());
-			mocked.when(DefaultAwsRegionProviderChain::builder).thenReturn(awsRegionProviderBuilder);
+			mocked.when(DefaultAwsRegionProviderChain::builder).thenReturn(this.awsRegionProviderBuilder);
 			BedrockProxyChatModel.builder().build();
 		}
 	}
diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java
index fc78d8af6bf..e062570d0e9 100644
--- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java
+++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java
@@ -30,7 +30,6 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.util.ObjectUtils;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Sinks;
 import reactor.core.publisher.Sinks.EmitFailureHandler;
@@ -50,6 +49,7 @@
 
 import org.springframework.ai.model.ModelOptionsUtils;
 import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
 
 /**
  * Abstract class for the Bedrock API. It provides the basic functionality to invoke the chat completion model and
@@ -322,6 +322,20 @@ protected Flux<SO> internalInvocationStream(I request, Class<SO> clazz) {
 		return eventSink.asFlux();
 	}
 
+	private Region getRegion(Region region) {
+		if (ObjectUtils.isEmpty(region)) {
+			try {
+				return DefaultAwsRegionProviderChain.builder().build().getRegion();
+			}
+			catch (SdkClientException e) {
+				throw new IllegalArgumentException("Region is empty and cannot be loaded from DefaultAwsRegionProviderChain: " + e.getMessage(), e);
+			}
+		}
+		else {
+			return region;
+		}
+	}
+
 	/**
 	 * Encapsulates the metrics about the model invocation.
 	 * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html
@@ -341,16 +355,5 @@ public record AmazonBedrockInvocationMetrics(
 			@JsonProperty("invocationLatency") Long invocationLatency) {
 	}
 
-	private Region getRegion(Region region) {
-		if (ObjectUtils.isEmpty(region)) {
-			try {
-				return DefaultAwsRegionProviderChain.builder().build().getRegion();
-			} catch (SdkClientException e) {
-				throw new IllegalArgumentException("Region is empty and cannot be loaded from DefaultAwsRegionProviderChain: " + e.getMessage(), e);
-			}
-		} else {
-			return region;
-		}
-	}
 }
 // @formatter:on
diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/AbstractBedrockApiTest.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/AbstractBedrockApiTest.java
index 700b47b6bbb..1a6f20d88b6 100644
--- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/AbstractBedrockApiTest.java
+++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/AbstractBedrockApiTest.java
@@ -16,6 +16,8 @@
 
 package org.springframework.ai.bedrock.api;
 
+import java.time.Duration;
+
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -28,11 +30,11 @@
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
 
-import java.time.Duration;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
 class AbstractBedrockApiTest {
@@ -49,10 +51,10 @@ class AbstractBedrockApiTest {
 	@Test
 	void shouldLoadRegionFromAwsDefaults() {
 		try (MockedStatic<DefaultAwsRegionProviderChain> mocked = mockStatic(DefaultAwsRegionProviderChain.class)) {
-			when(awsRegionProviderBuilder.build().getRegion()).thenReturn(Region.AF_SOUTH_1);
-			mocked.when(DefaultAwsRegionProviderChain::builder).thenReturn(awsRegionProviderBuilder);
+			when(this.awsRegionProviderBuilder.build().getRegion()).thenReturn(Region.AF_SOUTH_1);
+			mocked.when(DefaultAwsRegionProviderChain::builder).thenReturn(this.awsRegionProviderBuilder);
 			AbstractBedrockApi<Object, Object, Object> testBedrockApi = new TestBedrockApi("modelId",
-					awsCredentialsProvider, null, objectMapper, Duration.ofMinutes(5));
+					this.awsCredentialsProvider, null, this.objectMapper, Duration.ofMinutes(5));
 			assertThat(testBedrockApi.getRegion()).isEqualTo(Region.AF_SOUTH_1);
 		}
 	}
@@ -60,10 +62,10 @@ void shouldLoadRegionFromAwsDefaults() {
 	@Test
 	void shouldThrowIllegalArgumentIfAwsDefaultsFailed() {
 		try (MockedStatic<DefaultAwsRegionProviderChain> mocked = mockStatic(DefaultAwsRegionProviderChain.class)) {
-			when(awsRegionProviderBuilder.build().getRegion())
+			when(this.awsRegionProviderBuilder.build().getRegion())
 				.thenThrow(SdkClientException.builder().message("failed load").build());
-			mocked.when(DefaultAwsRegionProviderChain::builder).thenReturn(awsRegionProviderBuilder);
-			assertThatThrownBy(() -> new TestBedrockApi("modelId", awsCredentialsProvider, null, objectMapper,
+			mocked.when(DefaultAwsRegionProviderChain::builder).thenReturn(this.awsRegionProviderBuilder);
+			assertThatThrownBy(() -> new TestBedrockApi("modelId", this.awsCredentialsProvider, null, this.objectMapper,
 					Duration.ofMinutes(5)))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessageContaining("failed load");
diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/evaluation/RelevancyEvaluator.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/evaluation/RelevancyEvaluator.java
index 62f7ff7c8e6..4f083f8ce76 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/evaluation/RelevancyEvaluator.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/evaluation/RelevancyEvaluator.java
@@ -16,6 +16,9 @@
 
 package org.springframework.ai.chat.evaluation;
 
+import java.util.Collections;
+import java.util.Map;
+
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.ai.chat.prompt.PromptTemplate;
 import org.springframework.ai.evaluation.EvaluationRequest;
@@ -24,9 +27,6 @@
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
 
-import java.util.Collections;
-import java.util.Map;
-
 /**
  * Evaluates the relevancy of a response to a query based on the context provided.
  */
@@ -91,7 +91,7 @@ public static Builder builder() {
 		return new Builder();
 	}
 
-	public static class Builder {
+	public static final class Builder {
 
 		private ChatClient.Builder chatClientBuilder;
 
diff --git a/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/evaluation/RelevancyEvaluatorTests.java b/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/evaluation/RelevancyEvaluatorTests.java
index f13260d7fd3..36bed49fd95 100644
--- a/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/evaluation/RelevancyEvaluatorTests.java
+++ b/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/evaluation/RelevancyEvaluatorTests.java
@@ -17,6 +17,7 @@
 package org.springframework.ai.chat.evaluation;
 
 import org.junit.jupiter.api.Test;
+
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.ai.chat.model.ChatModel;
 
diff --git a/spring-ai-rag/src/main/java/org/springframework/ai/rag/preretrieval/query/transformation/TranslationQueryTransformer.java b/spring-ai-rag/src/main/java/org/springframework/ai/rag/preretrieval/query/transformation/TranslationQueryTransformer.java
index 3a1db430458..359d3e9383e 100644
--- a/spring-ai-rag/src/main/java/org/springframework/ai/rag/preretrieval/query/transformation/TranslationQueryTransformer.java
+++ b/spring-ai-rag/src/main/java/org/springframework/ai/rag/preretrieval/query/transformation/TranslationQueryTransformer.java
@@ -20,7 +20,6 @@
 import org.slf4j.LoggerFactory;
 
 import org.springframework.ai.chat.client.ChatClient;
-import org.springframework.ai.chat.prompt.ChatOptions;
 import org.springframework.ai.chat.prompt.PromptTemplate;
 import org.springframework.ai.rag.Query;
 import org.springframework.ai.rag.util.PromptAssert;
diff --git a/spring-ai-rag/src/test/java/org/springframework/ai/rag/retrieval/join/ConcatenationDocumentJoinerTests.java b/spring-ai-rag/src/test/java/org/springframework/ai/rag/retrieval/join/ConcatenationDocumentJoinerTests.java
index b7035b1e39a..f99f30d153a 100644
--- a/spring-ai-rag/src/test/java/org/springframework/ai/rag/retrieval/join/ConcatenationDocumentJoinerTests.java
+++ b/spring-ai-rag/src/test/java/org/springframework/ai/rag/retrieval/join/ConcatenationDocumentJoinerTests.java
@@ -21,6 +21,7 @@
 import java.util.Map;
 
 import org.junit.jupiter.api.Test;
+
 import org.springframework.ai.document.Document;
 import org.springframework.ai.rag.Query;
 
diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java
index 40c315c4287..9d180365805 100644
--- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java
+++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java
@@ -47,7 +47,6 @@
 import org.springframework.jdbc.core.JdbcTemplate;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;

From 62e7547c90511ff23182cad0a4a391f9508d099c Mon Sep 17 00:00:00 2001
From: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
Date: Thu, 22 May 2025 13:41:08 +0700
Subject: [PATCH 006/165] Fix typos (#3263)

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
---
 .../modules/ROOT/pages/api/chat/openai-chat.adoc |  6 +++---
 .../api/mcp/mcp-server-boot-starter-docs.adoc    | 16 ++++++++--------
 .../modules/ROOT/pages/api/tools-migration.adoc  |  2 +-
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
index 67b28f4e827..2dd6fd042f9 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
@@ -302,7 +302,7 @@ TIP: You can pass multiple audio files as well.
 OpenAI models that offer input audio multimodal support include `gpt-4o-audio-preview`.
 Refer to the link:https://platform.openai.com/docs/guides/audio[Audio] guide for more information.
 
-The OpenAI link:https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages[Assystant Message API] can contain a list of base64-encoded audio files with the message.
+The OpenAI link:https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages[Assistant Message API] can contain a list of base64-encoded audio files with the message.
 Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 Currently, OpenAI support only the following audio types: `audio/mp3` and `audio/wav`.
@@ -325,12 +325,12 @@ String text = response.getResult().getOutput().getContent(); // audio transcript
 byte[] waveAudio = response.getResult().getOutput().getMedia().get(0).getDataAsByteArray(); // audio data
 ----
 
-You have to specify an `audio` modality in the `OpenAiChatOptions` to generate audio output. 
+You have to specify an `audio` modality in the `OpenAiChatOptions` to generate audio output.
 The `AudioParameters` class provides the voice and audio format for the audio output.
 
 == Structured Outputs
 
-OpenAI provides custom https://platform.openai.com/docs/guides/structured-outputs[Structured Outputs] APIs that ensure your model generates responses conforming strictly to your provided `JSON Schema`. 
+OpenAI provides custom https://platform.openai.com/docs/guides/structured-outputs[Structured Outputs] APIs that ensure your model generates responses conforming strictly to your provided `JSON Schema`.
 In addition to the existing Spring AI model-agnostic xref::api/structured-output-converter.adoc[Structured Output Converter], these APIs offer enhanced control and precision.
 
 NOTE: Currently, OpenAI supports a link:https://platform.openai.com/docs/guides/structured-outputs/supported-schemas[subset of the JSON Schema language] format.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
index d9344f2d820..5f98f2f60d5 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
@@ -103,19 +103,19 @@ All properties are prefixed with `spring.ai.mcp.server`:
 |`tool-response-mime-type` |(optional) response MIME type per tool name. For example `spring.ai.mcp.server.tool-response-mime-type.generateImage=image/png` will associate the `image/png` mime type with the `generateImage()` tool name |`-`
 |`sse-message-endpoint` | Custom SSE Message endpoint path for web transport to be used by the client to send messages|`/mcp/message`
 |`sse-endpoint` |Custom SSE endpoint path for web transport |`/sse`
-|`base-url` | Optional URL prefix. For example `base-url=/api/v1` means that the client should access the sse endpont at `/api/v1` + `sse-endpoint` and the message endpoint is `/api/v1` + `sse-message-endpoint` | -
+|`base-url` | Optional URL prefix. For example `base-url=/api/v1` means that the client should access the sse endpoint at `/api/v1` + `sse-endpoint` and the message endpoint is `/api/v1` + `sse-message-endpoint` | -
 |`request-timeout` | Duration to wait for server responses before timing out requests. Applies to all requests made through the client, including tool calls, resource access, and prompt operations. | `20` seconds
 |===
 
 == Sync/Async Server Types
 
-* **Synchronous Server** - The default server type implemented using `McpSyncServer`. 
-It is designed for straightforward request-response patterns in your applications. 
-To enable this server type, set `spring.ai.mcp.server.type=SYNC` in your configuration. 
+* **Synchronous Server** - The default server type implemented using `McpSyncServer`.
+It is designed for straightforward request-response patterns in your applications.
+To enable this server type, set `spring.ai.mcp.server.type=SYNC` in your configuration.
 When activated, it automatically handles the configuration of synchronous tool specifications.
 
-* **Asynchronous Server** - The asynchronous server implementation uses `McpAsyncServer` and is optimized for non-blocking operations. 
-To enable this server type, configure your application with `spring.ai.mcp.server.type=ASYNC`. 
+* **Asynchronous Server** - The asynchronous server implementation uses `McpAsyncServer` and is optimized for non-blocking operations.
+To enable this server type, configure your application with `spring.ai.mcp.server.type=ASYNC`.
 This server type automatically sets up asynchronous tool specifications with built-in Project Reactor support.
 
 == Server Capabilities
@@ -250,7 +250,7 @@ Provides a standardized way for servers to expose completion capabilities to cli
 @Bean
 public List<McpServerFeatures.SyncCompletionSpecification> myCompletions() {
     var completion = new McpServerFeatures.SyncCompletionSpecification(
-        "code-completion", 
+        "code-completion",
         "Provides code completion suggestions",
         (exchange, request) -> {
             // Implementation that returns completion suggestions
@@ -260,7 +260,7 @@ public List<McpServerFeatures.SyncCompletionSpecification> myCompletions() {
             ));
         }
     );
-    
+
     return List.of(completion);
 }
 ----
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools-migration.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools-migration.adoc
index 429bc11aa92..b04ee1b1555 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools-migration.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools-migration.adoc
@@ -109,7 +109,7 @@ class WeatherTools {
 }
 ----
 
-And you can use the same `ChatClient#tools()` API to register method-based tool callbackes:
+And you can use the same `ChatClient#tools()` API to register method-based tool callbacks:
 
 [source,java]
 ----

From 4d716d5cd6d42ddbc3c45d181c38b405643b5a64 Mon Sep 17 00:00:00 2001
From: Hyunsik Aeom <aeomhs@gmail.com>
Date: Wed, 21 May 2025 14:02:09 +0900
Subject: [PATCH 007/165] fix: Add document's title

https://docs.spring.io/spring-ai/reference/api/effective-agents.html
---
 .../main/antora/modules/ROOT/pages/api/effective-agents.adoc   | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc
index fd5f1ea8538..439732923f9 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc
@@ -1,3 +1,6 @@
+[[effective-agents]]
+= Building Effective Agents
+
 In a recent research publication, https://www.anthropic.com/research/building-effective-agents[Building Effective Agents], Anthropic shared valuable insights about building effective Large Language Model (LLM) agents. What makes this research particularly interesting is its emphasis on simplicity and composability over complex frameworks. Let's explore how these principles translate into practical implementations using https://docs.spring.io/spring-ai/reference/index.html[Spring AI].
 
 image::https://raw.githubusercontent.com/spring-io/spring-io-static/refs/heads/main/blog/tzolov/spring-ai-agentic-systems.jpg[Agent Systems, width=350]

From e41f2b5bd25e97a0833146b1e7212955db0cac50 Mon Sep 17 00:00:00 2001
From: Jemin Huh <hjm1980@gmail.com>
Date: Thu, 22 May 2025 16:01:30 +0900
Subject: [PATCH 008/165] Fix incorrect external link formatting in
 effective-agents.adoc (#3275)

Signed-off-by: Jemin Huh <hjm1980@gmail.com>
---
 .../main/antora/modules/ROOT/pages/api/effective-agents.adoc  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc
index 439732923f9..af20ac32b19 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/effective-agents.adoc
@@ -110,7 +110,7 @@ String input = "My account was charged twice last week";
 String response = workflow.route(input, routes);
 ----
 
-=== 4. https://github.com/spring-projects/spring-ai-examples/tree/main/agentic-patterns/orchestrator-workers-workflow[Orchestrator-Workers]
+=== 4. https://github.com/spring-projects/spring-ai-examples/tree/main/agentic-patterns/orchestrator-workers[Orchestrator-Workers]
 
 image::https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F8985fc683fae4780fb34eab1365ab78c7e51bc8e-2401x1000.png&w=3840&q=75[Orchestration Workflow]
 
@@ -150,7 +150,7 @@ System.out.println("Analysis: " + response.analysis());
 System.out.println("Worker Outputs: " + response.workerResponses());
 ----
 
-=== 5. https://github.com/spring-projects/spring-ai-examples/tree/main/agentic-patterns/evaluator-optimizer-workflow[Evaluator-Optimizer]
+=== 5. https://github.com/spring-projects/spring-ai-examples/tree/main/agentic-patterns/evaluator-optimizer[Evaluator-Optimizer]
 
 image::https://www.anthropic.com/_next/image?url=https%3A%2F%2Fwww-cdn.anthropic.com%2Fimages%2F4zrzovbb%2Fwebsite%2F14f51e6406ccb29e695da48b17017e899a6119c7-2401x1000.png&w=3840&q=75[Evaluator-Optimizer Workflow]
 

From ea38612fe0a3aee15f23f39dd41f93294b64df5b Mon Sep 17 00:00:00 2001
From: Yang Buyi <52228969+yangbuyiya@users.noreply.github.com>
Date: Thu, 22 May 2025 18:07:58 +0800
Subject: [PATCH 009/165] Update mcp-server-boot-starter-docs.adoc (#3279)

Older STDIO transports can't be pulled, this should be a milestone version and should use `spring-ai-starter-mcp-server`.

Signed-off-by: Yang Buyi <52228969+yangbuyiya@users.noreply.github.com>
---
 .../ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
index 5f98f2f60d5..9d718c50089 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
@@ -28,7 +28,7 @@ Full MCP Server features support with `STDIO` server transport.
 ----
 <dependency>
     <groupId>org.springframework.ai</groupId>
-    <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
+    <artifactId>spring-ai-starter-mcp-server</artifactId>
 </dependency>
 ----
 

From a6858cfdc00e7ad4f38cda1f8956b8e5e078e556 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= <eddu.melendez@gmail.com>
Date: Mon, 19 May 2025 10:15:18 -0600
Subject: [PATCH 010/165] Add IT for Chat Memory with Sql Server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Eddú Meléndez <eddu.melendez@gmail.com>
---
 .../JdbcChatMemoryRepositorySqlServerIT.java  | 36 +++++++++++++++++++
 .../container-license-acceptance.txt          |  1 +
 2 files changed, 37 insertions(+)
 create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositorySqlServerIT.java
 create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/resources/container-license-acceptance.txt

diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositorySqlServerIT.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositorySqlServerIT.java
new file mode 100644
index 00000000000..f36e38f305f
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositorySqlServerIT.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023-2025 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.chat.memory.repository.jdbc;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.jdbc.Sql;
+
+/**
+ * Integration tests for {@link JdbcChatMemoryRepository} with SQL Server.
+ *
+ * @author Jonathan Leijendekker
+ * @author Thomas Vitale
+ * @author Mark Pollack
+ * @author Yanming Zhou
+ * @author Eddú Meléndez
+ */
+@SpringBootTest(properties = "spring.datasource.url=jdbc:tc:sqlserver:2022-latest:///")
+@Sql(scripts = "classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-sqlserver.sql",
+		executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
+class JdbcChatMemoryRepositorySqlServerIT extends AbstractJdbcChatMemoryRepositoryIT {
+
+}
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/resources/container-license-acceptance.txt b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/resources/container-license-acceptance.txt
new file mode 100644
index 00000000000..de8cca62a2b
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/resources/container-license-acceptance.txt
@@ -0,0 +1 @@
+mcr.microsoft.com/mssql/server:2022-latest
\ No newline at end of file

From cc5379344fad8ba5652c926112bd6b0c125bc118 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?=
 <eddu.melendez@gmail.com>
Date: Thu, 22 May 2025 09:36:49 -0600
Subject: [PATCH 011/165] Fix tools docs (#3292)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Eddú Meléndez <eddu.melendez@gmail.com>
---
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index e136e9a6e04..286fb7f4c69 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -625,7 +625,7 @@ The tool will only be available for the specific chat request it's added to.
 ----
 ChatClient.create(chatModel)
     .prompt("What's the weather like in Copenhagen?")
-    .tools("currentWeather")
+    .toolNames("currentWeather")
     .call()
     .content();
 ----
@@ -654,7 +654,7 @@ When using the dynamic specification approach, you can pass the tool name to the
 ChatModel chatModel = ...
 ChatOptions chatOptions = ToolCallingChatOptions.builder()
     .toolNames("currentWeather")
-    .build():
+    .build();
 Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions);
 chatModel.call(prompt);
 ----

From 03a2cb76edfe5dd06bf2175d64c5cfc5f4fdcc77 Mon Sep 17 00:00:00 2001
From: Cho-Hyun-Seung <105296338+Cho-Hyun-Seung@users.noreply.github.com>
Date: Tue, 27 May 2025 15:43:15 +0900
Subject: [PATCH 012/165] docs: update custom API key example using
 OpenAiChatModel.builder() (#3335)

Signed-off-by: Cho-Hyun-Seung <toki0327@naver.com>
---
 .../antora/modules/ROOT/pages/api/chat/openai-chat.adoc  | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
index 2dd6fd042f9..e47672c0f85 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
@@ -631,9 +631,12 @@ OpenAiApi openAiApi = OpenAiApi.builder()
     .apiKey(customApiKey)
     .build();
 
-// Create a chat client with the custom OpenAiApi instance
-OpenAiChatClient chatClient = new OpenAiChatClient(openAiApi);
-
+// Create a chat model with the custom OpenAiApi instance
+OpenAiChatmodel chatModel = OpenAiChatModel.builder()
+    .openAiApi(openAiApi)
+    .build();
+// Build the ChatClient using the custom chat model
+ChatClient openAiChatClient = ChatClient.builder(chatModel).build();
 ----
 
 This is useful when you need to:

From 544a448982a80ba2b5ecfe3169185ab579f199ff Mon Sep 17 00:00:00 2001
From: Jemin Huh <hjm1980@gmail.com>
Date: Tue, 27 May 2025 15:46:54 +0900
Subject: [PATCH 013/165] Fix awesome-spring-ai link to official repository
 (#3328)

Signed-off-by: Jemin Huh <hjm1980@gmail.com>
---
 README.md                                                       | 2 +-
 .../src/main/antora/modules/ROOT/pages/getting-started.adoc     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 981cf047248..4227900016b 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Please refer to the [Getting Started Guide](https://docs.spring.io/spring-ai/ref
 * [Documentation](https://docs.spring.io/spring-ai/reference/)
 * [Issues](https://github.com/spring-projects/spring-ai/issues)
 <!-- * [Discussions](https://github.com/spring-projects/spring-ai/discussions) - Go here if you have a question, suggestion, or feedback! -->
-* [Awesome Spring AI](https://github.com/danvega/awesome-spring-ai) - A curated list of awesome resources, tools, tutorials, and projects for building generative AI applications using Spring AI
+* [Awesome Spring AI](https://github.com/spring-ai-community/awesome-spring-ai) - A curated list of awesome resources, tools, tutorials, and projects for building generative AI applications using Spring AI
 * [Spring AI Examples](https://github.com/spring-projects/spring-ai-examples) contains example projects that explain specific features in more detail.
 
 ## Breaking changes
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc
index 475b1559efc..03f043b45d0 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc
@@ -153,4 +153,4 @@ Each of the following sections in the documentation shows which dependencies you
 
 == Spring AI samples
 
-Please refer to https://github.com/danvega/awesome-spring-ai[this page] for more resources and samples related to Spring AI.
+Please refer to https://github.com/spring-ai-community/awesome-spring-ai[this page] for more resources and samples related to Spring AI.

From 3fa218afaa7d869f66fe45fb1953d8cc73bed74d Mon Sep 17 00:00:00 2001
From: Mingyuan Wu <my.wu@outlook.com>
Date: Tue, 27 May 2025 15:10:59 +0800
Subject: [PATCH 014/165] Add metadata support for vector store types (#3325)

Signed-off-by: Mingyuan Wu <my.wu@outlook.com>
---
 .../additional-spring-configuration-metadata.json  | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
 create mode 100644 spring-ai-vector-store/src/main/resources/META-INF/additional-spring-configuration-metadata.json

diff --git a/spring-ai-vector-store/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-ai-vector-store/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 00000000000..546599fc45b
--- /dev/null
+++ b/spring-ai-vector-store/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,14 @@
+{
+  "groups": [],
+  "properties": [
+    {
+      "name": "spring.ai.vectorstore.type",
+      "type": "java.lang.String",
+      "description": "Vector store type. Requires a vector store."
+    }
+  ],
+  "hints": [],
+  "ignored": {
+    "properties": []
+  }
+}
\ No newline at end of file

From f6d73a910ef4b0962051826b86621dbdf1ac0b9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Henning=20P=C3=B6ttker?=
 <25299532+hpoettker@users.noreply.github.com>
Date: Tue, 27 May 2025 11:39:07 +0200
Subject: [PATCH 015/165] Fix links in documentation to `Media.java` (#3322)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Henning Pöttker <25299532+hpoettker@users.noreply.github.com>
---
 .../modules/ROOT/pages/api/chat/azure-openai-chat.adoc      | 2 +-
 .../antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc  | 2 +-
 .../antora/modules/ROOT/pages/api/chat/ollama-chat.adoc     | 2 +-
 .../antora/modules/ROOT/pages/api/chat/openai-chat.adoc     | 6 +++---
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
index 139b0d2f3b3..1b174d59faa 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
@@ -279,7 +279,7 @@ Multimodality refers to a model's ability to simultaneously understand and proce
 Presently, the Azure OpenAI `gpt-4o` model offers multimodal support.
 
 The Azure OpenAI can incorporate a list of base64-encoded images or image urls with the message.
-Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/model/Media.java[Media] type.
+Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `java.lang.Object` for the raw media data.
 
 Below is a code example excerpted from link:https://github.com/spring-projects/spring-ai/blob/c9a3e66f90187ce7eae7eb78c462ec622685de6c/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java#L293[OpenAiChatModelIT.java], illustrating the fusion of user text with an image using the `GPT_4_O` model.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc
index 14ae3217a2b..c5d37107a87 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc
@@ -197,7 +197,7 @@ Mistral AI models that offer vision multimodal support include `pixtral-large-la
 Refer to the link:https://docs.mistral.ai/capabilities/vision/[Vision] guide for more information.
 
 The Mistral AI link:https://docs.mistral.ai/api/#tag/chat/operation/chat_completion_v1_chat_completions_post[User Message API] can incorporate a list of base64-encoded images or image urls with the message.
-Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/model/Media.java[Media] type.
+Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 
 Below is a code example excerpted from `MistralAiChatModelIT.java`, illustrating the fusion of user text with an image.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc
index 2fa76b994c9..4a4c1c50cb4 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc
@@ -258,7 +258,7 @@ For further details, refer to the link:https://llava-vl.github.io/[LLaVA: Large
 
 The Ollama link:https://github.com/ollama/ollama/blob/main/docs/api.md#parameters-1[Message API] provides an "images" parameter to incorporate a list of base64-encoded images with the message.
 
-Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/model/Media.java[Media] type.
+Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 
 Below is a straightforward code example excerpted from link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java[OllamaChatModelMultimodalIT.java], illustrating the fusion of user text with an image.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
index e47672c0f85..43f5f9469e7 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
@@ -225,7 +225,7 @@ OpenAI models that offer vision multimodal support include `gpt-4`, `gpt-4o`, an
 Refer to the link:https://platform.openai.com/docs/guides/vision[Vision] guide for more information.
 
 The OpenAI link:https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages[User Message API] can incorporate a list of base64-encoded images or image urls with the message.
-Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/model/Media.java[Media] type.
+Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 
 Below is a code example excerpted from link:https://github.com/spring-projects/spring-ai/blob/c9a3e66f90187ce7eae7eb78c462ec622685de6c/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java#L293[OpenAiChatModelIT.java], illustrating the fusion of user text with an image using the `gpt-4o` model.
@@ -278,7 +278,7 @@ OpenAI models that offer input audio multimodal support include `gpt-4o-audio-pr
 Refer to the link:https://platform.openai.com/docs/guides/audio[Audio] guide for more information.
 
 The OpenAI link:https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages[User Message API] can incorporate a list of base64-encoded audio files with the message.
-Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/messages/Media.java[Media] type.
+Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 Currently, OpenAI support only the following media types: `audio/mp3` and `audio/wav`.
 
@@ -303,7 +303,7 @@ OpenAI models that offer input audio multimodal support include `gpt-4o-audio-pr
 Refer to the link:https://platform.openai.com/docs/guides/audio[Audio] guide for more information.
 
 The OpenAI link:https://platform.openai.com/docs/api-reference/chat/create#chat-create-messages[Assistant Message API] can contain a list of base64-encoded audio files with the message.
-Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Media.java[Media] type.
+Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 Currently, OpenAI support only the following audio types: `audio/mp3` and `audio/wav`.
 

From aba837a6cc1b0a9f952b292ae348175eb6cade10 Mon Sep 17 00:00:00 2001
From: Christian Tzolov <christian.tzolov@broadcom.com>
Date: Mon, 26 May 2025 18:20:11 +0200
Subject: [PATCH 016/165] feat(chroma): improve handling and testing of complex
 metadata values

- Convert non-primitive metadata values to JSON strings in ChromaApi.AddEmbeddingsRequest for compatibility
- Add tests to verify metadata conversion and complex metadata handling in Chroma vector store integration
- Ensure OpenAiChatModel always returns annotations as a list of maps in metadata
- Add test dependency on spring-ai-advisors-vector-store for advisor-related tests
- Add json-unit-assertj for improved JSON assertions and validating JSON content in tests

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
---
 .../pom.xml                                   |  7 ++++
 .../ChromaVectorStoreAutoConfigurationIT.java | 40 +++++++++++++++++++
 .../ai/openai/OpenAiChatModel.java            |  2 +-
 pom.xml                                       |  1 +
 vector-stores/spring-ai-chroma-store/pom.xml  |  8 ++++
 .../ai/chroma/vectorstore/ChromaApi.java      | 23 ++++++++++-
 .../ai/chroma/vectorstore/ChromaApiIT.java    | 18 +++++++++
 7 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
index bb852b30bfa..e3da8946946 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
@@ -103,5 +103,12 @@
 			<version>${project.parent.version}</version>
 			<scope>test</scope>
 		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.ai</groupId>
+			<artifactId>spring-ai-advisors-vector-store</artifactId>
+			<version>${project.parent.version}</version>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 </project>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java
index 62c2f87b51e..a96073bfbdf 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java
@@ -28,6 +28,13 @@
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
 
+import org.springframework.ai.chat.client.ChatClientRequest;
+import org.springframework.ai.chat.client.ChatClientResponse;
+import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.model.Generation;
+import org.springframework.ai.chat.prompt.Prompt;
 import org.springframework.ai.chroma.vectorstore.ChromaVectorStore;
 import org.springframework.ai.document.Document;
 import org.springframework.ai.embedding.EmbeddingModel;
@@ -68,6 +75,39 @@ public class ChromaVectorStoreAutoConfigurationIT {
 				"spring.ai.vectorstore.chroma.client.port=" + chroma.getMappedPort(8000),
 				"spring.ai.vectorstore.chroma.collectionName=TestCollection");
 
+	@Test
+	public void verifyThatChromaCanHandleComplexMetadataValues() {
+		this.contextRunner.withPropertyValues("spring.ai.vectorstore.chroma.initializeSchema=true").run(context -> {
+
+			VectorStore vectorStore = context.getBean(VectorStore.class);
+
+			VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
+				.defaultTopK(5)
+				.build();
+
+			assertThat(advisor.getName()).isEqualTo("VectorStoreChatMemoryAdvisor");
+
+			var req = ChatClientRequest.builder().prompt(Prompt.builder().content("UserPrompt").build()).build();
+
+			ChatClientRequest req2 = advisor.before(req, null);
+			assertThat(req2).isNotNull();
+
+			var response = ChatClientResponse.builder()
+				.chatResponse(ChatResponse.builder()
+					.generations(List
+						.of(new Generation(new AssistantMessage("AssistantMessage", Map.of("annotations", List.of())))))
+					.build())
+				.build();
+			var res2 = advisor.after(response, null);
+			assertThat(res2).isNotNull();
+
+			// Remove all documents from the store
+			List<Document> docs = vectorStore.similaritySearch("UserPrompt, AssistantMessage");
+			vectorStore.delete(docs.stream().map(doc -> doc.getId()).toList());
+
+		});
+	}
+
 	@Test
 	public void addAndSearchWithFilters() {
 
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
index 58a0062eccd..74c776e6dd0 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
@@ -219,7 +219,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
 							"index", choice.index(),
 							"finishReason", choice.finishReason() != null ? choice.finishReason().name() : "",
 							"refusal", StringUtils.hasText(choice.message().refusal()) ? choice.message().refusal() : "",
-							"annotations", choice.message().annotations() != null ? choice.message().annotations() : List.of());
+							"annotations", choice.message().annotations() != null ? choice.message().annotations() : List.of(Map.of()));
 					return buildGeneration(choice, metadata, request);
 				}).toList();
 				// @formatter:on
diff --git a/pom.xml b/pom.xml
index 83ca37920cb..a5f3b507db1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -309,6 +309,7 @@
 
 		<!-- testing dependencies -->
 		<okhttp3.version>4.12.0</okhttp3.version>
+		<json-unit-assertj.version>4.1.0</json-unit-assertj.version>
 
 		<!-- MCP-->
 		<mcp.sdk.version>0.10.0</mcp.sdk.version>
diff --git a/vector-stores/spring-ai-chroma-store/pom.xml b/vector-stores/spring-ai-chroma-store/pom.xml
index 38eb2c6655f..afb332a980d 100644
--- a/vector-stores/spring-ai-chroma-store/pom.xml
+++ b/vector-stores/spring-ai-chroma-store/pom.xml
@@ -95,6 +95,14 @@
 			<version>${project.parent.version}</version>
 			<scope>test</scope>
 		</dependency>
+
+		<dependency>
+			<groupId>net.javacrumbs.json-unit</groupId>
+			<artifactId>json-unit-assertj</artifactId>
+			<version>${json-unit-assertj.version}</version>
+			<scope>test</scope>
+		</dependency>
+
 	</dependencies>
 
 </project>
diff --git a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java
index 9c55e6011c5..79166c0c2a1 100644
--- a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java
+++ b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaApi.java
@@ -30,6 +30,7 @@
 
 import org.springframework.ai.chroma.vectorstore.ChromaApi.QueryRequest.Include;
 import org.springframework.ai.chroma.vectorstore.common.ChromaApiConstants;
+import org.springframework.ai.util.json.JsonParser;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.client.support.BasicAuthenticationInterceptor;
@@ -443,7 +444,27 @@ public record AddEmbeddingsRequest(// @formatter:off
 			@JsonProperty("metadatas") List<Map<String, Object>> metadata,
 			@JsonProperty("documents") List<String> documents) { // @formatter:on
 
-		// Convenance for adding a single embedding.
+		public AddEmbeddingsRequest {
+			// Process metadata to ensure all values are Integer, Boolean, or String.
+			// Other types are converted to JSON string using JsonParser.toJson().
+			List<Map<String, Object>> processedMetadatas = new ArrayList<>();
+			for (Map<String, Object> meta : metadata) {
+				Map<String, Object> processed = new HashMap<>();
+				for (Map.Entry<String, Object> entry : meta.entrySet()) {
+					Object value = entry.getValue();
+					if (value instanceof Number || value instanceof Boolean || value instanceof String) {
+						processed.put(entry.getKey(), value);
+					}
+					else {
+						processed.put(entry.getKey(), JsonParser.toJson(value));
+					}
+				}
+				processedMetadatas.add(processed);
+			}
+			metadata = processedMetadatas;
+		}
+
+		// Convenience for adding a single embedding.
 		public AddEmbeddingsRequest(String id, float[] embedding, Map<String, Object> metadata, String document) {
 			this(List.of(id), List.of(embedding), List.of(metadata), List.of(document));
 		}
diff --git a/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java b/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java
index 25478fab66d..dc8ccde275e 100644
--- a/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java
+++ b/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java
@@ -43,6 +43,8 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
 
 /**
  * @author Christian Tzolov
@@ -275,6 +277,22 @@ void shouldFailWhenCollectionDoesNotExist() {
 					"Collection non-existent doesn't exist and won't be created as the initializeSchema is set to false.");
 	}
 
+	@Test
+	public void testAddEmbeddingsRequestMetadataConversion() {
+		Map<String, Object> metadata = Map.of("intVal", 42, "boolVal", true, "strVal", "hello", "doubleVal", 3.14,
+				"listVal", List.of(1, 2, 3), "mapVal", Map.of("a", 1, "b", 2));
+		AddEmbeddingsRequest req = new AddEmbeddingsRequest("id", new float[] { 1f, 2f, 3f }, metadata, "doc");
+		Map<String, Object> processed = req.metadata().get(0);
+
+		assertThat(processed.get("intVal")).isInstanceOf(Integer.class);
+		assertThat(processed.get("boolVal")).isInstanceOf(Boolean.class);
+		assertThat(processed.get("strVal")).isInstanceOf(String.class);
+		assertThat(processed.get("doubleVal")).isInstanceOf(Number.class).isEqualTo(3.14);
+		assertThat(processed.get("listVal")).isInstanceOf(String.class).isEqualTo("[1,2,3]");
+		assertThat(processed.get("mapVal")).isInstanceOf(String.class);
+		assertThatJson(processed.get("mapVal")).isEqualTo("{a:1,b:2}");
+	}
+
 	@SpringBootConfiguration
 	public static class Config {
 

From 5488ce5ec1b072432f97aa828803bc2bb6d736d1 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Wed, 28 May 2025 02:46:14 +0900
Subject: [PATCH 017/165] fix typo in ToolCallback (#3352)

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
---
 .../main/java/org/springframework/ai/tool/ToolCallback.java   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java
index 19bc0a56b71..e22037ca90b 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java
@@ -51,8 +51,8 @@ default ToolMetadata getToolMetadata() {
 	 * Execute tool with the given input and context, and return the result to send back
 	 * to the AI model.
 	 */
-	default String call(String toolInput, @Nullable ToolContext tooContext) {
-		if (tooContext != null && !tooContext.getContext().isEmpty()) {
+	default String call(String toolInput, @Nullable ToolContext toolContext) {
+		if (toolContext != null && !toolContext.getContext().isEmpty()) {
 			throw new UnsupportedOperationException("Tool context is not supported!");
 		}
 		return call(toolInput);

From cfa4128d58238be53eb6fc96df42854865fbcf92 Mon Sep 17 00:00:00 2001
From: Ahmed Maruf <81756975+ohMaruf@users.noreply.github.com>
Date: Tue, 27 May 2025 10:56:37 +0200
Subject: [PATCH 018/165] fix(json): prevent double-serialization of already
 valid JSON strings in JsonParser.toJson

If the input to JsonParser.toJson is a String that is already a valid JSON (e.g. a JSON array or object),
the method now returns the string as-is, instead of re-serializing it into a quoted string.

This avoids issues with deserialization errors, especially when tools expect structured types like List<T>.

test(json): add unit test to ensure toJson skips serialization of valid JSON strings

Signed-off-by: Ahmed Maruf <81756975+ohMaruf@users.noreply.github.com>
---
 .../ai/util/json/JsonParser.java               | 18 +++++++++++++++++-
 .../MethodToolCallbackGenericTypesTest.java    |  6 +++---
 .../ai/util/json/JsonParserTests.java          |  7 +++++++
 3 files changed, 27 insertions(+), 4 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/util/json/JsonParser.java b/spring-ai-model/src/main/java/org/springframework/ai/util/json/JsonParser.java
index 88a31dd435c..b5448803152 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/util/json/JsonParser.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/util/json/JsonParser.java
@@ -100,9 +100,25 @@ public static <T> T fromJson(String json, TypeReference<T> type) {
 	}
 
 	/**
-	 * Converts a Java object to a JSON string.
+	 * Checks if a string is a valid JSON string.
+	 */
+	private static boolean isValidJson(String input) {
+		try {
+			OBJECT_MAPPER.readTree(input);
+			return true;
+		}
+		catch (JsonProcessingException e) {
+			return false;
+		}
+	}
+
+	/**
+	 * Converts a Java object to a JSON string if it's not already a valid JSON string.
 	 */
 	public static String toJson(@Nullable Object object) {
+		if (object instanceof String && isValidJson((String) object)) {
+			return (String) object;
+		}
 		try {
 			return OBJECT_MAPPER.writeValueAsString(object);
 		}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
index 9b1baeac431..b99faa71a3a 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
@@ -63,7 +63,7 @@ void testGenericListType() throws Exception {
 		String result = callback.call(toolInput);
 
 		// Verify the result
-		assertThat(result).isEqualTo("\"3 strings processed: [one, two, three]\"");
+		assertThat(result).isEqualTo("3 strings processed: [one, two, three]");
 	}
 
 	@Test
@@ -97,7 +97,7 @@ void testGenericMapType() throws Exception {
 		String result = callback.call(toolInput);
 
 		// Verify the result
-		assertThat(result).isEqualTo("\"3 entries processed: {one=1, two=2, three=3}\"");
+		assertThat(result).isEqualTo("3 entries processed: {one=1, two=2, three=3}");
 	}
 
 	@Test
@@ -134,7 +134,7 @@ void testNestedGenericType() throws Exception {
 		String result = callback.call(toolInput);
 
 		// Verify the result
-		assertThat(result).isEqualTo("\"2 maps processed: [{a=1, b=2}, {c=3, d=4}]\"");
+		assertThat(result).isEqualTo("2 maps processed: [{a=1, b=2}, {c=3, d=4}]");
 	}
 
 	/**
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonParserTests.java b/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonParserTests.java
index 1d0fbcc3a95..61e073b701b 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonParserTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonParserTests.java
@@ -255,6 +255,13 @@ void fromScientificNotationToLong() {
 		assertThat(value).isEqualTo(1_500_000_000_000L);
 	}
 
+	@Test
+	void doesNotDoubleSerializeValidJsonString() {
+		String input = "[1,2,3]";
+		String result = JsonParser.toJson(input);
+		assertThat(input).isEqualTo(result);
+	}
+
 	record TestRecord(String name, Integer age) {
 	}
 

From 0d91526f4c2d28fb96195cc4e061d2c3e6fb5084 Mon Sep 17 00:00:00 2001
From: Andrea Vacondio <andrea.vacondio@gmail.com>
Date: Wed, 28 May 2025 20:25:12 +0200
Subject: [PATCH 019/165] Update mistralai-embeddings.adoc (#3362)

Default value for the spring.ai.model.embedding should be "mistral" and not "true"

Signed-off-by: Andrea Vacondio <andrea.vacondio@gmail.com>
---
 .../modules/ROOT/pages/api/embeddings/mistralai-embeddings.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/mistralai-embeddings.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/mistralai-embeddings.adoc
index 743d982b521..0a6c86442d0 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/mistralai-embeddings.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/mistralai-embeddings.adoc
@@ -132,7 +132,7 @@ The prefix `spring.ai.mistralai.embedding` is property prefix that configures th
 | Property | Description | Default
 
 | spring.ai.mistralai.embedding.enabled (Removed and no longer valid) | Enable OpenAI embedding model.  | true
-| spring.ai.model.embedding | Enable OpenAI embedding model.  | true
+| spring.ai.model.embedding | Enable OpenAI embedding model.  | mistral
 | spring.ai.mistralai.embedding.base-url   | Optional overrides the spring.ai.mistralai.base-url to provide embedding specific url | -
 | spring.ai.mistralai.embedding.api-key    | Optional overrides the spring.ai.mistralai.api-key to provide embedding specific api-key  | -
 | spring.ai.mistralai.embedding.metadata-mode      | Document content extraction mode.      | EMBED

From a0c76a02735950fce277a171cc9f61fef544a222 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <1085481446@qq.com>
Date: Thu, 29 May 2025 19:56:01 +0800
Subject: [PATCH 020/165] doc: Fix advisor doc for using the right dependencies

  -  The documentation content regarding RAG has been updated. If you need to use the RetrievalAugmentationAdvisor, you should depend on spring-ai-rag.

Signed-off-by: Sun Yuhan <1085481446@qq.com>
---
 .../pages/api/retrieval-augmented-generation.adoc    | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/retrieval-augmented-generation.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/retrieval-augmented-generation.adoc
index 7f1d0463e12..bca102a0cee 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/retrieval-augmented-generation.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/retrieval-augmented-generation.adoc
@@ -13,7 +13,7 @@ NOTE: Learn more about Retrieval Augmented Generation in the xref:concepts.adoc#
 
 Spring AI provides out-of-the-box support for common RAG flows using the `Advisor` API.
 
-To use the `QuestionAnswerAdvisor` or `RetrievalAugmentationAdvisor`, you need to add the `spring-ai-advisors-vector-store` dependency to your project:
+To use the `QuestionAnswerAdvisor` or `VectorStoreChatMemoryAdvisor`, you need to add the `spring-ai-advisors-vector-store` dependency to your project:
 
 [source,xml]
 ----
@@ -130,6 +130,16 @@ Spring AI includes a xref:api/retrieval-augmented-generation.adoc#modules[librar
 The `RetrievalAugmentationAdvisor` is an `Advisor` providing an out-of-the-box implementation for the most common RAG flows,
 based on a modular architecture.
 
+To use the `RetrievalAugmentationAdvisor`, you need to add the `spring-ai-rag` dependency to your project:
+
+[source,xml]
+----
+<dependency>
+   <groupId>org.springframework.ai</groupId>
+   <artifactId>spring-ai-rag</artifactId>
+</dependency>
+----
+
 ==== Sequential RAG Flows
 
 ===== Naive RAG

From 148bf3376b2b0794e193ca6b37c0cf4307a0654c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Henning=20P=C3=B6ttker?=
 <25299532+hpoettker@users.noreply.github.com>
Date: Sun, 25 May 2025 00:41:42 +0200
Subject: [PATCH 021/165] Add MySQL schema for `SPRING_AI_CHAT_MEMORY`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Henning Pöttker <25299532+hpoettker@users.noreply.github.com>
---
 .../pom.xml                                   | 14 ++++++++
 .../memory/repository/jdbc/schema-mysql.sql   |  8 +++++
 .../JdbcChatMemoryRepositoryMariaDbIT.java    | 36 +++++++++++++++++++
 .../jdbc/JdbcChatMemoryRepositoryMysqlIT.java |  5 +--
 pom.xml                                       |  1 +
 5 files changed, 62 insertions(+), 2 deletions(-)
 create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql
 create mode 100644 memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMariaDbIT.java

diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
index 5be3bd8ca64..cf2d0bd6825 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
@@ -73,6 +73,14 @@
 			<optional>true</optional>
 		</dependency>
 
+		<dependency>
+			<groupId>com.mysql</groupId>
+			<artifactId>mysql-connector-j</artifactId>
+			<version>${mysql.version}</version>
+			<scope>test</scope>
+			<optional>true</optional>
+		</dependency>
+
 		<dependency>
 			<groupId>com.microsoft.sqlserver</groupId>
 			<artifactId>mssql-jdbc</artifactId>
@@ -104,6 +112,12 @@
 			<scope>test</scope>
 		</dependency>
 
+		<dependency>
+			<groupId>org.testcontainers</groupId>
+			<artifactId>mysql</artifactId>
+			<scope>test</scope>
+		</dependency>
+
 		<dependency>
 			<groupId>org.testcontainers</groupId>
 			<artifactId>mssqlserver</artifactId>
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql
new file mode 100644
index 00000000000..f8edb0c5827
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/main/resources/org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql
@@ -0,0 +1,8 @@
+CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
+    `conversation_id` VARCHAR(36) NOT NULL,
+    `content` TEXT NOT NULL,
+    `type` ENUM('USER', 'ASSISTANT', 'SYSTEM', 'TOOL') NOT NULL,
+    `timestamp` TIMESTAMP NOT NULL,
+
+    INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`)
+);
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMariaDbIT.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMariaDbIT.java
new file mode 100644
index 00000000000..a9127166e4a
--- /dev/null
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMariaDbIT.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023-2025 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.chat.memory.repository.jdbc;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.jdbc.Sql;
+
+/**
+ * Integration tests for {@link JdbcChatMemoryRepository} with MariaDB.
+ *
+ * @author Jonathan Leijendekker
+ * @author Thomas Vitale
+ * @author Mark Pollack
+ * @author Yanming Zhou
+ */
+@SpringBootTest
+@TestPropertySource(properties = { "spring.datasource.url=jdbc:tc:mariadb:10.3.39:///" })
+@Sql(scripts = "classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mariadb.sql")
+class JdbcChatMemoryRepositoryMariaDbIT extends AbstractJdbcChatMemoryRepositoryIT {
+
+}
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMysqlIT.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMysqlIT.java
index 3ee33e42b6f..e4e7eb756ba 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMysqlIT.java
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/JdbcChatMemoryRepositoryMysqlIT.java
@@ -27,10 +27,11 @@
  * @author Thomas Vitale
  * @author Mark Pollack
  * @author Yanming Zhou
+ * @author Henning Pöttker
  */
 @SpringBootTest
-@TestPropertySource(properties = { "spring.datasource.url=jdbc:tc:mariadb:10.3.39:///" })
-@Sql(scripts = "classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mariadb.sql")
+@TestPropertySource(properties = { "spring.datasource.url=jdbc:tc:mysql:8.0.42:///" })
+@Sql(scripts = "classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql")
 class JdbcChatMemoryRepositoryMysqlIT extends AbstractJdbcChatMemoryRepositoryIT {
 
 }
diff --git a/pom.xml b/pom.xml
index a5f3b507db1..e49c082fc64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -302,6 +302,7 @@
 		<opensearch-client.version>2.23.0</opensearch-client.version>
 		<postgresql.version>42.7.5</postgresql.version>
 		<mariadb.version>3.5.3</mariadb.version>
+		<mysql.version>9.2.0</mysql.version>
 		<commonmark.version>0.22.0</commonmark.version>
 
 		<couchbase.version>3.8.0</couchbase.version>

From a33045871f3564c5fde7bd7248df45319053d191 Mon Sep 17 00:00:00 2001
From: Jan-Eric Harnack <j.harnack+gh@posteo.de>
Date: Sun, 18 May 2025 23:43:38 +0200
Subject: [PATCH 022/165] feat(openai): add support for pdf files as media

Signed-off-by: Jan-Eric Harnack <janericharnack@googlemail.com>
---
 .../ai/openai/OpenAiChatModel.java            |  4 ++
 .../ai/openai/api/OpenAiApi.java              | 29 ++++++++++--
 .../openai/chat/MessageTypeContentTests.java  | 45 ++++++++++++++-----
 3 files changed, 63 insertions(+), 15 deletions(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
index 74c776e6dd0..7da34176c15 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
@@ -635,6 +635,10 @@ private MediaContent mapToMediaContent(Media media) {
 			return new MediaContent(
 					new MediaContent.InputAudio(fromAudioData(media.getData()), MediaContent.InputAudio.Format.WAV));
 		}
+		if (MimeTypeUtils.parseMimeType("application/pdf").equals(mimeType)) {
+			return new MediaContent(new MediaContent.InputFile(media.getName(),
+					this.fromMediaData(media.getMimeType(), media.getData())));
+		}
 		else {
 			return new MediaContent(
 					new MediaContent.ImageUrl(this.fromMediaData(media.getMimeType(), media.getData())));
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index 2b11927f908..d72534b4654 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -1367,14 +1367,15 @@ public record MediaContent(// @formatter:off
 			@JsonProperty("type") String type,
 			@JsonProperty("text") String text,
 			@JsonProperty("image_url") ImageUrl imageUrl,
-			@JsonProperty("input_audio") InputAudio inputAudio) { // @formatter:on
+			@JsonProperty("input_audio") InputAudio inputAudio,
+			@JsonProperty("file") InputFile inputFile) { // @formatter:on
 
 			/**
 			 * Shortcut constructor for a text content.
 			 * @param text The text content of the message.
 			 */
 			public MediaContent(String text) {
-				this("text", text, null, null);
+				this("text", text, null, null, null);
 			}
 
 			/**
@@ -1382,7 +1383,7 @@ public MediaContent(String text) {
 			 * @param imageUrl The image content of the message.
 			 */
 			public MediaContent(ImageUrl imageUrl) {
-				this("image_url", null, imageUrl, null);
+				this("image_url", null, imageUrl, null, null);
 			}
 
 			/**
@@ -1390,7 +1391,15 @@ public MediaContent(ImageUrl imageUrl) {
 			 * @param inputAudio The audio content of the message.
 			 */
 			public MediaContent(InputAudio inputAudio) {
-				this("input_audio", null, null, inputAudio);
+				this("input_audio", null, null, inputAudio, null);
+			}
+
+			/**
+			 * Shortcut constructor for a file content
+			 * @param inputFile The file content of the message.
+			 */
+			public MediaContent(InputFile inputFile) {
+				this("file", null, null, null, inputFile);
 			}
 
 			/**
@@ -1428,6 +1437,18 @@ public ImageUrl(String url) {
 
 			}
 
+			/**
+			 * Constructor for base64-encoded file
+			 *
+			 * @param filename name of the file
+			 * @param fileData file data with format
+			 * "data:{mimetype};base64,{base64-encoded-image-data}".
+			 */
+			public record InputFile(@JsonProperty("filename") String filename,
+					@JsonProperty("file_data") String fileData) {
+
+			}
+
 		}
 
 		/**
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java
index f24b71f62d1..f1a91dc6060 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java
@@ -18,6 +18,7 @@
 
 import java.net.MalformedURLException;
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
 
@@ -29,6 +30,8 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.util.MimeType;
 import reactor.core.publisher.Flux;
 
 import org.springframework.ai.chat.messages.SystemMessage;
@@ -126,11 +129,8 @@ public void userMessageWithMediaType() throws MalformedURLException {
 		given(this.openAiApi.chatCompletionEntity(this.pomptCaptor.capture(), this.headersCaptor.capture()))
 			.willReturn(Mockito.mock(ResponseEntity.class));
 
-		URI mediaUri = URI.create("http://test");
-		this.chatModel.call(new Prompt(List.of(UserMessage.builder()
-			.text("test message")
-			.media(List.of(Media.builder().mimeType(MimeTypeUtils.IMAGE_JPEG).data(mediaUri).build()))
-			.build())));
+		this.chatModel
+			.call(new Prompt(List.of(UserMessage.builder().text("test message").media(this.buildMediaList()).build())));
 
 		validateComplexContent(this.pomptCaptor.getValue());
 	}
@@ -141,11 +141,10 @@ public void streamUserMessageWithMediaType() throws MalformedURLException {
 		given(this.openAiApi.chatCompletionStream(this.pomptCaptor.capture(), this.headersCaptor.capture()))
 			.willReturn(this.fluxResponse);
 
-		URI mediaUrl = URI.create("http://test");
-		this.chatModel.stream(new Prompt(List.of(UserMessage.builder()
-			.text("test message")
-			.media(List.of(Media.builder().mimeType(MimeTypeUtils.IMAGE_JPEG).data(mediaUrl).build()))
-			.build()))).subscribe();
+		this.chatModel
+			.stream(new Prompt(
+					List.of(UserMessage.builder().text("test message").media(this.buildMediaList()).build())))
+			.subscribe();
 
 		validateComplexContent(this.pomptCaptor.getValue());
 	}
@@ -161,16 +160,40 @@ private void validateComplexContent(ChatCompletionRequest chatCompletionRequest)
 		@SuppressWarnings({ "unused", "unchecked" })
 		List<Map<String, Object>> mediaContents = (List<Map<String, Object>>) userMessage.rawContent();
 
-		assertThat(mediaContents).hasSize(2);
+		assertThat(mediaContents).hasSize(3);
 
+		// Assert text content
 		Map<String, Object> textContent = mediaContents.get(0);
 		assertThat(textContent.get("type")).isEqualTo("text");
 		assertThat(textContent.get("text")).isEqualTo("test message");
 
+		// Assert image content
 		Map<String, Object> imageContent = mediaContents.get(1);
 
 		assertThat(imageContent.get("type")).isEqualTo("image_url");
 		assertThat(imageContent).containsKey("image_url");
+
+		// Assert file content
+		Map<String, Object> fileContent = mediaContents.get(2);
+		assertThat(fileContent.get("type")).isEqualTo("file");
+		assertThat(fileContent).containsKey("file");
+		assertThat(fileContent.get("file")).isInstanceOf(Map.class);
+
+		Map<String, Object> fileMap = (Map<String, Object>) fileContent.get("file");
+		assertThat(fileMap.get("file_data")).isEqualTo("data:application/pdf;base64,JVBERi0xLjc=");
+	}
+
+	private List<Media> buildMediaList() {
+		URI imageUri = URI.create("http://test");
+		Media imageMedia = Media.builder().mimeType(MimeTypeUtils.IMAGE_JPEG).data(imageUri).build();
+
+		byte[] pdfData = "%PDF-1.7".getBytes(StandardCharsets.UTF_8);
+		Media pdfMedia = Media.builder()
+			.mimeType(MimeType.valueOf("application/pdf"))
+			.data(new ByteArrayResource(pdfData))
+			.build();
+
+		return List.of(imageMedia, pdfMedia);
 	}
 
 }

From 070b5fa7b551d2f11fa286af8f25d824a17bd6b2 Mon Sep 17 00:00:00 2001
From: pavan kumar punna <punnapavankumar9@gmail.com>
Date: Thu, 29 May 2025 22:43:55 +0530
Subject: [PATCH 023/165] docs: fix typo 'synch' to 'sync' in CONTRIBUTING.adoc
 (#3378)

Fixes: #3378

Signed-off-by: punnapavankumar9 <punnapavankumar9@gmail.com>
---
 CONTRIBUTING.adoc | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc
index 9ea709510c3..a8f986c7729 100644
--- a/CONTRIBUTING.adoc
+++ b/CONTRIBUTING.adoc
@@ -77,17 +77,17 @@ project to which you’re trying to contribute but that you don’t maintain."_
 
 == Keeping your Local Code in Sync
 * As mentioned above, you should always work on topic branches (since 'main' is a moving target). However, you do want
-to always keep your own 'origin' main branch in synch with the 'upstream' main.
+to always keep your own 'origin' main branch in sync with the 'upstream' main.
 * Within your local working directory, you can sync up all remotes' branches with: `git fetch --all`
 * While on your own local main branch: `git pull upstream main` (which is the equivalent of fetching upstream/main
 and merging that into the branch you are in currently)
-* Now that you're in synch, switch to the topic branch where you plan to work, e.g.: `git checkout -b GH-123`
+* Now that you're in sync, switch to the topic branch where you plan to work, e.g.: `git checkout -b GH-123`
 * When you get to a stopping point: `git commit`
-* If changes have occurred on the upstream/main while you were working you can synch again:
+* If changes have occurred on the upstream/main while you were working you can sync again:
 - Switch back to main: `git checkout main`
 - Then: `git pull upstream main`
 - Switch back to the topic branch: `git checkout GH-123` (no -b needed since the branch already exists)
-- Rebase the topic branch to minimize the distance between it and your recently synched main branch: `git rebase main`
+- Rebase the topic branch to minimize the distance between it and your recently synced main branch: `git rebase main`
 (Again, for more detail see https://git-scm.com/book/en/Git-Branching-Rebasing[the Pro Git section on rebasing]).
 * **Note** You cannot rebase if you have already pushed your branch to your remote because you'd be rewriting history
 (see **'The Perils of Rebasing'** in the article).

From cd364e39dd58a33065aad99d70874c1227499e13 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov <filip.hrisafov@gmail.com>
Date: Fri, 30 May 2025 10:21:56 +0200
Subject: [PATCH 024/165] Fix CustomerTools example to use existing methods
 (#3390)

Signed-off-by: Filip Hrisafov <filip.hrisafov@gmail.com>
---
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index 286fb7f4c69..fe39d655740 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -939,7 +939,7 @@ class CustomerTools {
 
     @Tool(description = "Retrieve customer information")
     Customer getCustomerInfo(Long id, ToolContext toolContext) {
-        return customerRepository.findById(id, toolContext.get("tenantId"));
+        return customerRepository.findById(id, toolContext.getContext().get("tenantId"));
     }
 
 }

From be79b892a56125edf3bac543e597100fe43cb8f5 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov <filip.hrisafov@gmail.com>
Date: Wed, 28 May 2025 20:01:51 +0200
Subject: [PATCH 025/165] Resolve OpenAI ApiKey for every request

Signed-off-by: Filip Hrisafov <filip.hrisafov@gmail.com>
---
 .../ai/openai/api/OpenAiApi.java              |  15 +-
 .../ai/openai/api/OpenAiAudioApi.java         |  21 +-
 .../ai/openai/api/OpenAiImageApi.java         |  13 +-
 .../ai/openai/api/OpenAiModerationApi.java    |  23 +-
 .../ai/openai/api/OpenAiApiBuilderTests.java  | 139 ++++++++++++
 .../audio/api/OpenAiAudioApiBuilderTests.java | 209 ++++++++++++++++++
 .../image/api/OpenAiImageApiBuilderTests.java | 176 +++++++++++++++
 .../api/OpenAiModerationApiBuilderTests.java  | 176 +++++++++++++++
 8 files changed, 752 insertions(+), 20 deletions(-)
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index d72534b4654..e0837b10cb5 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -62,6 +62,7 @@
  * @author Thomas Vitale
  * @author David Frizelle
  * @author Alexandros Pappas
+ * @author Filip Hrisafov
  */
 public class OpenAiApi {
 
@@ -128,10 +129,6 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> he
 
 		// @formatter:off
 		Consumer<HttpHeaders> finalHeaders = h -> {
-			if (!(apiKey instanceof NoopApiKey)) {
-				h.setBearerAuth(apiKey.getValue());
-			}
-
 			h.setContentType(MediaType.APPLICATION_JSON);
 			h.addAll(headers);
 		};
@@ -139,11 +136,21 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> he
 			.baseUrl(baseUrl)
 			.defaultHeaders(finalHeaders)
 			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
 			.build();
 
 		this.webClient = webClientBuilder.clone()
 			.baseUrl(baseUrl)
 			.defaultHeaders(finalHeaders)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
 			.build(); // @formatter:on
 	}
 
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
index 95604f30075..1d78ad1c3b0 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
@@ -49,6 +49,7 @@
  * @author Christian Tzolov
  * @author Ilayaperumal Gopinathan
  * @author Jonghoon Park
+ * @author Filip Hrisafov
  * @since 0.8.1
  */
 public class OpenAiAudioApi {
@@ -71,20 +72,30 @@ public OpenAiAudioApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, Strin
 			ResponseErrorHandler responseErrorHandler) {
 
 		Consumer<HttpHeaders> authHeaders = h -> {
-			if (!(apiKey instanceof NoopApiKey)) {
-				h.setBearerAuth(apiKey.getValue());
-			}
 			h.addAll(headers);
-			// h.setContentType(MediaType.APPLICATION_JSON);
 		};
 
+		// @formatter:off
 		this.restClient = restClientBuilder.clone()
 			.baseUrl(baseUrl)
 			.defaultHeaders(authHeaders)
 			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
 			.build();
 
-		this.webClient = webClientBuilder.clone().baseUrl(baseUrl).defaultHeaders(authHeaders).build();
+		this.webClient = webClientBuilder.clone()
+			.baseUrl(baseUrl)
+			.defaultHeaders(authHeaders)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
+			.build(); // @formatter:on
 	}
 
 	public static Builder builder() {
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
index bd32d42d9f6..55e34137bd1 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
 import org.springframework.ai.retry.RetryUtils;
+import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.Assert;
@@ -40,6 +41,7 @@
  *
  * @see <a href= "https://platform.openai.com/docs/api-reference/images">Images</a>
  * @author lambochen
+ * @author Filip Hrisafov
  */
 public class OpenAiImageApi {
 
@@ -62,15 +64,18 @@ public OpenAiImageApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, Strin
 			RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
 
 		// @formatter:off
-		this.restClient = restClientBuilder.baseUrl(baseUrl)
+		this.restClient = restClientBuilder.clone()
+			.baseUrl(baseUrl)
 			.defaultHeaders(h -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					h.setBearerAuth(apiKey.getValue());
-				}
 				h.setContentType(MediaType.APPLICATION_JSON);
 				h.addAll(headers);
 			})
 			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
 			.build();
 		// @formatter:on
 
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
index b9a6578b636..5a5600dea02 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
 import org.springframework.ai.retry.RetryUtils;
+import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.Assert;
@@ -40,6 +41,7 @@
  *
  * @author Ahmed Yousri
  * @author Ilayaperumal Gopinathan
+ * @author Filip Hrisafov
  * @see <a href=
  * "https://platform.openai.com/docs/api-reference/moderations">https://platform.openai.com/docs/api-reference/moderations</a>
  */
@@ -64,13 +66,20 @@ public OpenAiModerationApi(String baseUrl, ApiKey apiKey, MultiValueMap<String,
 
 		this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
 
-		this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(h -> {
-			if (!(apiKey instanceof NoopApiKey)) {
-				h.setBearerAuth(apiKey.getValue());
-			}
-			h.setContentType(MediaType.APPLICATION_JSON);
-			h.addAll(headers);
-		}).defaultStatusHandler(responseErrorHandler).build();
+		// @formatter:off
+		this.restClient = restClientBuilder.clone()
+			.baseUrl(baseUrl)
+			.defaultHeaders(h -> {
+				h.setContentType(MediaType.APPLICATION_JSON);
+				h.addAll(headers);
+			})
+			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
+			.build(); // @formatter:on
 	}
 
 	public ResponseEntity<OpenAiModerationResponse> createModeration(OpenAiModerationRequest openAiModerationRequest) {
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
index 30f28f610c9..e9d188052fa 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
@@ -16,10 +16,27 @@
 
 package org.springframework.ai.openai.api;
 
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.ai.model.ApiKey;
 import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.ResponseErrorHandler;
@@ -142,4 +159,126 @@ void testInvalidResponseErrorHandler() {
 			.hasMessageContaining("responseErrorHandler cannot be null");
 	}
 
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiApi api = OpenAiApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "chatcmpl-12345",
+							"object": "chat.completion",
+							"created": 1677858242,
+							"model": "gpt-3.5-turbo",
+							"choices": [
+								{
+						    		"index": 0,
+									"message": {
+									"role": "assistant",
+									"content": "Hello world"
+									},
+									"finish_reason": "stop"
+								}
+							],
+							"usage": {
+								"prompt_tokens": 10,
+								"completion_tokens": 5,
+								"total_tokens": 15
+							}
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
+					OpenAiApi.ChatCompletionMessage.Role.USER);
+			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
+					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, false);
+			ResponseEntity<OpenAiApi.ChatCompletion> response = api.chatCompletionEntity(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.chatCompletionEntity(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+		@Test
+		void dynamicApiKeyWebClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiApi api = OpenAiApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "chatcmpl-12345",
+							"object": "chat.completion",
+							"created": 1677858242,
+							"model": "gpt-3.5-turbo",
+							"choices": [
+								{
+						    		"index": 0,
+									"message": {
+									"role": "assistant",
+									"content": "Hello world"
+									},
+									"finish_reason": "stop"
+								}
+							],
+							"usage": {
+								"prompt_tokens": 10,
+								"completion_tokens": 5,
+								"total_tokens": 15
+							}
+						}
+						""".replace("\n", ""));
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
+					OpenAiApi.ChatCompletionMessage.Role.USER);
+			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
+					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, true);
+			List<OpenAiApi.ChatCompletionChunk> response = api.chatCompletionStream(request).collectList().block();
+			assertThat(response).hasSize(1);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.chatCompletionStream(request).collectList().block();
+			assertThat(response).hasSize(1);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
 }
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
new file mode 100644
index 00000000000..1b11fa6807a
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2023-2025 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.openai.audio.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiAudioApi;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * @author Filip Hrisafov
+ */
+class OpenAiAudioApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.openai.com";
+
+	@Test
+	void testMinimalBuilder() {
+		OpenAiAudioApi api = OpenAiAudioApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		headers.add("Custom-Header", "test-value");
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		WebClient.Builder webClientBuilder = WebClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		OpenAiAudioApi api = OpenAiAudioApi.builder()
+			.baseUrl(TEST_BASE_URL)
+			.apiKey(TEST_API_KEY)
+			.headers(headers)
+			.restClientBuilder(restClientBuilder)
+			.webClientBuilder(webClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidHeaders() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().headers(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("headers cannot be null");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidWebClientBuilder() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().webClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("webClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiAudioApi api = OpenAiAudioApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
+				.setBody("Audio bytes as string");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiAudioApi.SpeechRequest request = OpenAiAudioApi.SpeechRequest.builder()
+				.model(OpenAiAudioApi.TtsModel.TTS_1.value)
+				.input("Test input")
+				.build();
+			ResponseEntity<byte[]> response = api.createSpeech(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.createSpeech(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+		@Test
+		void dynamicApiKeyWebClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiAudioApi api = OpenAiAudioApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
+				.setBody("Audio bytes as string");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiAudioApi.SpeechRequest request = OpenAiAudioApi.SpeechRequest.builder()
+				.model(OpenAiAudioApi.TtsModel.TTS_1.value)
+				.input("Test input")
+				.build();
+			List<ResponseEntity<byte[]>> response = api.stream(request).collectList().block();
+			assertThat(response).hasSize(1);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.stream(request).collectList().block();
+			assertThat(response).hasSize(1);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
+}
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
new file mode 100644
index 00000000000..5564a087a71
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023-2025 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.openai.image.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiImageApi;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * @author Filip Hrisafov
+ */
+class OpenAiImageApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.openai.com";
+
+	@Test
+	void testMinimalBuilder() {
+		OpenAiImageApi api = OpenAiImageApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		headers.add("Custom-Header", "test-value");
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		OpenAiImageApi api = OpenAiImageApi.builder()
+			.baseUrl(TEST_BASE_URL)
+			.apiKey(TEST_API_KEY)
+			.headers(headers)
+			.restClientBuilder(restClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> OpenAiImageApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidHeaders() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().headers(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("headers cannot be null");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiImageApi api = OpenAiImageApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"created": 1589478378,
+							"data": [
+								{
+									"url": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Mini_Golden_Doodle.jpg"
+								}
+							]
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiImageApi.OpenAiImageRequest request = new OpenAiImageApi.OpenAiImageRequest("Test",
+					OpenAiImageApi.ImageModel.DALL_E_3.getValue());
+			ResponseEntity<OpenAiImageApi.OpenAiImageResponse> response = api.createImage(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.createImage(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
+}
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java
new file mode 100644
index 00000000000..1c757789b27
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023-2025 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.openai.moderation.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiModerationApi;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * @author Filip Hrisafov
+ */
+class OpenAiModerationApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.openai.com";
+
+	@Test
+	void testMinimalBuilder() {
+		OpenAiModerationApi api = OpenAiModerationApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		headers.add("Custom-Header", "test-value");
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		OpenAiModerationApi api = OpenAiModerationApi.builder()
+			.baseUrl(TEST_BASE_URL)
+			.apiKey(TEST_API_KEY)
+			.headers(headers)
+			.restClientBuilder(restClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidHeaders() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().headers(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("headers cannot be null");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiModerationApi api = OpenAiModerationApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"created": 1589478378,
+							"data": [
+								{
+									"url": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Mini_Golden_Doodle.jpg"
+								}
+							]
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiModerationApi.OpenAiModerationRequest request = new OpenAiModerationApi.OpenAiModerationRequest(
+					"Test");
+			ResponseEntity<OpenAiModerationApi.OpenAiModerationResponse> response = api.createModeration(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.createModeration(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
+}

From b34dcbf0c7a1bc93bd237cc91bf568bbc3dc8a06 Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <6052803+apappascs@users.noreply.github.com>
Date: Fri, 30 May 2025 12:32:26 +0200
Subject: [PATCH 026/165] feat(anthropic): add support for claude-opus-4 and
 claude-sonnet-4 (#3351)

* feat(anthropic): add support for claude-opus-4-0 and claude-sonnet-4-0 models

- Introduce `CLAUDE_OPUS_4` and `CLAUDE_SONNET_4` to `ChatModel` enum
- Update documentation to include new model options

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>

* add model aliases url to anthropic documentation

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>

---------

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
---
 .../springframework/ai/anthropic/api/AnthropicApi.java | 10 ++++++++++
 .../modules/ROOT/pages/api/chat/anthropic-chat.adoc    |  8 +++++---
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
index adcb897ad89..f9b63e936ca 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
@@ -241,6 +241,16 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
 	public enum ChatModel implements ChatModelDescription {
 
 		// @formatter:off
+		/**
+		 * The claude-opus-4-0 model.
+		 */
+		CLAUDE_OPUS_4("claude-opus-4-0"),
+
+		/**
+		 * The claude-sonnet-4-0 model.
+		 */
+		CLAUDE_SONNET_4("claude-sonnet-4-0"),
+
 		/**
 		 * The claude-3-7-sonnet-latest model.
 		 */
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
index cb4a77948f6..db982d4dc95 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
@@ -124,7 +124,7 @@ The prefix `spring.ai.anthropic` is used as the property prefix that lets you co
 | spring.ai.anthropic.completions-path   | The path to append to the base URL. |  `/v1/chat/completions`
 | spring.ai.anthropic.version   | Anthropic API version |  2023-06-01
 | spring.ai.anthropic.api-key    | The API Key           |  -
-| spring.ai.anthropic.beta-version | Enables new/experimental features. If set to `max-tokens-3-5-sonnet-2024-07-15` 
+| spring.ai.anthropic.beta-version | Enables new/experimental features. If set to `max-tokens-3-5-sonnet-2024-07-15`
 the output tokens limit is increased from `4096` to `8192` tokens (for claude-3-5-sonnet only). | `tools-2024-04-04`
 |====
 
@@ -149,7 +149,7 @@ The prefix `spring.ai.anthropic.chat` is the property prefix that lets you confi
 
 | spring.ai.anthropic.chat.enabled (Removed and no longer valid) | Enable Anthropic chat model.  | true
 | spring.ai.model.chat | Enable Anthropic chat model.  | anthropic
-| spring.ai.anthropic.chat.options.model | This is the Anthropic Chat model to use. Supports: `claude-3-7-sonnet-latest`, `claude-3-5-sonnet-latest`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229`, `claude-3-haiku-20240307` | `claude-3-7-sonnet-latest`
+| spring.ai.anthropic.chat.options.model | This is the Anthropic Chat model to use. Supports: `claude-opus-4-0`, `claude-sonnet-4-0`, `claude-3-7-sonnet-latest`, `claude-3-5-sonnet-latest`, `claude-3-opus-20240229`, `claude-3-sonnet-20240229`, `claude-3-haiku-20240307` | `claude-3-7-sonnet-latest`
 | spring.ai.anthropic.chat.options.temperature | The sampling temperature to use that controls the apparent creativity of generated completions. Higher values will make output more random while lower values will make results more focused and deterministic. It is not recommended to modify temperature and top_p for the same completions request as the interaction of these two settings is difficult to predict. | 0.8
 | spring.ai.anthropic.chat.options.max-tokens | The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length. | 500
 | spring.ai.anthropic.chat.options.stop-sequence | Custom text sequences that will cause the model to stop generating. Our models will normally stop when they have naturally completed their turn, which will result in a response stop_reason of "end_turn". If you want the model to stop generating when it encounters custom strings of text, you can use the stop_sequences parameter. If the model encounters one of the custom sequences, the response stop_reason value will be "stop_sequence" and the response stop_sequence value will contain the matched stop sequence. | -
@@ -164,6 +164,8 @@ The prefix `spring.ai.anthropic.chat` is the property prefix that lets you confi
 | spring.ai.anthropic.chat.options.http-headers | Optional HTTP headers to be added to the chat completion request. | -
 |====
 
+TIP: For the latest list of model aliases and their descriptions, see the link:https://docs.anthropic.com/en/docs/about-claude/models/overview#model-aliases[official Anthropic model aliases documentation].
+
 TIP: All properties prefixed with `spring.ai.anthropic.chat.options` can be overridden at runtime by adding a request specific <<chat-options>> to the `Prompt` call.
 
 == Runtime Options [[chat-options]]
@@ -262,7 +264,7 @@ spring.ai.anthropic.chat.options.temperature=0.7
 spring.ai.anthropic.chat.options.max-tokens=450
 ----
 
-TIP: replace the `api-key` with your Anthropic credentials.
+TIP: Replace the `api-key` with your Anthropic credentials.
 
 This will create a `AnthropicChatModel` implementation that you can inject into your class.
 Here is an example of a simple `@Controller` class that uses the chat model for text generations.

From 1eecd17529fbfc3c25d95a4324adb6fe2c302a65 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <1085481446@qq.com>
Date: Thu, 29 May 2025 17:37:02 +0800
Subject: [PATCH 027/165] fix(ollama): null check on chat model response'
 duration metadata

 - Fixed an issue where the `from` method of `org.springframework.ai.ollama.OllamaChatModel` could not work correctly when a tool call occurred while using Ollama.

Signed-off-by: Sun Yuhan <1085481446@qq.com>
---
 .../ai/ollama/OllamaChatModel.java            | 22 ++++++++-------
 .../ai/ollama/OllamaChatModelTests.java       | 27 +++++++++++++++++--
 2 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java
index a75a274a797..8d22df6ddcc 100644
--- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java
+++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java
@@ -84,6 +84,7 @@
  * @author Jihoon Kim
  * @author Alexandros Pappas
  * @author Ilayaperumal Gopinathan
+ * @author Sun Yuhan
  * @since 1.0.0
  */
 public class OllamaChatModel implements ChatModel {
@@ -170,18 +171,21 @@ static ChatResponseMetadata from(OllamaApi.ChatResponse response, ChatResponse p
 		Duration totalDuration = response.getTotalDuration();
 
 		if (previousChatResponse != null && previousChatResponse.getMetadata() != null) {
-			if (previousChatResponse.getMetadata().get(METADATA_EVAL_DURATION) != null) {
-				evalDuration = evalDuration.plus(previousChatResponse.getMetadata().get(METADATA_EVAL_DURATION));
+			Object metadataEvalDuration = previousChatResponse.getMetadata().get(METADATA_EVAL_DURATION);
+			if (metadataEvalDuration != null && evalDuration != null) {
+				evalDuration = evalDuration.plus((Duration) metadataEvalDuration);
 			}
-			if (previousChatResponse.getMetadata().get(METADATA_PROMPT_EVAL_DURATION) != null) {
-				promptEvalDuration = promptEvalDuration
-					.plus(previousChatResponse.getMetadata().get(METADATA_PROMPT_EVAL_DURATION));
+			Object metadataPromptEvalDuration = previousChatResponse.getMetadata().get(METADATA_PROMPT_EVAL_DURATION);
+			if (metadataPromptEvalDuration != null && promptEvalDuration != null) {
+				promptEvalDuration = promptEvalDuration.plus((Duration) metadataPromptEvalDuration);
 			}
-			if (previousChatResponse.getMetadata().get(METADATA_LOAD_DURATION) != null) {
-				loadDuration = loadDuration.plus(previousChatResponse.getMetadata().get(METADATA_LOAD_DURATION));
+			Object metadataLoadDuration = previousChatResponse.getMetadata().get(METADATA_LOAD_DURATION);
+			if (metadataLoadDuration != null && loadDuration != null) {
+				loadDuration = loadDuration.plus((Duration) metadataLoadDuration);
 			}
-			if (previousChatResponse.getMetadata().get(METADATA_TOTAL_DURATION) != null) {
-				totalDuration = totalDuration.plus(previousChatResponse.getMetadata().get(METADATA_TOTAL_DURATION));
+			Object metadataTotalDuration = previousChatResponse.getMetadata().get(METADATA_TOTAL_DURATION);
+			if (metadataTotalDuration != null && totalDuration != null) {
+				totalDuration = totalDuration.plus((Duration) metadataTotalDuration);
 			}
 			if (previousChatResponse.getMetadata().getUsage() != null) {
 				promptTokens += previousChatResponse.getMetadata().getUsage().getPromptTokens();
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java
index bbeb101fc9a..fdb3c43cb68 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java
@@ -37,8 +37,7 @@
 import org.springframework.ai.ollama.management.ModelManagementOptions;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.*;
 
 /**
  * @author Jihoon Kim
@@ -146,4 +145,28 @@ void buildChatResponseMetadataAggregationWithNonEmptyMetadata() {
 		assertEquals(promptEvalCount + 66, (Integer) metadata.get("prompt-eval-count"));
 	}
 
+	@Test
+	void buildChatResponseMetadataAggregationWithNonEmptyMetadataButEmptyEval() {
+
+		OllamaApi.ChatResponse response = new OllamaApi.ChatResponse("model", Instant.now(), null, null, null, null,
+				null, null, null, null, null);
+
+		ChatResponse previousChatResponse = ChatResponse.builder()
+			.generations(List.of())
+			.metadata(ChatResponseMetadata.builder()
+				.usage(new DefaultUsage(66, 99))
+				.keyValue("eval-duration", Duration.ofSeconds(2))
+				.keyValue("prompt-eval-duration", Duration.ofSeconds(2))
+				.build())
+			.build();
+
+		ChatResponseMetadata metadata = OllamaChatModel.from(response, previousChatResponse);
+
+		assertNull(metadata.get("eval-duration"));
+		assertNull(metadata.get("prompt-eval-duration"));
+		assertEquals(Integer.valueOf(99), metadata.get("eval-count"));
+		assertEquals(Integer.valueOf(66), metadata.get("prompt-eval-count"));
+
+	}
+
 }

From 8a2706d6246e5b7a868bc2979c6182fa946e14c8 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Fri, 30 May 2025 13:01:23 +0100
Subject: [PATCH 028/165] Revert "Resolve OpenAI ApiKey for every request"

This reverts commit be79b892a56125edf3bac543e597100fe43cb8f5.
---
 .../ai/openai/api/OpenAiApi.java              |  15 +-
 .../ai/openai/api/OpenAiAudioApi.java         |  21 +-
 .../ai/openai/api/OpenAiImageApi.java         |  13 +-
 .../ai/openai/api/OpenAiModerationApi.java    |  23 +-
 .../ai/openai/api/OpenAiApiBuilderTests.java  | 139 ------------
 .../audio/api/OpenAiAudioApiBuilderTests.java | 209 ------------------
 .../image/api/OpenAiImageApiBuilderTests.java | 176 ---------------
 .../api/OpenAiModerationApiBuilderTests.java  | 176 ---------------
 8 files changed, 20 insertions(+), 752 deletions(-)
 delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
 delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
 delete mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index e0837b10cb5..d72534b4654 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -62,7 +62,6 @@
  * @author Thomas Vitale
  * @author David Frizelle
  * @author Alexandros Pappas
- * @author Filip Hrisafov
  */
 public class OpenAiApi {
 
@@ -129,6 +128,10 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> he
 
 		// @formatter:off
 		Consumer<HttpHeaders> finalHeaders = h -> {
+			if (!(apiKey instanceof NoopApiKey)) {
+				h.setBearerAuth(apiKey.getValue());
+			}
+
 			h.setContentType(MediaType.APPLICATION_JSON);
 			h.addAll(headers);
 		};
@@ -136,21 +139,11 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> he
 			.baseUrl(baseUrl)
 			.defaultHeaders(finalHeaders)
 			.defaultStatusHandler(responseErrorHandler)
-			.defaultRequest(requestHeadersSpec -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
-				}
-			})
 			.build();
 
 		this.webClient = webClientBuilder.clone()
 			.baseUrl(baseUrl)
 			.defaultHeaders(finalHeaders)
-			.defaultRequest(requestHeadersSpec -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
-				}
-			})
 			.build(); // @formatter:on
 	}
 
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
index 1d78ad1c3b0..95604f30075 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
@@ -49,7 +49,6 @@
  * @author Christian Tzolov
  * @author Ilayaperumal Gopinathan
  * @author Jonghoon Park
- * @author Filip Hrisafov
  * @since 0.8.1
  */
 public class OpenAiAudioApi {
@@ -72,30 +71,20 @@ public OpenAiAudioApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, Strin
 			ResponseErrorHandler responseErrorHandler) {
 
 		Consumer<HttpHeaders> authHeaders = h -> {
+			if (!(apiKey instanceof NoopApiKey)) {
+				h.setBearerAuth(apiKey.getValue());
+			}
 			h.addAll(headers);
+			// h.setContentType(MediaType.APPLICATION_JSON);
 		};
 
-		// @formatter:off
 		this.restClient = restClientBuilder.clone()
 			.baseUrl(baseUrl)
 			.defaultHeaders(authHeaders)
 			.defaultStatusHandler(responseErrorHandler)
-			.defaultRequest(requestHeadersSpec -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
-				}
-			})
 			.build();
 
-		this.webClient = webClientBuilder.clone()
-			.baseUrl(baseUrl)
-			.defaultHeaders(authHeaders)
-			.defaultRequest(requestHeadersSpec -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
-				}
-			})
-			.build(); // @formatter:on
+		this.webClient = webClientBuilder.clone().baseUrl(baseUrl).defaultHeaders(authHeaders).build();
 	}
 
 	public static Builder builder() {
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
index 55e34137bd1..bd32d42d9f6 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
@@ -27,7 +27,6 @@
 import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
 import org.springframework.ai.retry.RetryUtils;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.Assert;
@@ -41,7 +40,6 @@
  *
  * @see <a href= "https://platform.openai.com/docs/api-reference/images">Images</a>
  * @author lambochen
- * @author Filip Hrisafov
  */
 public class OpenAiImageApi {
 
@@ -64,18 +62,15 @@ public OpenAiImageApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, Strin
 			RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
 
 		// @formatter:off
-		this.restClient = restClientBuilder.clone()
-			.baseUrl(baseUrl)
+		this.restClient = restClientBuilder.baseUrl(baseUrl)
 			.defaultHeaders(h -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					h.setBearerAuth(apiKey.getValue());
+				}
 				h.setContentType(MediaType.APPLICATION_JSON);
 				h.addAll(headers);
 			})
 			.defaultStatusHandler(responseErrorHandler)
-			.defaultRequest(requestHeadersSpec -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
-				}
-			})
 			.build();
 		// @formatter:on
 
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
index 5a5600dea02..b9a6578b636 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
@@ -27,7 +27,6 @@
 import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
 import org.springframework.ai.retry.RetryUtils;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.Assert;
@@ -41,7 +40,6 @@
  *
  * @author Ahmed Yousri
  * @author Ilayaperumal Gopinathan
- * @author Filip Hrisafov
  * @see <a href=
  * "https://platform.openai.com/docs/api-reference/moderations">https://platform.openai.com/docs/api-reference/moderations</a>
  */
@@ -66,20 +64,13 @@ public OpenAiModerationApi(String baseUrl, ApiKey apiKey, MultiValueMap<String,
 
 		this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
 
-		// @formatter:off
-		this.restClient = restClientBuilder.clone()
-			.baseUrl(baseUrl)
-			.defaultHeaders(h -> {
-				h.setContentType(MediaType.APPLICATION_JSON);
-				h.addAll(headers);
-			})
-			.defaultStatusHandler(responseErrorHandler)
-			.defaultRequest(requestHeadersSpec -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
-				}
-			})
-			.build(); // @formatter:on
+		this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(h -> {
+			if (!(apiKey instanceof NoopApiKey)) {
+				h.setBearerAuth(apiKey.getValue());
+			}
+			h.setContentType(MediaType.APPLICATION_JSON);
+			h.addAll(headers);
+		}).defaultStatusHandler(responseErrorHandler).build();
 	}
 
 	public ResponseEntity<OpenAiModerationResponse> createModeration(OpenAiModerationRequest openAiModerationRequest) {
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
index e9d188052fa..30f28f610c9 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
@@ -16,27 +16,10 @@
 
 package org.springframework.ai.openai.api;
 
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Queue;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.ai.model.ApiKey;
 import org.springframework.ai.model.SimpleApiKey;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.ResponseErrorHandler;
@@ -159,126 +142,4 @@ void testInvalidResponseErrorHandler() {
 			.hasMessageContaining("responseErrorHandler cannot be null");
 	}
 
-	@Nested
-	class MockRequests {
-
-		MockWebServer mockWebServer;
-
-		@BeforeEach
-		void setUp() throws IOException {
-			mockWebServer = new MockWebServer();
-			mockWebServer.start();
-		}
-
-		@AfterEach
-		void tearDown() throws IOException {
-			mockWebServer.shutdown();
-		}
-
-		@Test
-		void dynamicApiKeyRestClient() throws InterruptedException {
-			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
-			OpenAiApi api = OpenAiApi.builder()
-				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
-				.baseUrl(mockWebServer.url("/").toString())
-				.build();
-
-			MockResponse mockResponse = new MockResponse().setResponseCode(200)
-				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
-				.setBody("""
-						{
-							"id": "chatcmpl-12345",
-							"object": "chat.completion",
-							"created": 1677858242,
-							"model": "gpt-3.5-turbo",
-							"choices": [
-								{
-						    		"index": 0,
-									"message": {
-									"role": "assistant",
-									"content": "Hello world"
-									},
-									"finish_reason": "stop"
-								}
-							],
-							"usage": {
-								"prompt_tokens": 10,
-								"completion_tokens": 5,
-								"total_tokens": 15
-							}
-						}
-						""");
-			mockWebServer.enqueue(mockResponse);
-			mockWebServer.enqueue(mockResponse);
-
-			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
-					OpenAiApi.ChatCompletionMessage.Role.USER);
-			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
-					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, false);
-			ResponseEntity<OpenAiApi.ChatCompletion> response = api.chatCompletionEntity(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-			RecordedRequest recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
-
-			response = api.chatCompletionEntity(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-
-			recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
-		}
-
-		@Test
-		void dynamicApiKeyWebClient() throws InterruptedException {
-			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
-			OpenAiApi api = OpenAiApi.builder()
-				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
-				.baseUrl(mockWebServer.url("/").toString())
-				.build();
-
-			MockResponse mockResponse = new MockResponse().setResponseCode(200)
-				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
-				.setBody("""
-						{
-							"id": "chatcmpl-12345",
-							"object": "chat.completion",
-							"created": 1677858242,
-							"model": "gpt-3.5-turbo",
-							"choices": [
-								{
-						    		"index": 0,
-									"message": {
-									"role": "assistant",
-									"content": "Hello world"
-									},
-									"finish_reason": "stop"
-								}
-							],
-							"usage": {
-								"prompt_tokens": 10,
-								"completion_tokens": 5,
-								"total_tokens": 15
-							}
-						}
-						""".replace("\n", ""));
-			mockWebServer.enqueue(mockResponse);
-			mockWebServer.enqueue(mockResponse);
-
-			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
-					OpenAiApi.ChatCompletionMessage.Role.USER);
-			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
-					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, true);
-			List<OpenAiApi.ChatCompletionChunk> response = api.chatCompletionStream(request).collectList().block();
-			assertThat(response).hasSize(1);
-			RecordedRequest recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
-
-			response = api.chatCompletionStream(request).collectList().block();
-			assertThat(response).hasSize(1);
-
-			recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
-		}
-
-	}
-
 }
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
deleted file mode 100644
index 1b11fa6807a..00000000000
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright 2023-2025 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.openai.audio.api;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Queue;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.springframework.ai.model.ApiKey;
-import org.springframework.ai.model.SimpleApiKey;
-import org.springframework.ai.openai.api.OpenAiAudioApi;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.ResponseErrorHandler;
-import org.springframework.web.client.RestClient;
-import org.springframework.web.reactive.function.client.WebClient;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-
-/**
- * @author Filip Hrisafov
- */
-class OpenAiAudioApiBuilderTests {
-
-	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
-
-	private static final String TEST_BASE_URL = "https://test.openai.com";
-
-	@Test
-	void testMinimalBuilder() {
-		OpenAiAudioApi api = OpenAiAudioApi.builder().apiKey(TEST_API_KEY).build();
-
-		assertThat(api).isNotNull();
-	}
-
-	@Test
-	void testFullBuilder() {
-		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
-		headers.add("Custom-Header", "test-value");
-		RestClient.Builder restClientBuilder = RestClient.builder();
-		WebClient.Builder webClientBuilder = WebClient.builder();
-		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
-
-		OpenAiAudioApi api = OpenAiAudioApi.builder()
-			.baseUrl(TEST_BASE_URL)
-			.apiKey(TEST_API_KEY)
-			.headers(headers)
-			.restClientBuilder(restClientBuilder)
-			.webClientBuilder(webClientBuilder)
-			.responseErrorHandler(errorHandler)
-			.build();
-
-		assertThat(api).isNotNull();
-	}
-
-	@Test
-	void testMissingApiKey() {
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("apiKey must be set");
-	}
-
-	@Test
-	void testInvalidBaseUrl() {
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().baseUrl("").build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("baseUrl cannot be null or empty");
-
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().baseUrl(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("baseUrl cannot be null or empty");
-	}
-
-	@Test
-	void testInvalidHeaders() {
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().headers(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("headers cannot be null");
-	}
-
-	@Test
-	void testInvalidRestClientBuilder() {
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().restClientBuilder(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("restClientBuilder cannot be null");
-	}
-
-	@Test
-	void testInvalidWebClientBuilder() {
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().webClientBuilder(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("webClientBuilder cannot be null");
-	}
-
-	@Test
-	void testInvalidResponseErrorHandler() {
-		assertThatThrownBy(() -> OpenAiAudioApi.builder().responseErrorHandler(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("responseErrorHandler cannot be null");
-	}
-
-	@Nested
-	class MockRequests {
-
-		MockWebServer mockWebServer;
-
-		@BeforeEach
-		void setUp() throws IOException {
-			mockWebServer = new MockWebServer();
-			mockWebServer.start();
-		}
-
-		@AfterEach
-		void tearDown() throws IOException {
-			mockWebServer.shutdown();
-		}
-
-		@Test
-		void dynamicApiKeyRestClient() throws InterruptedException {
-			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
-			OpenAiAudioApi api = OpenAiAudioApi.builder()
-				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
-				.baseUrl(mockWebServer.url("/").toString())
-				.build();
-
-			MockResponse mockResponse = new MockResponse().setResponseCode(200)
-				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
-				.setBody("Audio bytes as string");
-			mockWebServer.enqueue(mockResponse);
-			mockWebServer.enqueue(mockResponse);
-
-			OpenAiAudioApi.SpeechRequest request = OpenAiAudioApi.SpeechRequest.builder()
-				.model(OpenAiAudioApi.TtsModel.TTS_1.value)
-				.input("Test input")
-				.build();
-			ResponseEntity<byte[]> response = api.createSpeech(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-			RecordedRequest recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
-
-			response = api.createSpeech(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-
-			recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
-		}
-
-		@Test
-		void dynamicApiKeyWebClient() throws InterruptedException {
-			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
-			OpenAiAudioApi api = OpenAiAudioApi.builder()
-				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
-				.baseUrl(mockWebServer.url("/").toString())
-				.build();
-
-			MockResponse mockResponse = new MockResponse().setResponseCode(200)
-				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
-				.setBody("Audio bytes as string");
-			mockWebServer.enqueue(mockResponse);
-			mockWebServer.enqueue(mockResponse);
-
-			OpenAiAudioApi.SpeechRequest request = OpenAiAudioApi.SpeechRequest.builder()
-				.model(OpenAiAudioApi.TtsModel.TTS_1.value)
-				.input("Test input")
-				.build();
-			List<ResponseEntity<byte[]>> response = api.stream(request).collectList().block();
-			assertThat(response).hasSize(1);
-			RecordedRequest recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
-
-			response = api.stream(request).collectList().block();
-			assertThat(response).hasSize(1);
-
-			recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
-		}
-
-	}
-
-}
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
deleted file mode 100644
index 5564a087a71..00000000000
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2023-2025 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.openai.image.api;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Queue;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.springframework.ai.model.ApiKey;
-import org.springframework.ai.model.SimpleApiKey;
-import org.springframework.ai.openai.api.OpenAiImageApi;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.ResponseErrorHandler;
-import org.springframework.web.client.RestClient;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-
-/**
- * @author Filip Hrisafov
- */
-class OpenAiImageApiBuilderTests {
-
-	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
-
-	private static final String TEST_BASE_URL = "https://test.openai.com";
-
-	@Test
-	void testMinimalBuilder() {
-		OpenAiImageApi api = OpenAiImageApi.builder().apiKey(TEST_API_KEY).build();
-
-		assertThat(api).isNotNull();
-	}
-
-	@Test
-	void testFullBuilder() {
-		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
-		headers.add("Custom-Header", "test-value");
-		RestClient.Builder restClientBuilder = RestClient.builder();
-		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
-
-		OpenAiImageApi api = OpenAiImageApi.builder()
-			.baseUrl(TEST_BASE_URL)
-			.apiKey(TEST_API_KEY)
-			.headers(headers)
-			.restClientBuilder(restClientBuilder)
-			.responseErrorHandler(errorHandler)
-			.build();
-
-		assertThat(api).isNotNull();
-	}
-
-	@Test
-	void testMissingApiKey() {
-		assertThatThrownBy(() -> OpenAiImageApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("apiKey must be set");
-	}
-
-	@Test
-	void testInvalidBaseUrl() {
-		assertThatThrownBy(() -> OpenAiImageApi.builder().baseUrl("").build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("baseUrl cannot be null or empty");
-
-		assertThatThrownBy(() -> OpenAiImageApi.builder().baseUrl(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("baseUrl cannot be null or empty");
-	}
-
-	@Test
-	void testInvalidHeaders() {
-		assertThatThrownBy(() -> OpenAiImageApi.builder().headers(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("headers cannot be null");
-	}
-
-	@Test
-	void testInvalidRestClientBuilder() {
-		assertThatThrownBy(() -> OpenAiImageApi.builder().restClientBuilder(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("restClientBuilder cannot be null");
-	}
-
-	@Test
-	void testInvalidResponseErrorHandler() {
-		assertThatThrownBy(() -> OpenAiImageApi.builder().responseErrorHandler(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("responseErrorHandler cannot be null");
-	}
-
-	@Nested
-	class MockRequests {
-
-		MockWebServer mockWebServer;
-
-		@BeforeEach
-		void setUp() throws IOException {
-			mockWebServer = new MockWebServer();
-			mockWebServer.start();
-		}
-
-		@AfterEach
-		void tearDown() throws IOException {
-			mockWebServer.shutdown();
-		}
-
-		@Test
-		void dynamicApiKeyRestClient() throws InterruptedException {
-			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
-			OpenAiImageApi api = OpenAiImageApi.builder()
-				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
-				.baseUrl(mockWebServer.url("/").toString())
-				.build();
-
-			MockResponse mockResponse = new MockResponse().setResponseCode(200)
-				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
-				.setBody("""
-						{
-							"created": 1589478378,
-							"data": [
-								{
-									"url": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Mini_Golden_Doodle.jpg"
-								}
-							]
-						}
-						""");
-			mockWebServer.enqueue(mockResponse);
-			mockWebServer.enqueue(mockResponse);
-
-			OpenAiImageApi.OpenAiImageRequest request = new OpenAiImageApi.OpenAiImageRequest("Test",
-					OpenAiImageApi.ImageModel.DALL_E_3.getValue());
-			ResponseEntity<OpenAiImageApi.OpenAiImageResponse> response = api.createImage(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-			RecordedRequest recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
-
-			response = api.createImage(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-
-			recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
-		}
-
-	}
-
-}
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java
deleted file mode 100644
index 1c757789b27..00000000000
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2023-2025 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.openai.moderation.api;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Queue;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.springframework.ai.model.ApiKey;
-import org.springframework.ai.model.SimpleApiKey;
-import org.springframework.ai.openai.api.OpenAiModerationApi;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.ResponseErrorHandler;
-import org.springframework.web.client.RestClient;
-
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-
-/**
- * @author Filip Hrisafov
- */
-class OpenAiModerationApiBuilderTests {
-
-	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
-
-	private static final String TEST_BASE_URL = "https://test.openai.com";
-
-	@Test
-	void testMinimalBuilder() {
-		OpenAiModerationApi api = OpenAiModerationApi.builder().apiKey(TEST_API_KEY).build();
-
-		assertThat(api).isNotNull();
-	}
-
-	@Test
-	void testFullBuilder() {
-		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
-		headers.add("Custom-Header", "test-value");
-		RestClient.Builder restClientBuilder = RestClient.builder();
-		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
-
-		OpenAiModerationApi api = OpenAiModerationApi.builder()
-			.baseUrl(TEST_BASE_URL)
-			.apiKey(TEST_API_KEY)
-			.headers(headers)
-			.restClientBuilder(restClientBuilder)
-			.responseErrorHandler(errorHandler)
-			.build();
-
-		assertThat(api).isNotNull();
-	}
-
-	@Test
-	void testMissingApiKey() {
-		assertThatThrownBy(() -> OpenAiModerationApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("apiKey must be set");
-	}
-
-	@Test
-	void testInvalidBaseUrl() {
-		assertThatThrownBy(() -> OpenAiModerationApi.builder().baseUrl("").build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("baseUrl cannot be null or empty");
-
-		assertThatThrownBy(() -> OpenAiModerationApi.builder().baseUrl(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("baseUrl cannot be null or empty");
-	}
-
-	@Test
-	void testInvalidHeaders() {
-		assertThatThrownBy(() -> OpenAiModerationApi.builder().headers(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("headers cannot be null");
-	}
-
-	@Test
-	void testInvalidRestClientBuilder() {
-		assertThatThrownBy(() -> OpenAiModerationApi.builder().restClientBuilder(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("restClientBuilder cannot be null");
-	}
-
-	@Test
-	void testInvalidResponseErrorHandler() {
-		assertThatThrownBy(() -> OpenAiModerationApi.builder().responseErrorHandler(null).build())
-			.isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("responseErrorHandler cannot be null");
-	}
-
-	@Nested
-	class MockRequests {
-
-		MockWebServer mockWebServer;
-
-		@BeforeEach
-		void setUp() throws IOException {
-			mockWebServer = new MockWebServer();
-			mockWebServer.start();
-		}
-
-		@AfterEach
-		void tearDown() throws IOException {
-			mockWebServer.shutdown();
-		}
-
-		@Test
-		void dynamicApiKeyRestClient() throws InterruptedException {
-			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
-			OpenAiModerationApi api = OpenAiModerationApi.builder()
-				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
-				.baseUrl(mockWebServer.url("/").toString())
-				.build();
-
-			MockResponse mockResponse = new MockResponse().setResponseCode(200)
-				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
-				.setBody("""
-						{
-							"created": 1589478378,
-							"data": [
-								{
-									"url": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Mini_Golden_Doodle.jpg"
-								}
-							]
-						}
-						""");
-			mockWebServer.enqueue(mockResponse);
-			mockWebServer.enqueue(mockResponse);
-
-			OpenAiModerationApi.OpenAiModerationRequest request = new OpenAiModerationApi.OpenAiModerationRequest(
-					"Test");
-			ResponseEntity<OpenAiModerationApi.OpenAiModerationResponse> response = api.createModeration(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-			RecordedRequest recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
-
-			response = api.createModeration(request);
-			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
-
-			recordedRequest = mockWebServer.takeRequest();
-			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
-		}
-
-	}
-
-}

From 7f93f03e5226b529bd0535b5811cf9192cf9bfcf Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Fri, 30 May 2025 10:00:50 -0400
Subject: [PATCH 029/165] add qodana.yaml to gitignore

---
 .gitignore | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitignore b/.gitignore
index 41865358107..949a99baa4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,5 @@ shell.log
 
 **/.claude/settings.local.json
 .devcontainer
+
+qodana.yaml
\ No newline at end of file

From 72da79714cbd269734cec43ffa6aaa58552799cf Mon Sep 17 00:00:00 2001
From: azurelee <aliqingdong@gmail.com>
Date: Fri, 30 May 2025 22:34:27 +0800
Subject: [PATCH 030/165] fix: Remove unreachable else-if branch in
 PromptChatMemoryAdvisor (#3395)

Fixes: #3395

Auto-cherry-pick to 1.0.x

Signed-off-by: aliqingdong <229436569@qq.com>
Co-authored-by: aliqingdong <229436569@qq.com>
---
 .../chat/client/advisor/PromptChatMemoryAdvisor.java | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java
index 5ae3c9459d0..2f30e6c1d05 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java
@@ -141,18 +141,18 @@ public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChai
 	@Override
 	public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
 		List<Message> assistantMessages = new ArrayList<>();
-		if (chatClientResponse.chatResponse() != null) {
+		// Handle streaming case where we have a single result
+		if (chatClientResponse.chatResponse() != null && chatClientResponse.chatResponse().getResult() != null
+				&& chatClientResponse.chatResponse().getResult().getOutput() != null) {
+			assistantMessages = List.of((Message) chatClientResponse.chatResponse().getResult().getOutput());
+		}
+		else if (chatClientResponse.chatResponse() != null) {
 			assistantMessages = chatClientResponse.chatResponse()
 				.getResults()
 				.stream()
 				.map(g -> (Message) g.getOutput())
 				.toList();
 		}
-		// Handle streaming case where we have a single result
-		else if (chatClientResponse.chatResponse() != null && chatClientResponse.chatResponse().getResult() != null
-				&& chatClientResponse.chatResponse().getResult().getOutput() != null) {
-			assistantMessages = List.of((Message) chatClientResponse.chatResponse().getResult().getOutput());
-		}
 
 		if (!assistantMessages.isEmpty()) {
 			this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId),

From a3e069fdcba734d392df17edb7c5d5ca200ef912 Mon Sep 17 00:00:00 2001
From: Andrea Vacondio <andrea.vacondio@gmail.com>
Date: Fri, 30 May 2025 19:11:20 +0200
Subject: [PATCH 031/165] Avoid looping through all the page tree if there is
 no destination page (#3396)

* Avoid looping through all the page tree if there is no destination page

Fixes: #3396

Signed-off-by: torakiki <andrea.vacondio@gmail.com>

(cherry picked from commit 7c674c9a6b72f2385b4d664f9a3ce29866a590c7)
---
 .../ai/reader/pdf/config/ParagraphManager.java       | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/config/ParagraphManager.java b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/config/ParagraphManager.java
index b5065afa052..f3e5eb69cc0 100644
--- a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/config/ParagraphManager.java
+++ b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/config/ParagraphManager.java
@@ -140,11 +140,13 @@ private int getPageNumber(PDOutlineItem current) throws IOException {
 			return -1;
 		}
 		PDPage currentPage = current.findDestinationPage(this.document);
-		PDPageTree pages = this.document.getDocumentCatalog().getPages();
-		for (int i = 0; i < pages.getCount(); i++) {
-			var page = pages.get(i);
-			if (page.equals(currentPage)) {
-				return i + 1;
+		if (currentPage != null) {
+			PDPageTree pages = this.document.getDocumentCatalog().getPages();
+			for (int i = 0; i < pages.getCount(); i++) {
+				var page = pages.get(i);
+				if (page.equals(currentPage)) {
+					return i + 1;
+				}
 			}
 		}
 		return -1;

From 3c900b9ec7bd39eaa8d77ab9e48fa713b7322f70 Mon Sep 17 00:00:00 2001
From: Soby Chacko <soby.chacko@broadcom.com>
Date: Thu, 29 May 2025 18:13:46 -0400
Subject: [PATCH 032/165] New GH Action workflows for auto-cherry-pick and
 backport-issue

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
---
 .github/workflows/auto-cherry-pick.yml | 13 +++++++++++++
 .github/workflows/backport-issue.yml   | 12 ++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 .github/workflows/auto-cherry-pick.yml
 create mode 100644 .github/workflows/backport-issue.yml

diff --git a/.github/workflows/auto-cherry-pick.yml b/.github/workflows/auto-cherry-pick.yml
new file mode 100644
index 00000000000..1e757c9f056
--- /dev/null
+++ b/.github/workflows/auto-cherry-pick.yml
@@ -0,0 +1,13 @@
+name: Auto Cherry-Pick
+
+on:
+  push:
+    branches:
+      - main
+      - '*.x'
+
+jobs:
+  cherry-pick-commit:
+    uses: spring-io/spring-github-workflows/.github/workflows/spring-cherry-pick.yml@v5
+    secrets:
+      GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
diff --git a/.github/workflows/backport-issue.yml b/.github/workflows/backport-issue.yml
new file mode 100644
index 00000000000..a5b420eb919
--- /dev/null
+++ b/.github/workflows/backport-issue.yml
@@ -0,0 +1,12 @@
+name: Backport Issue
+
+on:
+  push:
+    branches:
+      - '*.x'
+
+jobs:
+  backport-issue:
+    uses: spring-io/spring-github-workflows/.github/workflows/spring-backport-issue.yml@v5
+    secrets:
+      GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}

From ce23f05ca7a5fe8d1cc7e7079d990be35fdc900d Mon Sep 17 00:00:00 2001
From: Emmanuel Essien-nta <colourfulemmanuel@gmail.com>
Date: Fri, 30 May 2025 18:37:54 +0100
Subject: [PATCH 033/165] Add unit tests for OpenAiStreamFunctionCallingHelper
 (#3312)

* Add unit tests for OpenAiStreamFunctionCallingHelper

Fixes: #3312

Signed-off-by: Emmanuel Essien-nta <colourfulemmanuel@gmail.com>
Signed-off-by: Soby Chacko <chackos@vmware.com>
Co-authored-by: Soby Chacko <chackos@vmware.com>
(cherry picked from commit a58cfd96f8c69b5e8dd619a6243c3c488f3b2e7a)
---
 ...OpenAiStreamFunctionCallingHelperTest.java | 165 ++++++++++++++++++
 1 file changed, 165 insertions(+)
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelperTest.java

diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelperTest.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelperTest.java
new file mode 100644
index 00000000000..53d6c0e4063
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelperTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2023-2025 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.openai.api;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link OpenAiStreamFunctionCallingHelper}
+ *
+ * @author Emmanuel Essien-nta
+ */
+public class OpenAiStreamFunctionCallingHelperTest {
+
+	private final OpenAiStreamFunctionCallingHelper helper = new OpenAiStreamFunctionCallingHelper();
+
+	@Test
+	public void merge_whenInputIsValid() {
+		var expectedResult = new OpenAiApi.ChatCompletionChunk("id", Collections.emptyList(),
+				System.currentTimeMillis(), "model", "serviceTier", "fingerPrint", "object", null);
+		var previous = new OpenAiApi.ChatCompletionChunk(null, null, expectedResult.created(), expectedResult.model(),
+				expectedResult.serviceTier(), null, null, null);
+		var current = new OpenAiApi.ChatCompletionChunk(expectedResult.id(), null, null, null, null,
+				expectedResult.systemFingerprint(), expectedResult.object(), expectedResult.usage());
+
+		var result = helper.merge(previous, current);
+		assertThat(result).isEqualTo(expectedResult);
+	}
+
+	@Test
+	public void isStreamingToolFunctionCall_whenChatCompletionChunkIsNull() {
+		assertThat(helper.isStreamingToolFunctionCall(null)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCall_whenChatCompletionChunkChoicesIsEmpty() {
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, Collections.emptyList(), null, null, null, null, null,
+				null);
+		assertThat(helper.isStreamingToolFunctionCall(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCall_whenChatCompletionChunkFirstChoiceIsNull() {
+		var choice = (org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice) null;
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, Arrays.asList(choice), null, null, null, null, null, null);
+		assertThat(helper.isStreamingToolFunctionCall(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCall_whenChatCompletionChunkFirstChoiceDeltaIsNull() {
+		var choice = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(null, null, null,
+				null);
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, Arrays.asList(choice, null), null, null, null, null, null,
+				null);
+		assertThat(helper.isStreamingToolFunctionCall(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCall_whenChatCompletionChunkFirstChoiceDeltaToolCallsIsNullOrEmpty() {
+		var assertion = (Consumer<OpenAiApi.ChatCompletionMessage>) (OpenAiApi.ChatCompletionMessage delta) -> {
+			var choice = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(null, null,
+					delta, null);
+			var chunk = new OpenAiApi.ChatCompletionChunk(null, List.of(choice), null, null, null, null, null, null);
+			assertThat(helper.isStreamingToolFunctionCall(chunk)).isFalse();
+		};
+		// Test for null.
+		assertion.accept(new OpenAiApi.ChatCompletionMessage(null, null));
+		// Test for empty.
+		assertion.accept(
+				new OpenAiApi.ChatCompletionMessage(null, null, null, null, Collections.emptyList(), null, null, null));
+	}
+
+	@Test
+	public void isStreamingToolFunctionCall_whenChatCompletionChunkFirstChoiceDeltaToolCallsIsNonEmpty() {
+		var assertion = (Consumer<OpenAiApi.ChatCompletionMessage>) (OpenAiApi.ChatCompletionMessage delta) -> {
+			var choice = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(null, null,
+					delta, null);
+			var chunk = new OpenAiApi.ChatCompletionChunk(null, List.of(choice), null, null, null, null, null, null);
+			assertThat(helper.isStreamingToolFunctionCall(chunk)).isTrue();
+		};
+		assertion.accept(new OpenAiApi.ChatCompletionMessage(null, null, null, null,
+				List.of(Mockito.mock(org.springframework.ai.openai.api.OpenAiApi.ChatCompletionMessage.ToolCall.class)),
+				null, null, null));
+	}
+
+	@Test
+	public void isStreamingToolFunctionCallFinish_whenChatCompletionChunkIsNull() {
+		assertThat(helper.isStreamingToolFunctionCallFinish(null)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCallFinish_whenChatCompletionChunkChoicesIsEmpty() {
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, Collections.emptyList(), null, null, null, null, null,
+				null);
+		assertThat(helper.isStreamingToolFunctionCallFinish(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCallFinish_whenChatCompletionChunkFirstChoiceIsNull() {
+		var choice = (org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice) null;
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, Arrays.asList(choice), null, null, null, null, null, null);
+		assertThat(helper.isStreamingToolFunctionCallFinish(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCallFinish_whenChatCompletionChunkFirstChoiceDeltaIsNull() {
+		var choice = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(null, null, null,
+				null);
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, Arrays.asList(choice, null), null, null, null, null, null,
+				null);
+		assertThat(helper.isStreamingToolFunctionCallFinish(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCallFinish_whenChatCompletionChunkFirstChoiceIsNotToolCalls() {
+		var choice = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(null, null,
+				new OpenAiApi.ChatCompletionMessage(null, null), null);
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, List.of(choice), null, null, null, null, null, null);
+		assertThat(helper.isStreamingToolFunctionCallFinish(chunk)).isFalse();
+	}
+
+	@Test
+	public void isStreamingToolFunctionCallFinish_whenChatCompletionChunkFirstChoiceIsToolCalls() {
+		var choice = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(
+				OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS, null, new OpenAiApi.ChatCompletionMessage(null, null),
+				null);
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, List.of(choice), null, null, null, null, null, null);
+		assertThat(helper.isStreamingToolFunctionCallFinish(chunk)).isTrue();
+	}
+
+	@Test
+	public void chunkToChatCompletion_whenInputIsValid() {
+		var choice1 = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(
+				OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS, 1, new OpenAiApi.ChatCompletionMessage(null, null),
+				null);
+		var choice2 = new org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk.ChunkChoice(
+				OpenAiApi.ChatCompletionFinishReason.TOOL_CALLS, 2, new OpenAiApi.ChatCompletionMessage(null, null),
+				null);
+		var chunk = new OpenAiApi.ChatCompletionChunk(null, List.of(choice1, choice2), null, null, null, null, null,
+				null);
+		OpenAiApi.ChatCompletion result = helper.chunkToChatCompletion(chunk);
+		assertThat(result.object()).isEqualTo("chat.completion");
+		assertThat(result.choices()).hasSize(2);
+	}
+
+}
\ No newline at end of file

From f4f2cfd399882a8d59ffba784b9752656cc33861 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Sat, 31 May 2025 06:53:15 +0900
Subject: [PATCH 034/165] add description of embedding-field-name property
 (#3399)

Fixes: #3399

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit 9beee1d0f0eb483fb03149cb8b5b81f50e659ee7)
---
 .../antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc
index 1349a18cf2f..db21a3be3c7 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/elasticsearch.adoc
@@ -157,6 +157,7 @@ Properties starting with `spring.ai.vectorstore.elasticsearch.*` are used to con
 |`spring.ai.vectorstore.elasticsearch.index-name` | The name of the index to store the vectors | `spring-ai-document-index`
 |`spring.ai.vectorstore.elasticsearch.dimensions` | The number of dimensions in the vector | `1536`
 |`spring.ai.vectorstore.elasticsearch.similarity` | The similarity function to use | `cosine`
+|`spring.ai.vectorstore.elasticsearch.embedding-field-name` | The name of the vector field to search against | `embedding`
 |===
 
 The following similarity functions are available:

From dd6c0a95309b16a22df810099ba92273ac451a37 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov <filip.hrisafov@gmail.com>
Date: Wed, 28 May 2025 20:01:51 +0200
Subject: [PATCH 035/165] Resolve OpenAI ApiKey for every request

 - Set ApiKey as late as possible

Signed-off-by: Filip Hrisafov <filip.hrisafov@gmail.com>
---
 .../ai/openai/api/OpenAiApi.java              |  25 +-
 .../ai/openai/api/OpenAiAudioApi.java         |  21 +-
 .../ai/openai/api/OpenAiImageApi.java         |  13 +-
 .../ai/openai/api/OpenAiModerationApi.java    |  23 +-
 .../ai/openai/api/OpenAiApiBuilderTests.java  | 283 ++++++++++++++++++
 .../audio/api/OpenAiAudioApiBuilderTests.java | 209 +++++++++++++
 .../image/api/OpenAiImageApiBuilderTests.java | 176 +++++++++++
 .../api/OpenAiModerationApiBuilderTests.java  | 176 +++++++++++
 8 files changed, 904 insertions(+), 22 deletions(-)
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
 create mode 100644 models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index d72534b4654..0d38936853c 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -62,6 +62,7 @@
  * @author Thomas Vitale
  * @author David Frizelle
  * @author Alexandros Pappas
+ * @author Filip Hrisafov
  */
 public class OpenAiApi {
 
@@ -128,10 +129,6 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> he
 
 		// @formatter:off
 		Consumer<HttpHeaders> finalHeaders = h -> {
-			if (!(apiKey instanceof NoopApiKey)) {
-				h.setBearerAuth(apiKey.getValue());
-			}
-
 			h.setContentType(MediaType.APPLICATION_JSON);
 			h.addAll(headers);
 		};
@@ -179,12 +176,17 @@ public ResponseEntity<ChatCompletion> chatCompletionEntity(ChatCompletionRequest
 		Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false.");
 		Assert.notNull(additionalHttpHeader, "The additional HTTP headers can not be null.");
 
+		// @formatter:off
 		return this.restClient.post()
 			.uri(this.completionsPath)
-			.headers(headers -> headers.addAll(additionalHttpHeader))
+			.headers(headers -> {
+				headers.addAll(additionalHttpHeader);
+				addDefaultHeadersIfMissing(headers);
+			})
 			.body(chatRequest)
 			.retrieve()
 			.toEntity(ChatCompletion.class);
+		// @formatter:on
 	}
 
 	/**
@@ -213,9 +215,13 @@ public Flux<ChatCompletionChunk> chatCompletionStream(ChatCompletionRequest chat
 
 		AtomicBoolean isInsideTool = new AtomicBoolean(false);
 
+		// @formatter:off
 		return this.webClient.post()
 			.uri(this.completionsPath)
-			.headers(headers -> headers.addAll(additionalHttpHeader))
+			.headers(headers -> {
+				headers.addAll(additionalHttpHeader);
+				addDefaultHeadersIfMissing(headers);
+			}) // @formatter:on
 			.body(Mono.just(chatRequest), ChatCompletionRequest.class)
 			.retrieve()
 			.bodyToFlux(String.class)
@@ -289,6 +295,7 @@ public <T> ResponseEntity<EmbeddingList<Embedding>> embeddings(EmbeddingRequest<
 
 		return this.restClient.post()
 			.uri(this.embeddingsPath)
+			.headers(this::addDefaultHeadersIfMissing)
 			.body(embeddingRequest)
 			.retrieve()
 			.toEntity(new ParameterizedTypeReference<>() {
@@ -296,6 +303,12 @@ public <T> ResponseEntity<EmbeddingList<Embedding>> embeddings(EmbeddingRequest<
 			});
 	}
 
+	private void addDefaultHeadersIfMissing(HttpHeaders headers) {
+		if (!headers.containsKey(HttpHeaders.AUTHORIZATION) && !(this.apiKey instanceof NoopApiKey)) {
+			headers.setBearerAuth(this.apiKey.getValue());
+		}
+	}
+
 	// Package-private getters for mutate/copy
 	String getBaseUrl() {
 		return this.baseUrl;
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
index 95604f30075..1d78ad1c3b0 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
@@ -49,6 +49,7 @@
  * @author Christian Tzolov
  * @author Ilayaperumal Gopinathan
  * @author Jonghoon Park
+ * @author Filip Hrisafov
  * @since 0.8.1
  */
 public class OpenAiAudioApi {
@@ -71,20 +72,30 @@ public OpenAiAudioApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, Strin
 			ResponseErrorHandler responseErrorHandler) {
 
 		Consumer<HttpHeaders> authHeaders = h -> {
-			if (!(apiKey instanceof NoopApiKey)) {
-				h.setBearerAuth(apiKey.getValue());
-			}
 			h.addAll(headers);
-			// h.setContentType(MediaType.APPLICATION_JSON);
 		};
 
+		// @formatter:off
 		this.restClient = restClientBuilder.clone()
 			.baseUrl(baseUrl)
 			.defaultHeaders(authHeaders)
 			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
 			.build();
 
-		this.webClient = webClientBuilder.clone().baseUrl(baseUrl).defaultHeaders(authHeaders).build();
+		this.webClient = webClientBuilder.clone()
+			.baseUrl(baseUrl)
+			.defaultHeaders(authHeaders)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
+			.build(); // @formatter:on
 	}
 
 	public static Builder builder() {
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
index bd32d42d9f6..55e34137bd1 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
 import org.springframework.ai.retry.RetryUtils;
+import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.Assert;
@@ -40,6 +41,7 @@
  *
  * @see <a href= "https://platform.openai.com/docs/api-reference/images">Images</a>
  * @author lambochen
+ * @author Filip Hrisafov
  */
 public class OpenAiImageApi {
 
@@ -62,15 +64,18 @@ public OpenAiImageApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, Strin
 			RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
 
 		// @formatter:off
-		this.restClient = restClientBuilder.baseUrl(baseUrl)
+		this.restClient = restClientBuilder.clone()
+			.baseUrl(baseUrl)
 			.defaultHeaders(h -> {
-				if (!(apiKey instanceof NoopApiKey)) {
-					h.setBearerAuth(apiKey.getValue());
-				}
 				h.setContentType(MediaType.APPLICATION_JSON);
 				h.addAll(headers);
 			})
 			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
 			.build();
 		// @formatter:on
 
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
index b9a6578b636..5a5600dea02 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
 import org.springframework.ai.retry.RetryUtils;
+import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.util.Assert;
@@ -40,6 +41,7 @@
  *
  * @author Ahmed Yousri
  * @author Ilayaperumal Gopinathan
+ * @author Filip Hrisafov
  * @see <a href=
  * "https://platform.openai.com/docs/api-reference/moderations">https://platform.openai.com/docs/api-reference/moderations</a>
  */
@@ -64,13 +66,20 @@ public OpenAiModerationApi(String baseUrl, ApiKey apiKey, MultiValueMap<String,
 
 		this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
 
-		this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(h -> {
-			if (!(apiKey instanceof NoopApiKey)) {
-				h.setBearerAuth(apiKey.getValue());
-			}
-			h.setContentType(MediaType.APPLICATION_JSON);
-			h.addAll(headers);
-		}).defaultStatusHandler(responseErrorHandler).build();
+		// @formatter:off
+		this.restClient = restClientBuilder.clone()
+			.baseUrl(baseUrl)
+			.defaultHeaders(h -> {
+				h.setContentType(MediaType.APPLICATION_JSON);
+				h.addAll(headers);
+			})
+			.defaultStatusHandler(responseErrorHandler)
+			.defaultRequest(requestHeadersSpec -> {
+				if (!(apiKey instanceof NoopApiKey)) {
+					requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue());
+				}
+			})
+			.build(); // @formatter:on
 	}
 
 	public ResponseEntity<OpenAiModerationResponse> createModeration(OpenAiModerationRequest openAiModerationRequest) {
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
index 30f28f610c9..b47a4a91bac 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java
@@ -16,10 +16,27 @@
 
 package org.springframework.ai.openai.api;
 
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
 
 import org.springframework.ai.model.ApiKey;
 import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.ResponseErrorHandler;
@@ -142,4 +159,270 @@ void testInvalidResponseErrorHandler() {
 			.hasMessageContaining("responseErrorHandler cannot be null");
 	}
 
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiApi api = OpenAiApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "chatcmpl-12345",
+							"object": "chat.completion",
+							"created": 1677858242,
+							"model": "gpt-3.5-turbo",
+							"choices": [
+								{
+						    		"index": 0,
+									"message": {
+									"role": "assistant",
+									"content": "Hello world"
+									},
+									"finish_reason": "stop"
+								}
+							],
+							"usage": {
+								"prompt_tokens": 10,
+								"completion_tokens": 5,
+								"total_tokens": 15
+							}
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
+					OpenAiApi.ChatCompletionMessage.Role.USER);
+			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
+					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, false);
+			ResponseEntity<OpenAiApi.ChatCompletion> response = api.chatCompletionEntity(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.chatCompletionEntity(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+		@Test
+		void dynamicApiKeyRestClientWithAdditionalAuthorizationHeader() throws InterruptedException {
+			OpenAiApi api = OpenAiApi.builder().apiKey(() -> {
+				throw new AssertionFailedError("Should not be called, API key is provided in headers");
+			}).baseUrl(mockWebServer.url("/").toString()).build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "chatcmpl-12345",
+							"object": "chat.completion",
+							"created": 1677858242,
+							"model": "gpt-3.5-turbo",
+							"choices": [
+								{
+						    		"index": 0,
+									"message": {
+									"role": "assistant",
+									"content": "Hello world"
+									},
+									"finish_reason": "stop"
+								}
+							],
+							"usage": {
+								"prompt_tokens": 10,
+								"completion_tokens": 5,
+								"total_tokens": 15
+							}
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
+					OpenAiApi.ChatCompletionMessage.Role.USER);
+			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
+					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, false);
+
+			MultiValueMap<String, String> additionalHeaders = new LinkedMultiValueMap<>();
+			additionalHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer additional-key");
+			ResponseEntity<OpenAiApi.ChatCompletion> response = api.chatCompletionEntity(request, additionalHeaders);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer additional-key");
+		}
+
+		@Test
+		void dynamicApiKeyWebClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiApi api = OpenAiApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "chatcmpl-12345",
+							"object": "chat.completion",
+							"created": 1677858242,
+							"model": "gpt-3.5-turbo",
+							"choices": [
+								{
+						    		"index": 0,
+									"message": {
+									"role": "assistant",
+									"content": "Hello world"
+									},
+									"finish_reason": "stop"
+								}
+							],
+							"usage": {
+								"prompt_tokens": 10,
+								"completion_tokens": 5,
+								"total_tokens": 15
+							}
+						}
+						""".replace("\n", ""));
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
+					OpenAiApi.ChatCompletionMessage.Role.USER);
+			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
+					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, true);
+			List<OpenAiApi.ChatCompletionChunk> response = api.chatCompletionStream(request).collectList().block();
+			assertThat(response).hasSize(1);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.chatCompletionStream(request).collectList().block();
+			assertThat(response).hasSize(1);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+		@Test
+		void dynamicApiKeyWebClientWithAdditionalAuthorizationHeader() throws InterruptedException {
+			OpenAiApi api = OpenAiApi.builder().apiKey(() -> {
+				throw new AssertionFailedError("Should not be called, API key is provided in headers");
+			}).baseUrl(mockWebServer.url("/").toString()).build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "chatcmpl-12345",
+							"object": "chat.completion",
+							"created": 1677858242,
+							"model": "gpt-3.5-turbo",
+							"choices": [
+								{
+						    		"index": 0,
+									"message": {
+									"role": "assistant",
+									"content": "Hello world"
+									},
+									"finish_reason": "stop"
+								}
+							],
+							"usage": {
+								"prompt_tokens": 10,
+								"completion_tokens": 5,
+								"total_tokens": 15
+							}
+						}
+						""".replace("\n", ""));
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.ChatCompletionMessage chatCompletionMessage = new OpenAiApi.ChatCompletionMessage("Hello world",
+					OpenAiApi.ChatCompletionMessage.Role.USER);
+			OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(
+					List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, true);
+			MultiValueMap<String, String> additionalHeaders = new LinkedMultiValueMap<>();
+			additionalHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer additional-key");
+			List<OpenAiApi.ChatCompletionChunk> response = api.chatCompletionStream(request, additionalHeaders)
+				.collectList()
+				.block();
+			assertThat(response).hasSize(1);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer additional-key");
+		}
+
+		@Test
+		void dynamicApiKeyRestClientEmbeddings() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiApi api = OpenAiApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"object": "list",
+							"data": [
+						    	{
+						    		"object": "embedding",
+								"index": 0,
+						       "embedding": [
+						         -0.005540426,
+						         0.0047363234,
+						         -0.015009919,
+						         -0.027093535,
+						         -0.015173893,
+						         0.015173893,
+						         -0.017608276
+						         ]
+						     }
+						   ],
+						   "model": "text-embedding-ada-002-v2",
+						   "usage": {
+						     "prompt_tokens": 2,
+						     "total_tokens": 2
+						   }
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiApi.EmbeddingRequest<String> request = new OpenAiApi.EmbeddingRequest<>("Hello world");
+			ResponseEntity<OpenAiApi.EmbeddingList<OpenAiApi.Embedding>> response = api.embeddings(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.embeddings(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
 }
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
new file mode 100644
index 00000000000..1b11fa6807a
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2023-2025 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.openai.audio.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiAudioApi;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * @author Filip Hrisafov
+ */
+class OpenAiAudioApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.openai.com";
+
+	@Test
+	void testMinimalBuilder() {
+		OpenAiAudioApi api = OpenAiAudioApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		headers.add("Custom-Header", "test-value");
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		WebClient.Builder webClientBuilder = WebClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		OpenAiAudioApi api = OpenAiAudioApi.builder()
+			.baseUrl(TEST_BASE_URL)
+			.apiKey(TEST_API_KEY)
+			.headers(headers)
+			.restClientBuilder(restClientBuilder)
+			.webClientBuilder(webClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidHeaders() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().headers(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("headers cannot be null");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidWebClientBuilder() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().webClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("webClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> OpenAiAudioApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiAudioApi api = OpenAiAudioApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
+				.setBody("Audio bytes as string");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiAudioApi.SpeechRequest request = OpenAiAudioApi.SpeechRequest.builder()
+				.model(OpenAiAudioApi.TtsModel.TTS_1.value)
+				.input("Test input")
+				.build();
+			ResponseEntity<byte[]> response = api.createSpeech(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.createSpeech(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+		@Test
+		void dynamicApiKeyWebClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiAudioApi api = OpenAiAudioApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
+				.setBody("Audio bytes as string");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiAudioApi.SpeechRequest request = OpenAiAudioApi.SpeechRequest.builder()
+				.model(OpenAiAudioApi.TtsModel.TTS_1.value)
+				.input("Test input")
+				.build();
+			List<ResponseEntity<byte[]>> response = api.stream(request).collectList().block();
+			assertThat(response).hasSize(1);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.stream(request).collectList().block();
+			assertThat(response).hasSize(1);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
+}
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
new file mode 100644
index 00000000000..5564a087a71
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023-2025 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.openai.image.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiImageApi;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * @author Filip Hrisafov
+ */
+class OpenAiImageApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.openai.com";
+
+	@Test
+	void testMinimalBuilder() {
+		OpenAiImageApi api = OpenAiImageApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		headers.add("Custom-Header", "test-value");
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		OpenAiImageApi api = OpenAiImageApi.builder()
+			.baseUrl(TEST_BASE_URL)
+			.apiKey(TEST_API_KEY)
+			.headers(headers)
+			.restClientBuilder(restClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> OpenAiImageApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidHeaders() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().headers(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("headers cannot be null");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> OpenAiImageApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiImageApi api = OpenAiImageApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"created": 1589478378,
+							"data": [
+								{
+									"url": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Mini_Golden_Doodle.jpg"
+								}
+							]
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiImageApi.OpenAiImageRequest request = new OpenAiImageApi.OpenAiImageRequest("Test",
+					OpenAiImageApi.ImageModel.DALL_E_3.getValue());
+			ResponseEntity<OpenAiImageApi.OpenAiImageResponse> response = api.createImage(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.createImage(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
+}
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java
new file mode 100644
index 00000000000..1c757789b27
--- /dev/null
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023-2025 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.openai.moderation.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.ai.openai.api.OpenAiModerationApi;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+
+/**
+ * @author Filip Hrisafov
+ */
+class OpenAiModerationApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.openai.com";
+
+	@Test
+	void testMinimalBuilder() {
+		OpenAiModerationApi api = OpenAiModerationApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
+		headers.add("Custom-Header", "test-value");
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		OpenAiModerationApi api = OpenAiModerationApi.builder()
+			.baseUrl(TEST_BASE_URL)
+			.apiKey(TEST_API_KEY)
+			.headers(headers)
+			.restClientBuilder(restClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidHeaders() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().headers(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("headers cannot be null");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> OpenAiModerationApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			OpenAiModerationApi api = OpenAiModerationApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"created": 1589478378,
+							"data": [
+								{
+									"url": "https://upload.wikimedia.org/wikipedia/commons/4/4e/Mini_Golden_Doodle.jpg"
+								}
+							]
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			OpenAiModerationApi.OpenAiModerationRequest request = new OpenAiModerationApi.OpenAiModerationRequest(
+					"Test");
+			ResponseEntity<OpenAiModerationApi.OpenAiModerationResponse> response = api.createModeration(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key1");
+
+			response = api.createModeration(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer key2");
+		}
+
+	}
+
+}

From 623e7053366480ab64a0be47c7c8c14a27b2fe94 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <1085481446@qq.com>
Date: Wed, 21 May 2025 13:19:35 +0800
Subject: [PATCH 036/165] fix: Fixed a bug in the `augmentSystemMessage` method
 of the Prompt, where an extra system message was incorrectly added when the
 system message was not the first one in the message list.

Signed-off-by: Sun Yuhan <1085481446@qq.com>
---
 .../ai/chat/prompt/Prompt.java                | 16 +++++++-------
 .../ai/chat/prompt/PromptTests.java           | 22 +++++++++++++++++++
 2 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/Prompt.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/Prompt.java
index 2d5d8b64e9d..471e5a48233 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/Prompt.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/Prompt.java
@@ -198,21 +198,21 @@ else if (message instanceof ToolResponseMessage toolResponseMessage) {
 	 * @return a new {@link Prompt} instance with the augmented system message.
 	 */
 	public Prompt augmentSystemMessage(Function<SystemMessage, SystemMessage> systemMessageAugmenter) {
-
 		var messagesCopy = new ArrayList<>(this.messages);
-		for (int i = 0; i <= this.messages.size() - 1; i++) {
+		boolean found = false;
+		for (int i = 0; i < messagesCopy.size(); i++) {
 			Message message = messagesCopy.get(i);
 			if (message instanceof SystemMessage systemMessage) {
 				messagesCopy.set(i, systemMessageAugmenter.apply(systemMessage));
+				found = true;
 				break;
 			}
-			if (i == 0) {
-				// If no system message is found, create a new one with the provided text
-				// and add it as the first item in the list.
-				messagesCopy.add(0, systemMessageAugmenter.apply(new SystemMessage("")));
-			}
 		}
-
+		if (!found) {
+			// If no system message is found, create a new one with the provided text
+			// and add it as the first item in the list.
+			messagesCopy.add(0, systemMessageAugmenter.apply(new SystemMessage("")));
+		}
 		return new Prompt(messagesCopy, null == this.chatOptions ? null : this.chatOptions.copy());
 	}
 
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/PromptTests.java b/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/PromptTests.java
index db88c270103..2b7c9efdb5b 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/PromptTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/PromptTests.java
@@ -239,4 +239,26 @@ void augmentSystemMessageWhenNone() {
 		assertThat(prompt.getSystemMessage().getText()).isEqualTo("");
 	}
 
+	@Test
+	void augmentSystemMessageWhenNotFirst() {
+		Message[] messages = { new UserMessage("Hi"), new SystemMessage("Hello") };
+		Prompt prompt = Prompt.builder().messages(messages).build();
+
+		assertThat(prompt.getSystemMessage()).isNotNull();
+		assertThat(prompt.getUserMessage()).isNotNull();
+		assertThat(prompt.getUserMessage().getText()).isEqualTo("Hi");
+		assertThat(prompt.getSystemMessage().getText()).isEqualTo("Hello");
+
+		Prompt copy = prompt.augmentSystemMessage(message -> message.mutate().text("How are you?").build());
+
+		assertThat(copy.getSystemMessage()).isNotNull();
+		assertThat(copy.getInstructions().size()).isEqualTo(messages.length);
+		assertThat(copy.getSystemMessage().getText()).isEqualTo("How are you?");
+
+		assertThat(prompt.getSystemMessage()).isNotNull();
+		assertThat(prompt.getUserMessage()).isNotNull();
+		assertThat(prompt.getUserMessage().getText()).isEqualTo("Hi");
+		assertThat(prompt.getSystemMessage().getText()).isEqualTo("Hello");
+	}
+
 }

From 8cd64bc1f45a2f1a0f22c827d65618372c6edd7f Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Tue, 3 Jun 2025 06:30:35 +0800
Subject: [PATCH 037/165] docs: Remove duplicate `NoOpTemplateRenderer` mention
 in chatclient.adoc (#3414)

Fixes: #3414

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit 12bd905413b6a3d87620cff695ba629202aaa5a1)
---
 .../src/main/antora/modules/ROOT/pages/api/chatclient.adoc      | 2 --
 1 file changed, 2 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
index 7455a077229..92541ab7449 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
@@ -336,8 +336,6 @@ By default, Spring AI uses the `StTemplateRenderer` implementation, which is bas
 
 Spring AI also provides a `NoOpTemplateRenderer` for cases where no template processing is desired.
 
-Spring AI also provides a `NoOpTemplateRenderer`.
-
 NOTE: The `TemplateRenderer` configured directly on the `ChatClient` (via `.templateRenderer()`) applies only to the prompt content defined directly in the `ChatClient` builder chain (e.g., via `.user()`, `.system()`).
 It does *not* affect templates used internally by xref:api/retrieval-augmented-generation.adoc#_questionansweradvisor[Advisors] like `QuestionAnswerAdvisor`, which have their own template customization mechanisms (see xref:api/retrieval-augmented-generation.adoc#_custom_template[Custom Advisor Templates]).
 

From 8fe3cad5eeb3416e6be593d81ee68fd002592499 Mon Sep 17 00:00:00 2001
From: pavan kumar punna <punnapavankumar9@gmail.com>
Date: Tue, 3 Jun 2025 04:05:30 +0530
Subject: [PATCH 038/165] docs(bedrock-converse): fix MIME types and
 standardize formatting in video modalities docs (#3413)

Fixes: 3413

Signed-off-by: punnapavankumar9 <punnapavankumar9@gmail.com>
(cherry picked from commit 8e5a1d37d8b1e22fc50203078c63b96684667844)
---
 .../antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc
index f353d7d99b8..29c24d18cb4 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc
@@ -222,9 +222,9 @@ The image shows a close-up view of a wire fruit basket containing several pieces
 
 The link:https://docs.aws.amazon.com/nova/latest/userguide/modalities-video.html[Amazon Nova models] allow you to include a single video in the payload, which can be provided either in base64 format or through an Amazon S3 URI.
 
-Currently, Bedrock Nova supports the images of `video/x-matros`, `video/quicktime`, `video/mp4`, `video/video/webm`, `video/x-flv`, `video/mpeg`, `video/x-ms-wmv` and `image/3gpp` mime types.
+Currently, Bedrock Nova supports the videos of `video/x-matroska`, `video/quicktime`, `video/mp4`, `video/webm`, `video/x-flv`, `video/mpeg`, `video/x-ms-wmv` and `video/3gpp` mime types.
 
-Spring AI's `Message` interface supports multimodal AI models by introducing the `Media`` type.
+Spring AI's `Message` interface supports multimodal AI models by introducing the `Media` type.
 It contains data and information about media attachments in messages, using Spring's `org.springframework.util.MimeType` and a `java.lang.Object` for the raw media data.
 
 Below is a simple code example, demonstrating the combination of user text with a video.

From 9aa0c9a9c5ab40a9e29f30700b9ed9ef0496f3d1 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Tue, 3 Jun 2025 07:37:12 +0800
Subject: [PATCH 039/165] docs:Revise the documents of deepseek and
 minimax-chat (#3408)

Fixes: #3408

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit ea995dfa517b94758938873aeaf33d0beed7067d)
---
 models/spring-ai-deepseek/README.md                       | 2 +-
 .../springframework/ai/deepseek/DeepSeekChatOptions.java  | 2 +-
 .../antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc | 8 ++++----
 .../antora/modules/ROOT/pages/api/chat/minimax-chat.adoc  | 4 ++--
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/models/spring-ai-deepseek/README.md b/models/spring-ai-deepseek/README.md
index 2a084525110..e09c3f9c63e 100644
--- a/models/spring-ai-deepseek/README.md
+++ b/models/spring-ai-deepseek/README.md
@@ -1 +1 @@
-[DeepSeek Chat Documentation](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/deepseek-chat.html)
\ No newline at end of file
+[DeepSeek Chat Documentation](https://docs.spring.io/spring-ai/reference/api/chat/deepseek-chat.html)
\ No newline at end of file
diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java
index 0731a1eb6cc..b9c7a3d4962 100644
--- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java
+++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java
@@ -49,7 +49,7 @@ public class DeepSeekChatOptions implements ToolCallingChatOptions {
 
 	// @formatter:off
 	/**
-	 * ID of the model to use. You can use either usedeepseek-coder or deepseek-chat.
+	 * ID of the model to use. You can use either use deepseek-reasoner or deepseek-chat.
 	 */
 	private @JsonProperty("model") String model;
 	/**
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc
index 4f5d7e22db6..4666d081da5 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc
@@ -118,8 +118,8 @@ The prefix `spring.ai.deepseek.chat` is the property prefix that lets you config
 | spring.ai.deepseek.chat.base-url | Optionally overrides the spring.ai.deepseek.base-url to provide a chat-specific URL | https://api.deepseek.com/
 | spring.ai.deepseek.chat.api-key | Optionally overrides the spring.ai.deepseek.api-key to provide a chat-specific API key | -
 | spring.ai.deepseek.chat.completions-path | The path to the chat completions endpoint | /chat/completions
-| spring.ai.deepseek.chat.beta-prefix-path | The prefix path to the beta feature endpoint | /beta/chat/completions
-| spring.ai.deepseek.chat.options.model | ID of the model to use. You can use either deepseek-coder or deepseek-chat. | deepseek-chat
+| spring.ai.deepseek.chat.beta-prefix-path | The prefix path to the beta feature endpoint | /beta
+| spring.ai.deepseek.chat.options.model | ID of the model to use. You can use either deepseek-reasoner or deepseek-chat. | deepseek-chat
 | spring.ai.deepseek.chat.options.frequencyPenalty | Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. | 0.0f
 | spring.ai.deepseek.chat.options.maxTokens | The maximum number of tokens to generate in the chat completion. The total length of input tokens and generated tokens is limited by the model's context length. | -
 | spring.ai.deepseek.chat.options.presencePenalty | Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. |  0.0f
@@ -348,11 +348,11 @@ ChatCompletionMessage chatCompletionMessage =
 
 // Sync request
 ResponseEntity<ChatCompletion> response = deepSeekApi.chatCompletionEntity(
-    new ChatCompletionRequest(List.of(chatCompletionMessage), DeepSeekApi.ChatModel.DEEPSEEK_CHAT.getValue(), 0.7f, false));
+    new ChatCompletionRequest(List.of(chatCompletionMessage), DeepSeekApi.ChatModel.DEEPSEEK_CHAT.getValue(), 0.7, false));
 
 // Streaming request
 Flux<ChatCompletionChunk> streamResponse = deepSeekApi.chatCompletionStream(
-        new ChatCompletionRequest(List.of(chatCompletionMessage), DeepSeekApi.ChatModel.DEEPSEEK_CHAT.getValue(), 0.7f, true));
+    new ChatCompletionRequest(List.of(chatCompletionMessage), DeepSeekApi.ChatModel.DEEPSEEK_CHAT.getValue(), 0.7, true));
 ----
 
 Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekApi.java[DeepSeekApi.java]'s JavaDoc for further information.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc
index 1b9f172928a..5b98b1f7e6a 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc
@@ -282,11 +282,11 @@ ChatCompletionMessage chatCompletionMessage =
 
 // Sync request
 ResponseEntity<ChatCompletion> response = this.miniMaxApi.chatCompletionEntity(
-    new ChatCompletionRequest(List.of(this.chatCompletionMessage), MiniMaxApi.ChatModel.ABAB_6_5_S_Chat.getValue(), 0.7f, false));
+    new ChatCompletionRequest(List.of(this.chatCompletionMessage), MiniMaxApi.ChatModel.ABAB_6_5_S_Chat.getValue(), 0.7, false));
 
 // Streaming request
 Flux<ChatCompletionChunk> streamResponse = this.miniMaxApi.chatCompletionStream(
-        new ChatCompletionRequest(List.of(this.chatCompletionMessage), MiniMaxApi.ChatModel.ABAB_6_5_S_Chat.getValue(), 0.7f, true));
+    new ChatCompletionRequest(List.of(this.chatCompletionMessage), MiniMaxApi.ChatModel.ABAB_6_5_S_Chat.getValue(), 0.7, true));
 ----
 
 Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/api/MiniMaxApi.java[MiniMaxApi.java]'s JavaDoc for further information.

From 5ecfcce16333d0289ca451e8cfb6171c5edb9feb Mon Sep 17 00:00:00 2001
From: Soby Chacko <soby.chacko@broadcom.com>
Date: Mon, 2 Jun 2025 20:04:19 -0400
Subject: [PATCH 040/165] GH-3415: Add Spring Framework 7.x compatibility to
 RetryUtils#ResponseErrorHandler

Fixes: #3415

- Remove @Override annotation from handleError(ClientHttpResponse) method
 to support compilation against both Spring 6.2.x and 7.x
- Add delegation from new handleError(URI, HttpMethod, ClientHttpResponse)
 method to maintain backward compatibility
- Preserve existing error handling logic

This change allows Spring AI to work seamlessly across Spring Framework
versions 6.x and 7.x without requiring reflection or version detection at runtime.

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
---
 .../java/org/springframework/ai/retry/RetryUtils.java  | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java b/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java
index 960382d5c96..0c45fc9b7f5 100644
--- a/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java
+++ b/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -17,12 +17,14 @@
 package org.springframework.ai.retry;
 
 import java.io.IOException;
+import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.springframework.http.HttpMethod;
 import org.springframework.http.client.ClientHttpResponse;
 import org.springframework.lang.NonNull;
 import org.springframework.retry.RetryCallback;
@@ -37,6 +39,7 @@
  * provides a default RetryTemplate and a default ResponseErrorHandler.
  *
  * @author Christian Tzolov
+ * @author Soby Chacko
  * @since 0.8.1
  */
 public abstract class RetryUtils {
@@ -49,6 +52,11 @@ public boolean hasError(@NonNull ClientHttpResponse response) throws IOException
 		}
 
 		@Override
+		public void handleError(URI url, HttpMethod method, @NonNull ClientHttpResponse response) throws IOException {
+			handleError(response);
+		}
+
+		@SuppressWarnings("removal")
 		public void handleError(@NonNull ClientHttpResponse response) throws IOException {
 			if (response.getStatusCode().isError()) {
 				String error = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);

From ae9284cb01af2939b568a623e0b635f35da1798c Mon Sep 17 00:00:00 2001
From: Filip Hrisafov <filip.hrisafov@gmail.com>
Date: Thu, 29 May 2025 01:18:25 +0200
Subject: [PATCH 041/165] Add support for ApiKey for Anthropic and use it
 dynamically for every request

Fixes #3365

 - Set ApiKey as late as possible

Signed-off-by: Filip Hrisafov <filip.hrisafov@gmail.com>
(cherry picked from commit 0a1cf81cfc135b1259e2ef330234e9bb297bdb9e)
---
 .../ai/anthropic/api/AnthropicApi.java        |  41 ++-
 .../api/AnthropicApiBuilderTests.java         | 342 ++++++++++++++++++
 2 files changed, 377 insertions(+), 6 deletions(-)
 create mode 100644 models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java

diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
index f9b63e936ca..e0a0a1bbf5f 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
@@ -34,8 +34,10 @@
 import reactor.core.publisher.Mono;
 
 import org.springframework.ai.anthropic.api.StreamHelper.ChatCompletionResponseBuilder;
+import org.springframework.ai.model.ApiKey;
 import org.springframework.ai.model.ChatModelDescription;
 import org.springframework.ai.model.ModelOptionsUtils;
+import org.springframework.ai.model.SimpleApiKey;
 import org.springframework.ai.observation.conventions.AiProvider;
 import org.springframework.ai.retry.RetryUtils;
 import org.springframework.http.HttpHeaders;
@@ -60,6 +62,7 @@
  * @author Alexandros Pappas
  * @author Jonghoon Park
  * @author Claudio Silva Junior
+ * @author Filip Hrisafov
  * @since 1.0.0
  */
 public final class AnthropicApi {
@@ -96,6 +99,8 @@ public static Builder builder() {
 
 	private final WebClient webClient;
 
+	private final ApiKey apiKey;
+
 	/**
 	 * Create a new client api.
 	 * @param baseUrl api base URL.
@@ -107,18 +112,18 @@ public static Builder builder() {
 	 * @param responseErrorHandler Response error handler.
 	 * @param anthropicBetaFeatures Anthropic beta features.
 	 */
-	private AnthropicApi(String baseUrl, String completionsPath, String anthropicApiKey, String anthropicVersion,
+	private AnthropicApi(String baseUrl, String completionsPath, ApiKey anthropicApiKey, String anthropicVersion,
 			RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
 			ResponseErrorHandler responseErrorHandler, String anthropicBetaFeatures) {
 
 		Consumer<HttpHeaders> jsonContentHeaders = headers -> {
-			headers.add(HEADER_X_API_KEY, anthropicApiKey);
 			headers.add(HEADER_ANTHROPIC_VERSION, anthropicVersion);
 			headers.add(HEADER_ANTHROPIC_BETA, anthropicBetaFeatures);
 			headers.setContentType(MediaType.APPLICATION_JSON);
 		};
 
 		this.completionsPath = completionsPath;
+		this.apiKey = anthropicApiKey;
 
 		this.restClient = restClientBuilder.clone()
 			.baseUrl(baseUrl)
@@ -160,12 +165,17 @@ public ResponseEntity<ChatCompletionResponse> chatCompletionEntity(ChatCompletio
 		Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false.");
 		Assert.notNull(additionalHttpHeader, "The additional HTTP headers can not be null.");
 
+		// @formatter:off
 		return this.restClient.post()
 			.uri(this.completionsPath)
-			.headers(headers -> headers.addAll(additionalHttpHeader))
+			.headers(headers -> {
+				headers.addAll(additionalHttpHeader);
+				addDefaultHeadersIfMissing(headers);
+			})
 			.body(chatRequest)
 			.retrieve()
 			.toEntity(ChatCompletionResponse.class);
+		// @formatter:on
 	}
 
 	/**
@@ -196,9 +206,13 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
 
 		AtomicReference<ChatCompletionResponseBuilder> chatCompletionReference = new AtomicReference<>();
 
+		// @formatter:off
 		return this.webClient.post()
 			.uri(this.completionsPath)
-			.headers(headers -> headers.addAll(additionalHttpHeader))
+			.headers(headers -> {
+				headers.addAll(additionalHttpHeader);
+				addDefaultHeadersIfMissing(headers);
+			}) // @formatter:off
 			.body(Mono.just(chatRequest), ChatCompletionRequest.class)
 			.retrieve()
 			.bodyToFlux(String.class)
@@ -232,6 +246,15 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
 			.filter(chatCompletionResponse -> chatCompletionResponse.type() != null);
 	}
 
+	private void addDefaultHeadersIfMissing(HttpHeaders headers) {
+		if (!headers.containsKey(HEADER_X_API_KEY)) {
+			String apiKeyValue = this.apiKey.getValue();
+			if (StringUtils.hasText(apiKeyValue)) {
+				headers.add(HEADER_X_API_KEY, apiKeyValue);
+			}
+		}
+	}
+
 	/**
 	 * Check the <a href="https://docs.anthropic.com/claude/docs/models-overview">Models
 	 * overview</a> and <a href=
@@ -1349,7 +1372,7 @@ public static class Builder {
 
 		private String completionsPath = DEFAULT_MESSAGE_COMPLETIONS_PATH;
 
-		private String apiKey;
+		private ApiKey apiKey;
 
 		private String anthropicVersion = DEFAULT_ANTHROPIC_VERSION;
 
@@ -1373,12 +1396,18 @@ public Builder completionsPath(String completionsPath) {
 			return this;
 		}
 
-		public Builder apiKey(String apiKey) {
+		public Builder apiKey(ApiKey apiKey) {
 			Assert.notNull(apiKey, "apiKey cannot be null");
 			this.apiKey = apiKey;
 			return this;
 		}
 
+		public Builder apiKey(String simpleApiKey) {
+			Assert.notNull(simpleApiKey, "simpleApiKey cannot be null");
+			this.apiKey = new SimpleApiKey(simpleApiKey);
+			return this;
+		}
+
 		public Builder anthropicVersion(String anthropicVersion) {
 			Assert.notNull(anthropicVersion, "anthropicVersion cannot be null");
 			this.anthropicVersion = anthropicVersion;
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java
new file mode 100644
index 00000000000..d2c0fce008f
--- /dev/null
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2023-2025 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.anthropic.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Queue;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.model.ApiKey;
+import org.springframework.ai.model.SimpleApiKey;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.ResponseErrorHandler;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.opentest4j.AssertionFailedError;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class AnthropicApiBuilderTests {
+
+	private static final ApiKey TEST_API_KEY = new SimpleApiKey("test-api-key");
+
+	private static final String TEST_BASE_URL = "https://test.anthropic.com";
+
+	private static final String TEST_COMPLETIONS_PATH = "/test/completions";
+
+	@Test
+	void testMinimalBuilder() {
+		AnthropicApi api = AnthropicApi.builder().apiKey(TEST_API_KEY).build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testFullBuilder() {
+		RestClient.Builder restClientBuilder = RestClient.builder();
+		WebClient.Builder webClientBuilder = WebClient.builder();
+		ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class);
+
+		AnthropicApi api = AnthropicApi.builder()
+			.apiKey(TEST_API_KEY)
+			.baseUrl(TEST_BASE_URL)
+			.completionsPath(TEST_COMPLETIONS_PATH)
+			.restClientBuilder(restClientBuilder)
+			.webClientBuilder(webClientBuilder)
+			.responseErrorHandler(errorHandler)
+			.build();
+
+		assertThat(api).isNotNull();
+	}
+
+	@Test
+	void testMissingApiKey() {
+		assertThatThrownBy(() -> AnthropicApi.builder().build()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("apiKey must be set");
+	}
+
+	@Test
+	void testInvalidBaseUrl() {
+		assertThatThrownBy(() -> AnthropicApi.builder().baseUrl("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+
+		assertThatThrownBy(() -> AnthropicApi.builder().baseUrl(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("baseUrl cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidCompletionsPath() {
+		assertThatThrownBy(() -> AnthropicApi.builder().completionsPath("").build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("completionsPath cannot be null or empty");
+
+		assertThatThrownBy(() -> AnthropicApi.builder().completionsPath(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("completionsPath cannot be null or empty");
+	}
+
+	@Test
+	void testInvalidRestClientBuilder() {
+		assertThatThrownBy(() -> AnthropicApi.builder().restClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("restClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidWebClientBuilder() {
+		assertThatThrownBy(() -> AnthropicApi.builder().webClientBuilder(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("webClientBuilder cannot be null");
+	}
+
+	@Test
+	void testInvalidResponseErrorHandler() {
+		assertThatThrownBy(() -> AnthropicApi.builder().responseErrorHandler(null).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("responseErrorHandler cannot be null");
+	}
+
+	@Nested
+	class MockRequests {
+
+		MockWebServer mockWebServer;
+
+		@BeforeEach
+		void setUp() throws IOException {
+			mockWebServer = new MockWebServer();
+			mockWebServer.start();
+		}
+
+		@AfterEach
+		void tearDown() throws IOException {
+			mockWebServer.shutdown();
+		}
+
+		@Test
+		void dynamicApiKeyRestClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			AnthropicApi api = AnthropicApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
+						 	"type": "message",
+						 	"role": "assistant",
+						 	"content": [],
+						 	"model": "claude-opus-3-latest",
+						 	"stop_reason": null,
+						 	"stop_sequence": null,
+							 "usage": {
+						     	"input_tokens": 25,
+						     	"output_tokens": 1
+							}
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			AnthropicApi.AnthropicMessage chatCompletionMessage = new AnthropicApi.AnthropicMessage(
+					List.of(new AnthropicApi.ContentBlock("Hello world")), AnthropicApi.Role.USER);
+			AnthropicApi.ChatCompletionRequest request = AnthropicApi.ChatCompletionRequest.builder()
+				.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS)
+				.temperature(0.8)
+				.messages(List.of(chatCompletionMessage))
+				.build();
+			ResponseEntity<AnthropicApi.ChatCompletionResponse> response = api.chatCompletionEntity(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
+			assertThat(recordedRequest.getHeader("x-api-key")).isEqualTo("key1");
+
+			response = api.chatCompletionEntity(request);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
+			assertThat(recordedRequest.getHeader("x-api-key")).isEqualTo("key2");
+		}
+
+		@Test
+		void dynamicApiKeyRestClientWithAdditionalApiKeyHeader() throws InterruptedException {
+			AnthropicApi api = AnthropicApi.builder().apiKey(() -> {
+				throw new AssertionFailedError("Should not be called, API key is provided in headers");
+			}).baseUrl(mockWebServer.url("/").toString()).build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+				.setBody("""
+						{
+							"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
+						 	"type": "message",
+						 	"role": "assistant",
+						 	"content": [],
+						 	"model": "claude-opus-3-latest",
+						 	"stop_reason": null,
+						 	"stop_sequence": null,
+							 "usage": {
+						     	"input_tokens": 25,
+						     	"output_tokens": 1
+							}
+						}
+						""");
+			mockWebServer.enqueue(mockResponse);
+
+			AnthropicApi.AnthropicMessage chatCompletionMessage = new AnthropicApi.AnthropicMessage(
+					List.of(new AnthropicApi.ContentBlock("Hello world")), AnthropicApi.Role.USER);
+			AnthropicApi.ChatCompletionRequest request = AnthropicApi.ChatCompletionRequest.builder()
+				.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS)
+				.temperature(0.8)
+				.messages(List.of(chatCompletionMessage))
+				.build();
+			MultiValueMap<String, String> additionalHeaders = new LinkedMultiValueMap<>();
+			additionalHeaders.add("x-api-key", "additional-key");
+			ResponseEntity<AnthropicApi.ChatCompletionResponse> response = api.chatCompletionEntity(request,
+					additionalHeaders);
+			assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
+			assertThat(recordedRequest.getHeader("x-api-key")).isEqualTo("additional-key");
+		}
+
+		@Test
+		void dynamicApiKeyWebClient() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			AnthropicApi api = AnthropicApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_EVENT_STREAM_VALUE)
+				.setBody("""
+						{
+							"type": "message_start",
+							"message": {
+								"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
+								"type": "message",
+								"role": "assistant",
+								"content": [],
+								"model": "claude-opus-4-20250514",
+								"stop_reason": null,
+								"stop_sequence": null,
+								"usage": {
+									"input_tokens": 25,
+									"output_tokens": 1
+								}
+							}
+						}
+						""".replace("\n", ""));
+			mockWebServer.enqueue(mockResponse);
+			mockWebServer.enqueue(mockResponse);
+
+			AnthropicApi.AnthropicMessage chatCompletionMessage = new AnthropicApi.AnthropicMessage(
+					List.of(new AnthropicApi.ContentBlock("Hello world")), AnthropicApi.Role.USER);
+			AnthropicApi.ChatCompletionRequest request = AnthropicApi.ChatCompletionRequest.builder()
+				.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS)
+				.temperature(0.8)
+				.messages(List.of(chatCompletionMessage))
+				.stream(true)
+				.build();
+			api.chatCompletionStream(request).collectList().block();
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
+			assertThat(recordedRequest.getHeader("x-api-key")).isEqualTo("key1");
+
+			api.chatCompletionStream(request).collectList().block();
+
+			recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
+			assertThat(recordedRequest.getHeader("x-api-key")).isEqualTo("key2");
+		}
+
+		@Test
+		void dynamicApiKeyWebClientWithAdditionalApiKey() throws InterruptedException {
+			Queue<ApiKey> apiKeys = new LinkedList<>(List.of(new SimpleApiKey("key1"), new SimpleApiKey("key2")));
+			AnthropicApi api = AnthropicApi.builder()
+				.apiKey(() -> Objects.requireNonNull(apiKeys.poll()).getValue())
+				.baseUrl(mockWebServer.url("/").toString())
+				.build();
+
+			MockResponse mockResponse = new MockResponse().setResponseCode(200)
+				.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_EVENT_STREAM_VALUE)
+				.setBody("""
+						{
+							"type": "message_start",
+							"message": {
+								"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY",
+								"type": "message",
+								"role": "assistant",
+								"content": [],
+								"model": "claude-opus-4-20250514",
+								"stop_reason": null,
+								"stop_sequence": null,
+								"usage": {
+									"input_tokens": 25,
+									"output_tokens": 1
+								}
+							}
+						}
+						""".replace("\n", ""));
+			mockWebServer.enqueue(mockResponse);
+
+			AnthropicApi.AnthropicMessage chatCompletionMessage = new AnthropicApi.AnthropicMessage(
+					List.of(new AnthropicApi.ContentBlock("Hello world")), AnthropicApi.Role.USER);
+			AnthropicApi.ChatCompletionRequest request = AnthropicApi.ChatCompletionRequest.builder()
+				.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS)
+				.temperature(0.8)
+				.messages(List.of(chatCompletionMessage))
+				.stream(true)
+				.build();
+			MultiValueMap<String, String> additionalHeaders = new LinkedMultiValueMap<>();
+			additionalHeaders.add("x-api-key", "additional-key");
+
+			api.chatCompletionStream(request, additionalHeaders).collectList().block();
+			RecordedRequest recordedRequest = mockWebServer.takeRequest();
+			assertThat(recordedRequest.getHeader(HttpHeaders.AUTHORIZATION)).isNull();
+			assertThat(recordedRequest.getHeader("x-api-key")).isEqualTo("additional-key");
+		}
+
+	}
+
+}

From d03654b2e72750dfb76eb78aa9f505daf75bf429 Mon Sep 17 00:00:00 2001
From: Jan-Eric Harnack <janericharnack@googlemail.com>
Date: Tue, 3 Jun 2025 19:18:45 +0200
Subject: [PATCH 042/165] chore: move speech1.mp3 to src/test/resources

Fixes: 3433

Signed-off-by: Jan-Eric Harnack <janericharnack@googlemail.com>
(cherry picked from commit ab88fea83e662c09904f42f2bf3096069c8f8852)
---
 .../springframework/ai/openai/api/OpenAiApiIT.java  |   2 +-
 .../ai/openai/chat/OpenAiChatModelIT.java           |   4 ++--
 .../resources => test/resources/speech}/speech1.mp3 | Bin
 3 files changed, 3 insertions(+), 3 deletions(-)
 rename models/spring-ai-openai/src/{main/resources => test/resources/speech}/speech1.mp3 (100%)

diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java
index 36c1eb84a7b..bf56a9fc2e8 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java
@@ -100,7 +100,7 @@ void embeddings() {
 
 	@Test
 	void inputAudio() throws IOException {
-		var audioData = new ClassPathResource("speech1.mp3").getContentAsByteArray();
+		var audioData = new ClassPathResource("speech/speech1.mp3").getContentAsByteArray();
 		List<ChatCompletionMessage.MediaContent> content = List
 			.of(new ChatCompletionMessage.MediaContent("What is this recording about?"),
 					new ChatCompletionMessage.MediaContent(new ChatCompletionMessage.MediaContent.InputAudio(
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
index 108e1d31bfa..79f39bd118b 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
@@ -570,7 +570,7 @@ void streamingMultiModalityOutputAudio(String modelName) throws IOException {
 	@ParameterizedTest(name = "{0} : {displayName} ")
 	@ValueSource(strings = { "gpt-4o-audio-preview" })
 	void multiModalityInputAudio(String modelName) {
-		var audioResource = new ClassPathResource("speech1.mp3");
+		var audioResource = new ClassPathResource("speech/speech1.mp3");
 		var userMessage = UserMessage.builder()
 			.text("What is this recording about?")
 			.media(List.of(new Media(MimeTypeUtils.parseMimeType("audio/mp3"), audioResource)))
@@ -587,7 +587,7 @@ void multiModalityInputAudio(String modelName) {
 	@ParameterizedTest(name = "{0} : {displayName} ")
 	@ValueSource(strings = { "gpt-4o-audio-preview" })
 	void streamingMultiModalityInputAudio(String modelName) {
-		var audioResource = new ClassPathResource("speech1.mp3");
+		var audioResource = new ClassPathResource("speech/speech1.mp3");
 		var userMessage = UserMessage.builder()
 			.text("What is this recording about?")
 			.media(List.of(new Media(MimeTypeUtils.parseMimeType("audio/mp3"), audioResource)))
diff --git a/models/spring-ai-openai/src/main/resources/speech1.mp3 b/models/spring-ai-openai/src/test/resources/speech/speech1.mp3
similarity index 100%
rename from models/spring-ai-openai/src/main/resources/speech1.mp3
rename to models/spring-ai-openai/src/test/resources/speech/speech1.mp3

From 35e8ee9b06d5dbcba95728cbf3e5b797045173c1 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Wed, 4 Jun 2025 09:58:40 +0900
Subject: [PATCH 043/165] Changes related to the response format have been
 added to the document. (#3434)

Fixes: #3354

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit b070c67336aa05e5b3fd8a9e90bef0b1736a36ae)
---
 .../modules/ROOT/pages/api/chat/azure-openai-chat.adoc       | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
index 1b174d59faa..636b2c4951d 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
@@ -236,7 +236,10 @@ Deployments model name to provide as part of this completions request. | gpt-4o
 | spring.ai.azure.openai.chat.options.n | The number of chat completions choices that should be generated for a chat completions response. | -
 | spring.ai.azure.openai.chat.options.stop | A collection of textual sequences that will end completions generation. | -
 | spring.ai.azure.openai.chat.options.presencePenalty |  A value that influences the probability of generated tokens appearing based on their existing presence in generated text. Positive values will make tokens less likely to appear when they already exist and increase the model's likelihood to output new topics. | -
-| spring.ai.azure.openai.chat.options.responseFormat | An object specifying the format that the model must output. Using `AzureOpenAiResponseFormat.JSON` enables JSON mode, which guarantees the message the model generates is valid JSON.  Using AzureOpenAiResponseFormat.TEXT enables TEXT mode.| -
+| spring.ai.azure.openai.chat.options.responseFormat.type | Compatible with `GPT-4o`, `GPT-4o mini`, `GPT-4 Turbo` and all `GPT-3.5 Turbo` models newer than `gpt-3.5-turbo-1106`.
+The `JSON_OBJECT` type enables JSON mode, which guarantees the message the model generates is valid JSON.
+The `JSON_SCHEMA` type enables Structured Outputs which guarantees the model will match your supplied JSON schema. The `JSON_SCHEMA` type requires setting the `responseFormat.schema` property as well. | -
+| spring.ai.azure.openai.chat.options.responseFormat.schema | Response format JSON schema. Applicable only for `responseFormat.type=JSON_SCHEMA` | -
 | spring.ai.azure.openai.chat.options.frequencyPenalty | A value that influences the probability of generated tokens appearing based on their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their frequency increases and decrease the likelihood of the model repeating the same statements verbatim. | -
 | spring.ai.azure.openai.chat.options.proxy-tool-calls | If true, the Spring AI will not handle the function calls internally, but will proxy them to the client. Then is the client's responsibility to handle the function calls, dispatch them to the appropriate function, and return the results. If false (the default), the Spring AI will handle the function calls internally. Applicable only for chat models with function calling support | false
 |====

From de3edbd867716e116963a3f3cc1d491af44a84f9 Mon Sep 17 00:00:00 2001
From: SexyProgrammer <49146257+SexyProgrammer@users.noreply.github.com>
Date: Wed, 4 Jun 2025 16:59:51 +0800
Subject: [PATCH 044/165] fix: correct ToolDefinition.builder() usage in docs
 (#3431)

Signed-off-by: SexyProgrammer <49146257+SexyProgrammer@users.noreply.github.com>
(cherry picked from commit 8af1d3958f5740dd0bd1484a47c6a51da27bf131)
---
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc       | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index fe39d655740..d649452b1f0 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -316,7 +316,7 @@ The `ToolMetadata.Builder` allows you to build a `ToolMetadata` instance and def
 ----
 Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
 ToolCallback toolCallback = MethodToolCallback.builder()
-    .toolDefinition(ToolDefinition.builder(method)
+    .toolDefinition(ToolDefinitions.builder(method)
             .description("Get the current date and time in the user's timezone")
             .build())
     .toolMethod(method)
@@ -349,7 +349,7 @@ class DateTimeTools {
 ----
 Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
 ToolCallback toolCallback = MethodToolCallback.builder()
-    .toolDefinition(ToolDefinition.builder(method)
+    .toolDefinition(ToolDefinitions.builder(method)
             .description("Get the current date and time in the user's timezone")
             .build())
     .toolMethod(method)
@@ -801,7 +801,7 @@ If you'd rather provide some or all of the attributes explicitly, you can use th
 [source,java]
 ----
 Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
-ToolDefinition toolDefinition = ToolDefinition.builder(method)
+ToolDefinition toolDefinition = ToolDefinitions.builder(method)
     .name("currentDateTime")
     .description("Get the current date and time in the user's timezone")
     .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))

From 3fe052fd83183d9eee1a30bedbfd239da9638916 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Wed, 4 Jun 2025 17:29:38 +0800
Subject: [PATCH 045/165] fix: Fixed the missing properties in the
 AutoConfiguration of ChromaVectorStore. (#3435)

Signed-off-by: Sun Yuhan <1085481446@qq.com>
(cherry picked from commit da948339839e7f8413ad12612ea0830ede6fb96e)
---
 .../autoconfigure/ChromaVectorStoreAutoConfiguration.java       | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/main/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/main/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfiguration.java
index 4af9a68728b..b6916f2cb3d 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/main/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfiguration.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/main/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfiguration.java
@@ -95,6 +95,8 @@ public ChromaVectorStore vectorStore(EmbeddingModel embeddingModel, ChromaApi ch
 			BatchingStrategy chromaBatchingStrategy) {
 		return ChromaVectorStore.builder(chromaApi, embeddingModel)
 			.collectionName(storeProperties.getCollectionName())
+			.databaseName(storeProperties.getDatabaseName())
+			.tenantName(storeProperties.getTenantName())
 			.initializeSchema(storeProperties.isInitializeSchema())
 			.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
 			.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))

From 96c45c7ee9f498f71830d0b485628380d155953f Mon Sep 17 00:00:00 2001
From: Gerrit Meier <meistermeier@gmail.com>
Date: Wed, 15 Jan 2025 16:09:37 +0100
Subject: [PATCH 046/165] Neo4j module: Determine default embedding dimension
 from model.

In cases where no custom size is set, derive the size by the given
embedding model.

Had to migrate the embeddingDimension Spring Boot property from int to Integer
to introduce the null check for the fluent config. Everything else would
have been just noisy.

Fixes #977

Signed-off-by: Gerrit Meier <meistermeier@gmail.com>
(cherry picked from commit cb97d9c4e27f7021734ce629786848cc69c42d27)
---
 .../Neo4jVectorStoreAutoConfiguration.java    |  3 +-
 .../Neo4jVectorStoreProperties.java           |  4 +--
 .../vectorstore/neo4j/Neo4jVectorStore.java   |  7 +++--
 .../vectorstore/neo4j/Neo4jVectorStoreIT.java | 28 +++++++++++++++++++
 4 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java
index 6c46a0f06db..10fe9716849 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java
@@ -68,7 +68,8 @@ public Neo4jVectorStore vectorStore(Driver driver, EmbeddingModel embeddingModel
 			.customObservationConvention(customObservationConvention.getIfAvailable(() -> null))
 			.batchingStrategy(batchingStrategy)
 			.databaseName(properties.getDatabaseName())
-			.embeddingDimension(properties.getEmbeddingDimension())
+			.embeddingDimension(properties.getEmbeddingDimension() != null ? properties.getEmbeddingDimension()
+					: embeddingModel.dimensions())
 			.distanceType(properties.getDistanceType())
 			.label(properties.getLabel())
 			.embeddingProperty(properties.getEmbeddingProperty())
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreProperties.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreProperties.java
index 0554175b1ce..5e0ed0f6763 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreProperties.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreProperties.java
@@ -33,7 +33,7 @@ public class Neo4jVectorStoreProperties extends CommonVectorStoreProperties {
 
 	private String databaseName;
 
-	private int embeddingDimension = Neo4jVectorStore.DEFAULT_EMBEDDING_DIMENSION;
+	private Integer embeddingDimension;
 
 	private Neo4jVectorStore.Neo4jDistanceType distanceType = Neo4jVectorStore.Neo4jDistanceType.COSINE;
 
@@ -57,7 +57,7 @@ public void setDatabaseName(String databaseName) {
 		this.databaseName = databaseName;
 	}
 
-	public int getEmbeddingDimension() {
+	public Integer getEmbeddingDimension() {
 		return this.embeddingDimension;
 	}
 
diff --git a/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStore.java b/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStore.java
index 8fe7f5558e0..7d44aa2a9ad 100644
--- a/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStore.java
+++ b/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStore.java
@@ -136,6 +136,7 @@ public class Neo4jVectorStore extends AbstractObservationVectorStore implements
 
 	private static final Logger logger = LoggerFactory.getLogger(Neo4jVectorStore.class);
 
+	@Deprecated(forRemoval = true)
 	public static final int DEFAULT_EMBEDDING_DIMENSION = 1536;
 
 	public static final int DEFAULT_TRANSACTION_SIZE = 10_000;
@@ -189,7 +190,7 @@ protected Neo4jVectorStore(Builder builder) {
 
 		this.driver = builder.driver;
 		this.sessionConfig = builder.sessionConfig;
-		this.embeddingDimension = builder.embeddingDimension;
+		this.embeddingDimension = builder.embeddingDimension.orElseGet(() -> builder.getEmbeddingModel().dimensions());
 		this.distanceType = builder.distanceType;
 		this.embeddingProperty = SchemaNames.sanitize(builder.embeddingProperty).orElseThrow();
 		this.label = SchemaNames.sanitize(builder.label).orElseThrow();
@@ -404,7 +405,7 @@ public static class Builder extends AbstractVectorStoreBuilder<Builder> {
 
 		private SessionConfig sessionConfig = SessionConfig.defaultConfig();
 
-		private int embeddingDimension = DEFAULT_EMBEDDING_DIMENSION;
+		private Optional<Integer> embeddingDimension = Optional.empty();
 
 		private Neo4jDistanceType distanceType = Neo4jDistanceType.COSINE;
 
@@ -459,7 +460,7 @@ public Builder sessionConfig(SessionConfig sessionConfig) {
 		 */
 		public Builder embeddingDimension(int dimension) {
 			Assert.isTrue(dimension >= 1, "Dimension has to be positive");
-			this.embeddingDimension = dimension;
+			this.embeddingDimension = Optional.of(dimension);
 			return this;
 		}
 
diff --git a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java
index d4bc5347cc8..5d8ceb7b7b4 100644
--- a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java
+++ b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java
@@ -31,6 +31,7 @@
 import org.neo4j.driver.AuthTokens;
 import org.neo4j.driver.Driver;
 import org.neo4j.driver.GraphDatabase;
+import org.springframework.context.annotation.Primary;
 import org.testcontainers.containers.Neo4jContainer;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
@@ -356,16 +357,43 @@ void getNativeClientTest() {
 		});
 	}
 
+	@Test
+	void vectorIndexDimensionsDefaultAndOverwriteWorks() {
+		this.contextRunner.run(context -> {
+			var result = context.getBean(Driver.class)
+				.executableQuery(
+						"SHOW VECTOR INDEXES yield name, options return name, options['indexConfig']['vector.dimensions'] as dimensions")
+				.execute()
+				.records()
+				.stream()
+				.map(r -> r.get("name").asString() + r.get("dimensions").asInt())
+				.toList();
+			assertThat(result).containsExactlyInAnyOrder("secondIndex123", "spring-ai-document-index1536");
+		});
+	}
+
 	@SpringBootConfiguration
 	@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
 	public static class TestApplication {
 
 		@Bean
+		@Primary
 		public VectorStore vectorStore(Driver driver, EmbeddingModel embeddingModel) {
 
 			return Neo4jVectorStore.builder(driver, embeddingModel).initializeSchema(true).build();
 		}
 
+		@Bean
+		public VectorStore vectorStoreWithCustomDimension(Driver driver, EmbeddingModel embeddingModel) {
+
+			return Neo4jVectorStore.builder(driver, embeddingModel)
+				.initializeSchema(true)
+				.indexName("secondIndex")
+				.embeddingProperty("somethingElse")
+				.embeddingDimension(123)
+				.build();
+		}
+
 		@Bean
 		public Driver driver() {
 			return GraphDatabase.driver(neo4jContainer.getBoltUrl(),

From c1a6a62320064672ba921afe1c655d7f11a904ff Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Wed, 4 Jun 2025 13:49:31 +0100
Subject: [PATCH 047/165] Update CI/CD GH Action to run against valid non main
 branches

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 .github/workflows/continuous-integration.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index a7d89794da7..c8301af4990 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -2,7 +2,9 @@ name: CI/CD build
 
 on:
   push:
-    branches: [ "main" ]
+    branches:
+      - main
+      - '*.*.x'
 
 jobs:
   build:

From c790d6c14732452d7c3d9dcbccd3ec68ac0ca4d5 Mon Sep 17 00:00:00 2001
From: lambochen <lambochen@yeah.net>
Date: Thu, 29 May 2025 22:26:58 +0800
Subject: [PATCH 048/165] fix: OpenAiChatOptions support transfer
 webSearchOptions

Unit test to veriy OpenAiChatOptions.fromOptions.webSearchOptions

Fixes: 3377

Signed-off-by: lambochen <lambochen@yeah.net>
(cherry picked from commit 4621c812cc3cad11f0508d0e56a1e7dd70f610c4)
---
 .../ai/openai/OpenAiChatOptions.java          |  2 ++
 .../ai/openai/OpenAiChatOptionsTests.java     | 21 +++++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java
index a1a9fede77e..afbbd803ec6 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java
@@ -49,6 +49,7 @@
  * @author Mariusz Bernacki
  * @author Thomas Vitale
  * @author Ilayaperumal Gopinathan
+ * @author lambochen
  * @since 0.8.0
  */
 @JsonInclude(Include.NON_NULL)
@@ -266,6 +267,7 @@ public static OpenAiChatOptions fromOptions(OpenAiChatOptions fromOptions) {
 			.store(fromOptions.getStore())
 			.metadata(fromOptions.getMetadata())
 			.reasoningEffort(fromOptions.getReasoningEffort())
+			.webSearchOptions(fromOptions.getWebSearchOptions())
 			.build();
 	}
 
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java
index d09808f1a31..70b7f1fad66 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/OpenAiChatOptionsTests.java
@@ -30,6 +30,7 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.AudioParameters.Voice.ALLOY;
+import static org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.WebSearchOptions.SearchContextSize.MEDIUM;
 
 /**
  * Tests for {@link OpenAiChatOptions}.
@@ -259,4 +260,24 @@ void testDefaultValues() {
 		assertThat(options.getStopSequences()).isNull();
 	}
 
+	@Test
+	void testFromOptions_webSearchOptions() {
+		var chatOptions = OpenAiChatOptions.builder()
+			.webSearchOptions(new OpenAiApi.ChatCompletionRequest.WebSearchOptions(MEDIUM,
+					new OpenAiApi.ChatCompletionRequest.WebSearchOptions.UserLocation("type",
+							new OpenAiApi.ChatCompletionRequest.WebSearchOptions.UserLocation.Approximate("beijing",
+									"china", "region", "UTC+8"))))
+			.build();
+		var target = OpenAiChatOptions.fromOptions(chatOptions);
+		assertThat(target.getWebSearchOptions()).isNotNull();
+		assertThat(target.getWebSearchOptions().searchContextSize()).isEqualTo(MEDIUM);
+		assertThat(target.getWebSearchOptions().userLocation()).isNotNull();
+		assertThat(target.getWebSearchOptions().userLocation().type()).isEqualTo("type");
+		assertThat(target.getWebSearchOptions().userLocation().approximate()).isNotNull();
+		assertThat(target.getWebSearchOptions().userLocation().approximate().city()).isEqualTo("beijing");
+		assertThat(target.getWebSearchOptions().userLocation().approximate().country()).isEqualTo("china");
+		assertThat(target.getWebSearchOptions().userLocation().approximate().region()).isEqualTo("region");
+		assertThat(target.getWebSearchOptions().userLocation().approximate().timezone()).isEqualTo("UTC+8");
+	}
+
 }

From 343ab5c94e8456f5c734b24faecaf9bb99902bc4 Mon Sep 17 00:00:00 2001
From: pavan kumar punna <punnapavankumar9@gmail.com>
Date: Thu, 5 Jun 2025 07:32:11 +0530
Subject: [PATCH 049/165] feat(openai): for tts and chat completions update
 supported voices enums (#3411)

* feat(openai): for tts and chat completions update supported voices enums

Fixes: #3411

Signed-off-by: punnapavankumar9 <punnapavankumar9@gmail.com>
(cherry picked from commit eb69a60df13371374932053d70eae303bc0164e4)
---
 .../ai/openai/api/OpenAiApi.java              |  8 +++++++
 .../ai/openai/api/OpenAiAudioApi.java         | 22 ++++++++++++++-----
 2 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index 0d38936853c..21a90a5d848 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -1180,6 +1180,12 @@ public record AudioParameters(
 			public enum Voice {
 				/** Alloy voice */
 				@JsonProperty("alloy") ALLOY,
+				/** Ash voice */
+				@JsonProperty("ash") ASH,
+				/** Ballad voice */
+				@JsonProperty("ballad") BALLAD,
+				/** Coral voice */
+				@JsonProperty("coral") CORAL,
 				/** Echo voice */
 				@JsonProperty("echo") ECHO,
 				/** Fable voice */
@@ -1188,6 +1194,8 @@ public enum Voice {
 				@JsonProperty("onyx") ONYX,
 				/** Nova voice */
 				@JsonProperty("nova") NOVA,
+				/** Sage voice */
+				@JsonProperty("sage") SAGE,
 				/** Shimmer voice */
 				@JsonProperty("shimmer") SHIMMER
 			}
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
index 1d78ad1c3b0..1177a98f1d3 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
@@ -240,7 +240,12 @@ public enum TtsModel {
 		 * The latest text to speech model, optimized for quality.
 		 */
 		@JsonProperty("tts-1-hd")
-		TTS_1_HD("tts-1-hd");
+		TTS_1_HD("tts-1-hd"),
+		/**
+		 * Text-to-speech model powered by GPT-4o mini
+		 */
+		@JsonProperty("gpt-4o-mini-tts")
+		GPT_4_O_MINI_TTS("gpt-4o-mini-tts");
 		// @formatter:on
 
 		public final String value;
@@ -330,14 +335,15 @@ public Class<?> getResponseType() {
 	 * Speech</a>
 	 *
 	 * @param model The model to use for generating the audio. One of the available TTS
-	 * models: tts-1 or tts-1-hd.
+	 * models: tts-1, tts-1-hd, or gpt-4o-mini-tts.
 	 * @param input The input text to synthesize. Must be at most 4096 tokens long.
 	 * @param voice The voice to use for synthesis. One of the available voices for the
-	 * chosen model: 'alloy', 'echo', 'fable', 'onyx', 'nova', and 'shimmer'.
+	 * chosen model: 'alloy', 'ash', 'ballad', 'coral', 'echo', 'fable', 'onyx', 'nova',
+	 * 'sage', 'shimmer', and 'verse'.
 	 * @param responseFormat The format to audio in. Supported formats are mp3, opus, aac,
-	 * and flac. Defaults to mp3.
+	 * flac, wav, and pcm. Defaults to mp3.
 	 * @param speed The speed of the voice synthesis. The acceptable range is from 0.25
-	 * (slowest) to 4.0 (fastest).
+	 * (slowest) to 4.0 (fastest). Does not work with gpt-4o-mini-tts.
 	 */
 	@JsonInclude(Include.NON_NULL)
 	public record SpeechRequest(
@@ -361,6 +367,8 @@ public enum Voice {
 			// @formatter:off
 			@JsonProperty("alloy")
 			ALLOY("alloy"),
+			@JsonProperty("ballad")
+			BALLAD("ballad"),
 			@JsonProperty("echo")
 			ECHO("echo"),
 			@JsonProperty("fable")
@@ -376,7 +384,9 @@ public enum Voice {
 			@JsonProperty("coral")
 			CORAL("coral"),
 			@JsonProperty("ash")
-			ASH("ash");
+			ASH("ash"),
+			@JsonProperty("verse")
+			VERSE("verse");
 			// @formatter:on
 
 			public final String value;

From 0c43e24b669ea746ea86e39dc5313bc62617e7d5 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Thu, 5 Jun 2025 22:02:19 +0900
Subject: [PATCH 050/165] docs: update huggingface doc (#3445)

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit 925def272530c22da36e9d5c34fe97e74abc85be)
---
 .../main/antora/modules/ROOT/pages/api/chat/huggingface.adoc  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/huggingface.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/huggingface.adoc
index 11c7ccf8f35..7ee36094338 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/huggingface.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/huggingface.adoc
@@ -113,7 +113,7 @@ The prefix `spring.ai.huggingface` is the property prefix that lets you configur
 | spring.ai.huggingface.chat.api-key    | API Key to authenticate with the Inference Endpoint.  |  -
 | spring.ai.huggingface.chat.url        | URL of the Inference Endpoint to connect to           |  -
 | spring.ai.huggingface.chat.enabled (Removed and no longer valid)   | Enable Hugging Face chat model.                       | true
-| spring.ai.model.chat (Removed and no longer valid)   | Enable Hugging Face chat model.                       | huggingface
+| spring.ai.model.chat                  | Enable Hugging Face chat model.                       | huggingface
 |====
 
 == Sample Controller (Auto-configuration)
@@ -186,5 +186,5 @@ HuggingfaceChatModel chatModel = new HuggingfaceChatModel(apiKey, url);
 ChatResponse response = this.chatModel.call(
     new Prompt("Generate the names of 5 famous pirates."));
 
-System.out.println(response.getGeneration().getResult().getOutput().getContent());
+System.out.println(response.getResult().getOutput().getText());
 ----

From 36903ebf52c4b5306ecb416fe791b5ad8ce8fbee Mon Sep 17 00:00:00 2001
From: Ahoo Wang <ahoowang@qq.com>
Date: Fri, 6 Jun 2025 17:19:45 +0800
Subject: [PATCH 051/165] perf(logging): optimize debug logging in
 PromptChatMemoryAdvisor (#3448)

- Enclosed debug logging statements within an isDebugEnabled() check
- Reduced unnecessary method calls and object creations when debug logging is disabled

Signed-off-by: Ahoo Wang <ahoowang@qq.com>
(cherry picked from commit e45b1b3f8d6fd5079ed889ad5b8687d985974268)
---
 .../advisor/PromptChatMemoryAdvisor.java      | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java
index 2f30e6c1d05..de88715e896 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/PromptChatMemoryAdvisor.java
@@ -157,13 +157,18 @@ else if (chatClientResponse.chatResponse() != null) {
 		if (!assistantMessages.isEmpty()) {
 			this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
 					assistantMessages);
-			logger.debug("[PromptChatMemoryAdvisor.after] Added ASSISTANT messages to memory for conversationId={}: {}",
-					this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
-					assistantMessages);
-			List<Message> memoryMessages = this.chatMemory
-				.get(this.getConversationId(chatClientResponse.context(), this.defaultConversationId));
-			logger.debug("[PromptChatMemoryAdvisor.after] Memory after ASSISTANT add for conversationId={}: {}",
-					this.getConversationId(chatClientResponse.context(), this.defaultConversationId), memoryMessages);
+
+			if (logger.isDebugEnabled()) {
+				logger.debug(
+						"[PromptChatMemoryAdvisor.after] Added ASSISTANT messages to memory for conversationId={}: {}",
+						this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
+						assistantMessages);
+				List<Message> memoryMessages = this.chatMemory
+					.get(this.getConversationId(chatClientResponse.context(), this.defaultConversationId));
+				logger.debug("[PromptChatMemoryAdvisor.after] Memory after ASSISTANT add for conversationId={}: {}",
+						this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
+						memoryMessages);
+			}
 		}
 		return chatClientResponse;
 	}

From 7ba7727585df389333dfc9bbb449ab94cc61c3d5 Mon Sep 17 00:00:00 2001
From: Piotr Kubowicz <piotr.kubowicz@gmail.com>
Date: Thu, 5 Jun 2025 14:32:12 +0200
Subject: [PATCH 052/165] build: Use spring-web instead of spring-webflux in
 spring-ai-retry

The dependency on spring-webflux was unnecessary, there was no code
inside spring-ai-retry that made use of it.

Also remove 'optional' from slf4j-api dependency (it's not optional,
if you don't have it on classpath, RetryUtils will fail to load).

Add explicit dependency on spring-webflux for modules that directly
import WebClient.

Fixes #3307

Signed-off-by: Piotr Kubowicz <piotr.kubowicz@gmail.com>
(cherry picked from commit cecc046c26ebc265a1355b43cc6b4bfbcd290ee4)
---
 models/spring-ai-anthropic/pom.xml  | 5 +++++
 models/spring-ai-deepseek/pom.xml   | 5 +++++
 models/spring-ai-minimax/pom.xml    | 5 +++++
 models/spring-ai-mistral-ai/pom.xml | 5 +++++
 models/spring-ai-ollama/pom.xml     | 5 +++++
 models/spring-ai-openai/pom.xml     | 5 +++++
 models/spring-ai-zhipuai/pom.xml    | 5 +++++
 spring-ai-retry/pom.xml             | 5 ++---
 8 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/models/spring-ai-anthropic/pom.xml b/models/spring-ai-anthropic/pom.xml
index 4467fe5e109..8614ebb7f59 100644
--- a/models/spring-ai-anthropic/pom.xml
+++ b/models/spring-ai-anthropic/pom.xml
@@ -77,6 +77,11 @@
 			<artifactId>spring-context-support</artifactId>
 		</dependency>
 
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-webflux</artifactId>
+		</dependency>
+
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
diff --git a/models/spring-ai-deepseek/pom.xml b/models/spring-ai-deepseek/pom.xml
index 491808c2c61..bdbdbd7aa11 100644
--- a/models/spring-ai-deepseek/pom.xml
+++ b/models/spring-ai-deepseek/pom.xml
@@ -40,6 +40,11 @@
 			<artifactId>spring-context-support</artifactId>
 		</dependency>
 
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-webflux</artifactId>
+		</dependency>
+
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
diff --git a/models/spring-ai-minimax/pom.xml b/models/spring-ai-minimax/pom.xml
index 4ee50d7117c..38d0c7c9833 100644
--- a/models/spring-ai-minimax/pom.xml
+++ b/models/spring-ai-minimax/pom.xml
@@ -60,6 +60,11 @@
             <artifactId>spring-context-support</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
diff --git a/models/spring-ai-mistral-ai/pom.xml b/models/spring-ai-mistral-ai/pom.xml
index 3381d23fbc1..fdb78e81784 100644
--- a/models/spring-ai-mistral-ai/pom.xml
+++ b/models/spring-ai-mistral-ai/pom.xml
@@ -61,6 +61,11 @@
 			<artifactId>spring-context-support</artifactId>
 		</dependency>
 
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
diff --git a/models/spring-ai-ollama/pom.xml b/models/spring-ai-ollama/pom.xml
index c12e6acd68b..fb01a7c79b2 100644
--- a/models/spring-ai-ollama/pom.xml
+++ b/models/spring-ai-ollama/pom.xml
@@ -57,6 +57,11 @@
             <version>${project.parent.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
diff --git a/models/spring-ai-openai/pom.xml b/models/spring-ai-openai/pom.xml
index 3934f395017..cb256062106 100644
--- a/models/spring-ai-openai/pom.xml
+++ b/models/spring-ai-openai/pom.xml
@@ -72,6 +72,11 @@
 			<artifactId>spring-context-support</artifactId>
 		</dependency>
 
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-webflux</artifactId>
+		</dependency>
+
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
diff --git a/models/spring-ai-zhipuai/pom.xml b/models/spring-ai-zhipuai/pom.xml
index 2e894e41fbe..6a42df08f6d 100644
--- a/models/spring-ai-zhipuai/pom.xml
+++ b/models/spring-ai-zhipuai/pom.xml
@@ -58,6 +58,11 @@
             <artifactId>spring-context-support</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+        </dependency>
+
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
diff --git a/spring-ai-retry/pom.xml b/spring-ai-retry/pom.xml
index 10e0468aa1c..fe7ecc4d1c8 100644
--- a/spring-ai-retry/pom.xml
+++ b/spring-ai-retry/pom.xml
@@ -49,13 +49,12 @@
 
 		<dependency>
 			<groupId>org.springframework</groupId>
-			<artifactId>spring-webflux</artifactId>
+			<artifactId>spring-web</artifactId>
 		</dependency>
 
 		<dependency>
 			<groupId>org.slf4j</groupId>
 			<artifactId>slf4j-api</artifactId>
-			<optional>true</optional>
 		</dependency>
 
 		<!-- test dependencies -->
@@ -66,4 +65,4 @@
 		</dependency>
 
 	</dependencies>
-</project>
\ No newline at end of file
+</project>

From 963b9dbded4ab244bc982b97057b36574ec49cff Mon Sep 17 00:00:00 2001
From: Solomon Hsu <solnone@gmail.com>
Date: Thu, 29 May 2025 11:29:33 +0800
Subject: [PATCH 053/165] fix(qdrant): support Long type in payload by
 converting to String

Qdrant does not support java.lang.Long as a native payload type.
Previously, passing a Long value caused the following exception:

  java.lang.IllegalArgumentException: Unsupported Qdrant value type: class java.lang.Long
    at org.springframework.ai.vectorstore.qdrant.QdrantValueFactory.value(QdrantValueFactory.java:85)
    at org.springframework.ai.vectorstore.qdrant.QdrantValueFactory.lambda$toValueMap$1(QdrantValueFactory.java:46)
    ...

To resolve this, Long values are now converted to their String representation
in QdrantValueFactory. This preserves precision and avoids serialization issues.

test(qdrant): add test to verify Long metadata values are stored as String

Adds a test to ensure that Long values in document metadata are
safely converted to String before being stored in Qdrant.

The test inserts a document with a Long-type "ref_id", performs
a similarity search, and verifies that:
- the result is not empty,
- the retrieved "ref_id" is a String,
- the value matches the original Long when parsed.

Also ensures cleanup by deleting the inserted document from the store.

Signed-off-by: Solomon Hsu <solnone@gmail.com>
(cherry picked from commit f0d4cbf03750469bc992bb8329096132bbef5c96)
---
 .../qdrant/QdrantValueFactory.java            |  3 +++
 .../qdrant/QdrantVectorStoreIT.java           | 21 +++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java
index b328f418985..c062e1f6cae 100644
--- a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java
+++ b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java
@@ -75,6 +75,9 @@ private static Value value(Object value) {
 				return ValueFactory.value((String) value);
 			case "Integer":
 				return ValueFactory.value((Integer) value);
+			case "Long":
+				// use String representation
+				return ValueFactory.value(String.valueOf(value));
 			case "Double":
 				return ValueFactory.value((Double) value);
 			case "Float":
diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
index 004bf32d226..b2af2ab758c 100644
--- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
+++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
@@ -314,6 +314,27 @@ void getNativeClientTest() {
 		});
 	}
 
+	@Test
+	void shouldConvertLongToString() {
+		this.contextRunner.run(context -> {
+			QdrantVectorStore vectorStore = context.getBean(QdrantVectorStore.class);
+			var refId = System.currentTimeMillis();
+			var doc = new Document("Long type ref_id", Map.of("ref_id", refId));
+			vectorStore.add(List.of(doc));
+
+			List<Document> results = vectorStore
+				.similaritySearch(SearchRequest.builder().query("Long type ref_id").topK(1).build());
+			assertThat(results).hasSize(1);
+			Document resultDoc = results.get(0);
+			var resultRefId = resultDoc.getMetadata().get("ref_id");
+			assertThat(resultRefId).isInstanceOf(String.class);
+			assertThat(Double.valueOf((String) resultRefId)).isEqualTo(refId);
+
+			// Remove all documents from the store
+			vectorStore.delete(List.of(resultDoc.getId()));
+		});
+	}
+
 	@SpringBootConfiguration
 	public static class TestApplication {
 

From a3d0898ee5da128b015b43ccdb43eb4ef958d5d6 Mon Sep 17 00:00:00 2001
From: chainHo <chainhou@gmail.com>
Date: Fri, 6 Jun 2025 18:34:02 +0800
Subject: [PATCH 054/165] fix:Typo cloud bindings (#3436)

* fix:Typo cloud bindings doc

Signed-off-by: chainHo <chainhou@gmail.com>
(cherry picked from commit 694bb50be5825bb2d91f3ce0291020d9aadcb780)
---
 .../main/antora/modules/ROOT/pages/api/cloud-bindings.adoc  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/cloud-bindings.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/cloud-bindings.adoc
index 4c1247d3238..eaed94d27d1 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/cloud-bindings.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/cloud-bindings.adoc
@@ -33,13 +33,13 @@ TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Man
 
 == Available Cloud Bindings
 
-The following are the components for which the cloud binding support is currently available in the  `spring-ai-spring-clou-bindings` module:
+The following are the components for which the cloud binding support is currently available in the  `spring-ai-spring-cloud-bindings` module:
 
 [cols="|,|"]
 |====
 | Service Type	 | Binding Type | Source Properties | Target Properties
 | `Chroma Vector Store`
-| `chroma` | `uri`, `username`, `passwor` | `spring.ai.vectorstore.chroma.client.host`, `spring.ai.vectorstore.chroma.client.port`, `spring.ai.vectorstore.chroma.client.username`, `spring.ai.vectorstore.chroma.client.host.password`
+| `chroma` | `uri`, `username`, `password` | `spring.ai.vectorstore.chroma.client.host`, `spring.ai.vectorstore.chroma.client.port`, `spring.ai.vectorstore.chroma.client.username`, `spring.ai.vectorstore.chroma.client.host.password`
 
 | `Mistral AI`
 | `mistralai` | `api-key`, `uri` | `spring.ai.mistralai.api-key`, `spring.ai.mistralai.base-url`
@@ -54,5 +54,5 @@ The following are the components for which the cloud binding support is currentl
 | `weaviate` | `uri`, `api-key` | `spring.ai.vectorstore.weaviate.scheme`, `spring.ai.vectorstore.weaviate.host`, `spring.ai.vectorstore.weaviate.api-key`
 
 | `Tanzu GenAI`
-| `genai` | `uri`, `api-key`, `model-capabilities` (`chat` and `embedding`), `model-name` | `spring.ai.openai.chat.base-url`, , spring.ai.openai.chat.api-key`, `spring.ai.openai.chat.options.model`, `spring.ai.openai.embedding.base-url`, , spring.ai.openai.embedding.api-key`, `spring.ai.openai.embedding.options.model`
+| `genai` | `uri`, `api-key`, `model-capabilities` (`chat` and `embedding`), `model-name` | `spring.ai.openai.chat.base-url`, `spring.ai.openai.chat.api-key`, `spring.ai.openai.chat.options.model`, `spring.ai.openai.embedding.base-url`, `spring.ai.openai.embedding.api-key`, `spring.ai.openai.embedding.options.model`
 |====

From dfa09c193093aec667304f8392358e45cf88d625 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?=
 <eddu.melendez@gmail.com>
Date: Mon, 9 Jun 2025 00:26:28 -0600
Subject: [PATCH 055/165] Make auto-configuration deps optional in
 testcontainers module (#3465)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, spring-ai-spring-boot-testcontainers module brings
auto-configuration modules plus other dependencies which are unnecessary.

Signed-off-by: Eddú Meléndez <eddu.melendez@gmail.com>
(cherry picked from commit 161c437556a3f79761c8d8991f1fa22f68cbc78e)
---
 spring-ai-spring-boot-testcontainers/pom.xml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml
index 0b81e6dd2e3..276e9f73802 100644
--- a/spring-ai-spring-boot-testcontainers/pom.xml
+++ b/spring-ai-spring-boot-testcontainers/pom.xml
@@ -48,48 +48,57 @@
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-model-ollama</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-opensearch</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-chroma</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-mongodb-atlas</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-milvus</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-qdrant</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-weaviate</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-typesense</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 
 		<dependency>
 			<groupId>com.google.protobuf</groupId>
 			<artifactId>protobuf-java</artifactId>
 			<version>${protobuf-java.version}</version>
+			<optional>true</optional>
 		</dependency>
 
 		<!-- production dependencies -->

From 8053ae39c5fdf469c25fa317682655194faedd3c Mon Sep 17 00:00:00 2001
From: WOONBE <kepull2918@naver.com>
Date: Mon, 9 Jun 2025 17:00:26 +0900
Subject: [PATCH 056/165] Refactor QdrantObjectFactory to allow null metadata
 values

Add test for check toObjectMap method allows null metadata values

Signed-off-by: WOONBE <kepull2918@naver.com>
---
 .../qdrant/QdrantObjectFactory.java           |  8 ++-
 .../qdrant/QdrantObjectFactoryTests.java      | 53 +++++++++++++++++++
 2 files changed, 60 insertions(+), 1 deletion(-)
 create mode 100644 vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactoryTests.java

diff --git a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactory.java b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactory.java
index 08220a53c19..d3233e666a0 100644
--- a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactory.java
+++ b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactory.java
@@ -16,6 +16,7 @@
 
 package org.springframework.ai.vectorstore.qdrant;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -30,6 +31,7 @@
  * Utility methods for building Java objects from io.qdrant.client.grpc.JsonWithInt.Value.
  *
  * @author Anush Shetty
+ * @author Heonwoo Kim
  * @since 0.8.1
  */
 final class QdrantObjectFactory {
@@ -41,7 +43,11 @@ private QdrantObjectFactory() {
 
 	public static Map<String, Object> toObjectMap(Map<String, Value> payload) {
 		Assert.notNull(payload, "Payload map must not be null");
-		return payload.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> object(e.getValue())));
+		Map<String, Object> map = new HashMap<>();
+		for (Map.Entry<String, Value> entry : payload.entrySet()) {
+			map.put(entry.getKey(), object(entry.getValue()));
+		}
+		return map;
 	}
 
 	private static Object object(ListValue listValue) {
diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactoryTests.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactoryTests.java
new file mode 100644
index 00000000000..336ea24f828
--- /dev/null
+++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantObjectFactoryTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023-2024 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.vectorstore.qdrant;
+
+import java.util.Map;
+
+import io.qdrant.client.grpc.JsonWithInt.NullValue;
+import io.qdrant.client.grpc.JsonWithInt.Value;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link QdrantObjectFactory}.
+ *
+ * @author Heonwoo Kim
+ */
+
+class QdrantObjectFactoryTests {
+
+	@Test
+	void toObjectMapShouldHandleNullValues() {
+		Map<String, Value> payloadWithNull = Map.of("name", Value.newBuilder().setStringValue("Spring AI").build(),
+				"version", Value.newBuilder().setDoubleValue(1.0).build(), "is_ga",
+				Value.newBuilder().setBoolValue(true).build(), "description",
+				Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build());
+
+		Map<String, Object> result = QdrantObjectFactory.toObjectMap(payloadWithNull);
+
+		assertThat(result).isNotNull();
+		assertThat(result).hasSize(4);
+		assertThat(result.get("name")).isEqualTo("Spring AI");
+		assertThat(result.get("version")).isEqualTo(1.0);
+		assertThat(result.get("is_ga")).isEqualTo(true);
+		assertThat(result).containsKey("description");
+		assertThat(result.get("description")).isNull();
+	}
+
+}

From 512ae893d225eb5d05f124e01bcde80a3e75ff2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?=
 <eddu.melendez@gmail.com>
Date: Tue, 10 Jun 2025 10:34:37 -0600
Subject: [PATCH 057/165] Make auto-configuration deps optional in docker
 compose module (#3502)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently, spring-ai-spring-boot-docker-compose module brings
auto-configuration modules plus other dependencies which are unnecessary.

Signed-off-by: Eddú Meléndez <eddu.melendez@gmail.com>
(cherry picked from commit 825bcf6ab0987503657343ebd8d1ab4895be74b7)
---
 spring-ai-spring-boot-docker-compose/pom.xml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml
index b1c9a9184ee..b59661caec9 100644
--- a/spring-ai-spring-boot-docker-compose/pom.xml
+++ b/spring-ai-spring-boot-docker-compose/pom.xml
@@ -47,32 +47,38 @@
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-opensearch</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-chroma</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-weaviate</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-qdrant</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-vector-store-typesense</artifactId>
 			<version>${project.parent.version}</version>
+			<optional>true</optional>
 		</dependency>
 
         <dependency>
             <groupId>com.google.protobuf</groupId>
             <artifactId>protobuf-java</artifactId>
             <version>${protobuf-java.version}</version>
+			<optional>true</optional>
         </dependency>
 
         <!-- production dependencies -->

From 95ea7f22229db778360a54e1e864f03b86aa590e Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Tue, 10 Jun 2025 16:05:51 +0800
Subject: [PATCH 058/165] fix: Fixed GH-3492, Fixed the SQL error in the
 `isTableExists` method of `MariaDBSchemaValidator`.

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit b925c50f0407af0097ac409baa7e0cc44b4782d0)
---
 .../ai/vectorstore/mariadb/MariaDBSchemaValidator.java     | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/vector-stores/spring-ai-mariadb-store/src/main/java/org/springframework/ai/vectorstore/mariadb/MariaDBSchemaValidator.java b/vector-stores/spring-ai-mariadb-store/src/main/java/org/springframework/ai/vectorstore/mariadb/MariaDBSchemaValidator.java
index cb82358955f..82fbc0d68f0 100644
--- a/vector-stores/spring-ai-mariadb-store/src/main/java/org/springframework/ai/vectorstore/mariadb/MariaDBSchemaValidator.java
+++ b/vector-stores/spring-ai-mariadb-store/src/main/java/org/springframework/ai/vectorstore/mariadb/MariaDBSchemaValidator.java
@@ -45,12 +45,11 @@ public MariaDBSchemaValidator(JdbcTemplate jdbcTemplate) {
 
 	private boolean isTableExists(String schemaName, String tableName) {
 		// schema and table are expected to be escaped
-		String sql = String.format(
-				"SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
-				(schemaName == null) ? "SCHEMA()" : schemaName, tableName);
+		String sql = "SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";
 		try {
 			// Query for a single integer value, if it exists, table exists
-			this.jdbcTemplate.queryForObject(sql, Integer.class);
+			this.jdbcTemplate.queryForObject(sql, Integer.class, (schemaName == null) ? "SCHEMA()" : schemaName,
+					tableName);
 			return true;
 		}
 		catch (DataAccessException e) {

From 95e9808f55ebc2c5690b6e6c4a87d4604200a6df Mon Sep 17 00:00:00 2001
From: SiBo Ai <46702223+ai-afk@users.noreply.github.com>
Date: Wed, 11 Jun 2025 01:04:56 +0800
Subject: [PATCH 059/165] docs: Fix the incorrect code in the openai-chat.adoc
 document (#3493)

Signed-off-by: aisibo1 <aisibo1@jd.com>
(cherry picked from commit e9646c269ec34205f98026a4bdb53a3394a2be97)
---
 .../main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
index 43f5f9469e7..b6aec282423 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
@@ -632,7 +632,7 @@ OpenAiApi openAiApi = OpenAiApi.builder()
     .build();
 
 // Create a chat model with the custom OpenAiApi instance
-OpenAiChatmodel chatModel = OpenAiChatModel.builder()
+OpenAiChatModel chatModel = OpenAiChatModel.builder()
     .openAiApi(openAiApi)
     .build();
 // Build the ChatClient using the custom chat model

From 518ba27b28a2f4446bc1f10b0304f342a3d64573 Mon Sep 17 00:00:00 2001
From: Guan Huo <9uanhuo@gmail.com>
Date: Wed, 11 Jun 2025 04:38:47 +0800
Subject: [PATCH 060/165] fix: missing 'embeddingFieldName' when configuring ES
 VectorStore (#3501)

Fixes: #3500

Signed-off-by: Jionghui Zheng <9uanhuo@gmail.com>
(cherry picked from commit 1f5dc3f73974c31c5d3e27c9c304e29f98b78516)
---
 .../ElasticsearchVectorStoreAutoConfiguration.java            | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java
index 19f8d36b870..7046adad375 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java
@@ -45,6 +45,7 @@
  * @author Christian Tzolov
  * @author Soby Chacko
  * @author Jonghoon Park
+ * @author Jionghui Zheng
  * @since 1.0.0
  */
 @AutoConfiguration(after = ElasticsearchRestClientAutoConfiguration.class)
@@ -72,6 +73,9 @@ ElasticsearchVectorStore vectorStore(ElasticsearchVectorStoreProperties properti
 		mapper.from(properties::getIndexName).whenHasText().to(elasticsearchVectorStoreOptions::setIndexName);
 		mapper.from(properties::getDimensions).whenNonNull().to(elasticsearchVectorStoreOptions::setDimensions);
 		mapper.from(properties::getSimilarity).whenNonNull().to(elasticsearchVectorStoreOptions::setSimilarity);
+		mapper.from(properties::getEmbeddingFieldName)
+			.whenHasText()
+			.to(elasticsearchVectorStoreOptions::setEmbeddingFieldName);
 
 		return ElasticsearchVectorStore.builder(restClient, embeddingModel)
 			.options(elasticsearchVectorStoreOptions)

From 25662fc15bdb1d579174e9cbfd1b68df91b6740c Mon Sep 17 00:00:00 2001
From: Soby Chacko <soby.chacko@broadcom.com>
Date: Tue, 10 Jun 2025 17:03:23 -0400
Subject: [PATCH 061/165] Add test for embeddingFieldName config in ES
 VectorStore

Adding test for commit in main 1f5dc3f73974c31c5d3e27c9c304e29f98b78516

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
(cherry picked from commit 6451fef547b110f171cfe4ba9c392316d2d252eb)
---
 ...lasticsearchVectorStoreAutoConfigurationIT.java | 14 +++++++++++++-
 .../elasticsearch/ElasticsearchVectorStore.java    |  2 --
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java
index e86548e4be7..48e5191a171 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java
@@ -17,6 +17,7 @@
 package org.springframework.ai.vectorstore.elasticsearch.autoconfigure;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +38,7 @@
 import org.springframework.ai.vectorstore.SearchRequest;
 import org.springframework.ai.vectorstore.VectorStore;
 import org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStore;
+import org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStoreOptions;
 import org.springframework.ai.vectorstore.elasticsearch.SimilarityFunction;
 import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -136,7 +138,8 @@ public void propertiesTest() {
 					"spring.ai.vectorstore.elasticsearch.index-name=example",
 					"spring.ai.vectorstore.elasticsearch.dimensions=1024",
 					"spring.ai.vectorstore.elasticsearch.dense-vector-indexing=true",
-					"spring.ai.vectorstore.elasticsearch.similarity=cosine")
+					"spring.ai.vectorstore.elasticsearch.similarity=cosine",
+					"spring.ai.vectorstore.elasticsearch.embedding-field-name=custom_embedding_field")
 			.run(context -> {
 				var properties = context.getBean(ElasticsearchVectorStoreProperties.class);
 				var elasticsearchVectorStore = context.getBean(ElasticsearchVectorStore.class);
@@ -146,7 +149,16 @@ public void propertiesTest() {
 				assertThat(properties.getDimensions()).isEqualTo(1024);
 				assertThat(properties.getSimilarity()).isEqualTo(SimilarityFunction.cosine);
 
+				assertThat(properties.getEmbeddingFieldName()).isEqualTo("custom_embedding_field");
+
 				assertThat(elasticsearchVectorStore).isNotNull();
+
+				Field optionsField = ElasticsearchVectorStore.class.getDeclaredField("options");
+				optionsField.setAccessible(true);
+				var options = (ElasticsearchVectorStoreOptions) optionsField.get(elasticsearchVectorStore);
+
+				assertThat(options).isNotNull();
+				assertThat(options.getEmbeddingFieldName()).isEqualTo("custom_embedding_field");
 			});
 	}
 
diff --git a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
index f0919463294..4a0ceff3dbf 100644
--- a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
+++ b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
@@ -147,8 +147,6 @@
  */
 public class ElasticsearchVectorStore extends AbstractObservationVectorStore implements InitializingBean {
 
-	private static final Logger logger = LoggerFactory.getLogger(ElasticsearchVectorStore.class);
-
 	private static final Map<SimilarityFunction, VectorStoreSimilarityMetric> SIMILARITY_TYPE_MAPPING = Map.of(
 			SimilarityFunction.cosine, VectorStoreSimilarityMetric.COSINE, SimilarityFunction.l2_norm,
 			VectorStoreSimilarityMetric.EUCLIDEAN, SimilarityFunction.dot_product, VectorStoreSimilarityMetric.DOT);

From fcbdac96142327f46b63a9d4992fab5b0f470d7f Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Mon, 9 Jun 2025 11:21:34 +0800
Subject: [PATCH 062/165] fix: MethodToolCallbackProvider#isFunctionalType
 logic to check functiontype

   - Change LHR <-> RHR in the ClassUtils.isAssignable

Fixes #GH-3355

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit fe4f0d197ca192450d202bcb90ff972cc2b73cfe)
---
 .../method/MethodToolCallbackProvider.java     |  6 +++---
 .../MethodToolCallbackProviderTests.java       | 18 ++++++++++++++++++
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
index 666aa6f97f3..65a42dfea87 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
@@ -103,9 +103,9 @@ public ToolCallback[] getToolCallbacks() {
 	}
 
 	private boolean isFunctionalType(Method toolMethod) {
-		var isFunction = ClassUtils.isAssignable(toolMethod.getReturnType(), Function.class)
-				|| ClassUtils.isAssignable(toolMethod.getReturnType(), Supplier.class)
-				|| ClassUtils.isAssignable(toolMethod.getReturnType(), Consumer.class);
+		var isFunction = ClassUtils.isAssignable(Function.class, toolMethod.getReturnType())
+				|| ClassUtils.isAssignable(Supplier.class, toolMethod.getReturnType())
+				|| ClassUtils.isAssignable(Consumer.class, toolMethod.getReturnType());
 
 		if (isFunction) {
 			logger.warn("Method {} is annotated with @Tool but returns a functional type. "
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
index cdc9fccedfb..df52c92a6fc 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
@@ -78,6 +78,15 @@ void whenMultipleToolObjectsWithSameToolNameThenThrow() {
 			.hasMessageContaining("Multiple tools with the same name (validTool) found in sources");
 	}
 
+	@Test
+	void whenToolObjectHasObjectTypeMethodThenSuccess() {
+		MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
+			.toolObjects(new ObjectTypeToolMethodsObject())
+			.build();
+		assertThat(provider.getToolCallbacks()).hasSize(1);
+		assertThat(provider.getToolCallbacks()[0].getToolDefinition().name()).isEqualTo("objectTool");
+	}
+
 	static class ValidToolObject {
 
 		@Tool
@@ -137,4 +146,13 @@ public String validTool() {
 
 	}
 
+	static class ObjectTypeToolMethodsObject {
+
+		@Tool
+		public Object objectTool() {
+			return "Object tool result";
+		}
+
+	}
+
 }

From 2ca1be2b838e87b9c0674b4b8b76f1010eb45951 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Mon, 9 Jun 2025 10:48:16 +0800
Subject: [PATCH 063/165] fix: corrected a logic error in the
 `validateToolContextSupport` method caused by incorrect parameter order.

Fixes #GH-3466

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
---
 .../ai/tool/method/MethodToolCallback.java    |  4 +-
 .../MethodToolCallbackGenericTypesTest.java   | 77 +++++++++++++++++++
 2 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
index cc320a54d5c..d5f3970ea5a 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
@@ -118,8 +118,8 @@ public String call(String toolInput, @Nullable ToolContext toolContext) {
 	private void validateToolContextSupport(@Nullable ToolContext toolContext) {
 		var isNonEmptyToolContextProvided = toolContext != null && !CollectionUtils.isEmpty(toolContext.getContext());
 		var isToolContextAcceptedByMethod = Stream.of(this.toolMethod.getParameterTypes())
-			.anyMatch(type -> ClassUtils.isAssignable(type, ToolContext.class));
-		if (isToolContextAcceptedByMethod && !isNonEmptyToolContextProvided) {
+			.anyMatch(type -> ClassUtils.isAssignable(ToolContext.class, type));
+		if (isNonEmptyToolContextProvided && !isToolContextAcceptedByMethod) {
 			throw new IllegalArgumentException("ToolContext is required by the method as an argument");
 		}
 	}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
index b99faa71a3a..544d440a9a9 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
@@ -22,10 +22,12 @@
 
 import org.junit.jupiter.api.Test;
 
+import org.springframework.ai.chat.model.ToolContext;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
  * Tests for {@link MethodToolCallback} with generic types.
@@ -137,6 +139,76 @@ void testNestedGenericType() throws Exception {
 		assertThat(result).isEqualTo("2 maps processed: [{a=1, b=2}, {c=3, d=4}]");
 	}
 
+	@Test
+	void testToolContextType() throws Exception {
+		// Create a test object with a method that takes a List<Map<String, Integer>>
+		TestGenericClass testObject = new TestGenericClass();
+		Method method = TestGenericClass.class.getMethod("processStringListInToolContext", ToolContext.class);
+
+		// Create a tool definition
+		ToolDefinition toolDefinition = DefaultToolDefinition.builder()
+			.name("processToolContext")
+			.description("Process tool context")
+			.inputSchema("{}")
+			.build();
+
+		// Create a MethodToolCallback
+		MethodToolCallback callback = MethodToolCallback.builder()
+			.toolDefinition(toolDefinition)
+			.toolMethod(method)
+			.toolObject(testObject)
+			.build();
+
+		// Create an empty JSON input
+		String toolInput = """
+				{}
+				""";
+
+		// Create a toolContext
+		ToolContext toolContext = new ToolContext(Map.of("foo", "bar"));
+
+		// Call the tool
+		String result = callback.call(toolInput, toolContext);
+
+		// Verify the result
+		assertThat(result).isEqualTo("1 entries processed {foo=bar}");
+	}
+
+	@Test
+	void testToolContextTypeWithNonToolContextArgs() throws Exception {
+		// Create a test object with a method that takes a List<String>
+		TestGenericClass testObject = new TestGenericClass();
+		Method method = TestGenericClass.class.getMethod("processStringList", List.class);
+
+		// Create a tool definition
+		ToolDefinition toolDefinition = DefaultToolDefinition.builder()
+			.name("processStringList")
+			.description("Process a list of strings")
+			.inputSchema("{}")
+			.build();
+
+		// Create a MethodToolCallback
+		MethodToolCallback callback = MethodToolCallback.builder()
+			.toolDefinition(toolDefinition)
+			.toolMethod(method)
+			.toolObject(testObject)
+			.build();
+
+		// Create a JSON input with a list of strings
+		String toolInput = """
+				{
+					"strings": ["one", "two", "three"]
+				}
+				""";
+
+		// Create a toolContext
+		ToolContext toolContext = new ToolContext(Map.of("foo", "bar"));
+
+		// Call the tool and verify
+		assertThatThrownBy(() -> callback.call(toolInput, toolContext)).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("ToolContext is required by the method as an argument");
+	}
+
 	/**
 	 * Test class with methods that use generic types.
 	 */
@@ -154,6 +226,11 @@ public String processListOfMaps(List<Map<String, Integer>> listOfMaps) {
 			return listOfMaps.size() + " maps processed: " + listOfMaps;
 		}
 
+		public String processStringListInToolContext(ToolContext toolContext) {
+			Map<String, Object> context = toolContext.getContext();
+			return context.size() + " entries processed " + context;
+		}
+
 	}
 
 }

From bb52db5e7029bacced252711df5868dbca1ad93b Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Wed, 11 Jun 2025 10:58:47 +0100
Subject: [PATCH 064/165] Fix OpenAiChatClientMethodInvokingFunctionCallbackIT

 - Update test to check if the exception is thrown when using the method without toolcontext as method argument but with explicit toolcontext values

 - Add test to verify that exception is not thrown when toolcontext is not set even with the callback method with the toolcontext argument

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 ...lientMethodInvokingFunctionCallbackIT.java | 31 +++++++++++++++++--
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java
index 2a766a6b055..c405aacadfb 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java
@@ -37,6 +37,7 @@
 import org.springframework.util.ReflectionUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
 
 @SpringBootTest(classes = OpenAiTestConfiguration.class)
@@ -165,12 +166,12 @@ void methodGetWeatherToolContext() {
 	}
 
 	@Test
-	void methodGetWeatherToolContextButMissingContextArgument() {
+	void methodGetWeatherNonStaticButWithToolContext() {
 
 		TestFunctionClass targetObject = new TestFunctionClass();
 
-		var toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
-				Unit.class, ToolContext.class);
+		var toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
+				Unit.class);
 
 		// @formatter:off
 		assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
@@ -182,6 +183,7 @@ void methodGetWeatherToolContextButMissingContextArgument() {
 					.toolMethod(toolMethod)
 					.toolObject(targetObject)
 					.build())
+				.toolContext(Map.of("tool-context", "value"))
 				.call()
 				.content())
 				.isInstanceOf(IllegalArgumentException.class)
@@ -189,6 +191,29 @@ void methodGetWeatherToolContextButMissingContextArgument() {
 		// @formatter:on
 	}
 
+	@Test
+	void methodGetWeatherToolContextButWithoutToolContext() {
+
+		TestFunctionClass targetObject = new TestFunctionClass();
+
+		var toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
+				Unit.class, ToolContext.class);
+
+		// @formatter:off
+		assertThatCode(() ->ChatClient.create(this.chatModel).prompt()
+				.user("What's the weather like in San Francisco, Tokyo, and Paris?  Use Celsius.")
+				.toolCallbacks(MethodToolCallback.builder()
+						.toolDefinition(ToolDefinitions.builder(toolMethod)
+								.description("Get the weather in location")
+								.build())
+						.toolMethod(toolMethod)
+						.toolObject(targetObject)
+						.build())
+				.call()
+				.content()).doesNotThrowAnyException();
+		// @formatter:on
+	}
+
 	@Test
 	void methodNoParameters() {
 

From 0a1d326aca2812ba196565973cca228869ac408d Mon Sep 17 00:00:00 2001
From: Gareth Evans <gareth@bryncynfelin.co.uk>
Date: Fri, 6 Jun 2025 12:15:45 +0100
Subject: [PATCH 065/165] fix: bedrock titan embeddings should return usage

Signed-off-by: Gareth Evans <gareth@bryncynfelin.co.uk>
(cherry picked from commit c4e434a5e5787c7df31b127e371af62c6d36cb31)
---
 .../titan/BedrockTitanEmbeddingModel.java        | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java
index ca71e6fcc4d..c32be005c0d 100644
--- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java
+++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java
@@ -28,12 +28,14 @@
 import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi;
 import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingRequest;
 import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingResponse;
+import org.springframework.ai.chat.metadata.DefaultUsage;
 import org.springframework.ai.document.Document;
 import org.springframework.ai.embedding.AbstractEmbeddingModel;
 import org.springframework.ai.embedding.Embedding;
 import org.springframework.ai.embedding.EmbeddingOptions;
 import org.springframework.ai.embedding.EmbeddingRequest;
 import org.springframework.ai.embedding.EmbeddingResponse;
+import org.springframework.ai.embedding.EmbeddingResponseMetadata;
 import org.springframework.util.Assert;
 
 /**
@@ -89,6 +91,7 @@ public EmbeddingResponse call(EmbeddingRequest request) {
 
 		List<Embedding> embeddings = new ArrayList<>();
 		var indexCounter = new AtomicInteger(0);
+		int tokenUsage = 0;
 
 		for (String inputContent : request.getInstructions()) {
 			var apiRequest = createTitanEmbeddingRequest(inputContent, request.getOptions());
@@ -111,6 +114,10 @@ public EmbeddingResponse call(EmbeddingRequest request) {
 				}
 
 				embeddings.add(new Embedding(response.embedding(), indexCounter.getAndIncrement()));
+
+				if (response.inputTextTokenCount() != null) {
+					tokenUsage += response.inputTextTokenCount();
+				}
 			}
 			catch (Exception ex) {
 				logger.error("Titan API embedding failed for input at index {}: {}", indexCounter.get(),
@@ -120,7 +127,10 @@ public EmbeddingResponse call(EmbeddingRequest request) {
 			}
 		}
 
-		return new EmbeddingResponse(embeddings);
+		EmbeddingResponseMetadata embeddingResponseMetadata = new EmbeddingResponseMetadata("",
+				getDefaultUsage(tokenUsage));
+
+		return new EmbeddingResponse(embeddings, embeddingResponseMetadata);
 	}
 
 	private TitanEmbeddingRequest createTitanEmbeddingRequest(String inputContent, EmbeddingOptions requestOptions) {
@@ -155,6 +165,10 @@ private String summarizeInput(String input) {
 		return input.length() > 100 ? input.substring(0, 100) + "..." : input;
 	}
 
+	private DefaultUsage getDefaultUsage(int tokens) {
+		return new DefaultUsage(tokens, 0);
+	}
+
 	public enum InputType {
 
 		TEXT, IMAGE

From 379901bde105417d3c7c9bc209524a8f51e9252b Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <apappascs@gmail.com>
Date: Fri, 18 Apr 2025 12:24:16 +0200
Subject: [PATCH 066/165] feat(anthropic): Add support for streaming thinking
 events

Add necessary types and update stream processing to handle Anthropic's 'thinking' content blocks and deltas in streaming responses. This resolves an issue where an IllegalArgumentException was thrown for unhandled thinking event types.
format

Added docs

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
---
 models/spring-ai-anthropic/README.md          |   2 +-
 .../ai/anthropic/api/AnthropicApi.java        |  20 ++-
 .../ai/anthropic/api/StreamHelper.java        |  89 +++++++---
 .../ai/anthropic/AnthropicChatModelIT.java    |  37 +++-
 .../ai/anthropic/api/AnthropicApiIT.java      | 167 ++++++++++++++++--
 .../client/AnthropicChatClientIT.java         |   6 +-
 .../src/main/antora/modules/ROOT/nav.adoc     |   2 +-
 .../ROOT/pages/api/chat/anthropic-chat.adoc   | 164 ++++++++++++++++-
 8 files changed, 433 insertions(+), 54 deletions(-)

diff --git a/models/spring-ai-anthropic/README.md b/models/spring-ai-anthropic/README.md
index a23e89d089f..5c62eb9c47a 100644
--- a/models/spring-ai-anthropic/README.md
+++ b/models/spring-ai-anthropic/README.md
@@ -1,2 +1,2 @@
-[Anthropic 3 Chat Documentation](https://docs.spring.io/spring-ai/reference/api/chat/anthropic-chat.html)
+[Anthropic Chat Documentation](https://docs.spring.io/spring-ai/reference/api/chat/anthropic-chat.html)
 
diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
index e0a0a1bbf5f..e3485574948 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
@@ -1124,8 +1124,11 @@ public record ContentBlockStartEvent(
 
 		@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type",
 				visible = true)
-		@JsonSubTypes({ @JsonSubTypes.Type(value = ContentBlockToolUse.class, name = "tool_use"),
-				@JsonSubTypes.Type(value = ContentBlockText.class, name = "text") })
+		@JsonSubTypes({
+				@JsonSubTypes.Type(value = ContentBlockToolUse.class, name = "tool_use"),
+				@JsonSubTypes.Type(value = ContentBlockText.class, name = "text"),
+				@JsonSubTypes.Type(value = ContentBlockThinking.class, name = "thinking")
+		})
 		public interface ContentBlockBody {
 			String type();
 		}
@@ -1157,6 +1160,19 @@ public record ContentBlockText(
 			@JsonProperty("type") String type,
 			@JsonProperty("text") String text) implements ContentBlockBody {
 		}
+
+		/**
+		 * Thinking content block.
+		 * @param type The content block type.
+		 * @param thinking The thinking content.
+		 */
+		@JsonInclude(Include.NON_NULL)
+		public record ContentBlockThinking(
+			@JsonProperty("type") String type,
+			@JsonProperty("thinking") String thinking,
+			@JsonProperty("signature") String signature) implements ContentBlockBody {
+		}
+		
 	}
 	// @formatter:on
 
diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java
index e08a9669085..980c3342afe 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java
@@ -26,9 +26,12 @@
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent.ContentBlockDeltaJson;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent.ContentBlockDeltaText;
+import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent.ContentBlockDeltaThinking;
+import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockDeltaEvent.ContentBlockDeltaSignature;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent.ContentBlockText;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent.ContentBlockToolUse;
+import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlockStartEvent.ContentBlockThinking;
 import org.springframework.ai.anthropic.api.AnthropicApi.EventType;
 import org.springframework.ai.anthropic.api.AnthropicApi.MessageDeltaEvent;
 import org.springframework.ai.anthropic.api.AnthropicApi.MessageStartEvent;
@@ -36,19 +39,19 @@
 import org.springframework.ai.anthropic.api.AnthropicApi.StreamEvent;
 import org.springframework.ai.anthropic.api.AnthropicApi.ToolUseAggregationEvent;
 import org.springframework.ai.anthropic.api.AnthropicApi.Usage;
-import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
 /**
- * Helper class to support streaming function calling.
+ * Helper class to support streaming function calling and thinking events.
  * <p>
  * It can merge the streamed {@link StreamEvent} chunks in case of function calling
- * message.
+ * message. It passes through other events like text, thinking, and signature deltas.
  *
  * @author Mariusz Bernacki
  * @author Christian Tzolov
  * @author Jihoon Kim
+ * @author Alexandros Pappas
  * @since 1.0.0
  */
 public class StreamHelper {
@@ -61,13 +64,16 @@ public boolean isToolUseStart(StreamEvent event) {
 	}
 
 	public boolean isToolUseFinish(StreamEvent event) {
-
-		if (event == null || event.type() == null || event.type() != EventType.CONTENT_BLOCK_STOP) {
-			return false;
-		}
-		return true;
+		// Tool use streaming sequence ends with a CONTENT_BLOCK_STOP event.
+		// The logic relies on the state machine (isInsideTool flag) managed in
+		// chatCompletionStream to know if this stop event corresponds to a tool use.
+		return event != null && event.type() != null && event.type() == EventType.CONTENT_BLOCK_STOP;
 	}
 
+	/**
+	 * Merge the tool‑use related streaming events into one aggregate event so that the
+	 * upper layers see a single ContentBlock with the full JSON input.
+	 */
 	public StreamEvent mergeToolUseEvents(StreamEvent previousEvent, StreamEvent event) {
 
 		ToolUseAggregationEvent eventAggregator = (ToolUseAggregationEvent) previousEvent;
@@ -76,8 +82,7 @@ public StreamEvent mergeToolUseEvents(StreamEvent previousEvent, StreamEvent eve
 			ContentBlockStartEvent contentBlockStart = (ContentBlockStartEvent) event;
 
 			if (ContentBlock.Type.TOOL_USE.getValue().equals(contentBlockStart.contentBlock().type())) {
-				ContentBlockStartEvent.ContentBlockToolUse cbToolUse = (ContentBlockToolUse) contentBlockStart
-					.contentBlock();
+				ContentBlockToolUse cbToolUse = (ContentBlockToolUse) contentBlockStart.contentBlock();
 
 				return eventAggregator.withIndex(contentBlockStart.index())
 					.withId(cbToolUse.id())
@@ -102,6 +107,14 @@ else if (event.type() == EventType.CONTENT_BLOCK_STOP) {
 		return event;
 	}
 
+	/**
+	 * Converts a raw {@link StreamEvent} potentially containing tool use aggregates or
+	 * other block types (text, thinking) into a {@link ChatCompletionResponse} chunk.
+	 * @param event The incoming StreamEvent.
+	 * @param contentBlockReference Holds the state of the response being built across
+	 * multiple events.
+	 * @return A ChatCompletionResponse representing the processed chunk.
+	 */
 	public ChatCompletionResponse eventToChatCompletionResponse(StreamEvent event,
 			AtomicReference<ChatCompletionResponseBuilder> contentBlockReference) {
 
@@ -135,28 +148,41 @@ else if (event.type().equals(EventType.TOOL_USE_AGGREGATE)) {
 		else if (event.type().equals(EventType.CONTENT_BLOCK_START)) {
 			ContentBlockStartEvent contentBlockStartEvent = (ContentBlockStartEvent) event;
 
-			Assert.isTrue(contentBlockStartEvent.contentBlock().type().equals("text"),
-					"The json content block should have been aggregated. Unsupported content block type: "
-							+ contentBlockStartEvent.contentBlock().type());
-
-			ContentBlockText contentBlockText = (ContentBlockText) contentBlockStartEvent.contentBlock();
-			ContentBlock contentBlock = new ContentBlock(Type.TEXT, null, contentBlockText.text(),
-					contentBlockStartEvent.index());
-			contentBlockReference.get().withType(event.type().name()).withContent(List.of(contentBlock));
+			if (contentBlockStartEvent.contentBlock() instanceof ContentBlockText textBlock) {
+				ContentBlock cb = new ContentBlock(Type.TEXT, null, textBlock.text(), contentBlockStartEvent.index());
+				contentBlockReference.get().withType(event.type().name()).withContent(List.of(cb));
+			}
+			else if (contentBlockStartEvent.contentBlock() instanceof ContentBlockThinking thinkingBlock) {
+				ContentBlock cb = new ContentBlock(Type.THINKING, null, null, contentBlockStartEvent.index(), null,
+						null, null, null, null, null, thinkingBlock.thinking(), null);
+				contentBlockReference.get().withType(event.type().name()).withContent(List.of(cb));
+			}
+			else {
+				throw new IllegalArgumentException(
+						"Unsupported content block type: " + contentBlockStartEvent.contentBlock().type());
+			}
 		}
 		else if (event.type().equals(EventType.CONTENT_BLOCK_DELTA)) {
-
 			ContentBlockDeltaEvent contentBlockDeltaEvent = (ContentBlockDeltaEvent) event;
 
-			Assert.isTrue(contentBlockDeltaEvent.delta().type().equals("text_delta"),
-					"The json content block delta should have been aggregated. Unsupported content block type: "
-							+ contentBlockDeltaEvent.delta().type());
-
-			ContentBlockDeltaText deltaTxt = (ContentBlockDeltaText) contentBlockDeltaEvent.delta();
-
-			var contentBlock = new ContentBlock(Type.TEXT_DELTA, null, deltaTxt.text(), contentBlockDeltaEvent.index());
-
-			contentBlockReference.get().withType(event.type().name()).withContent(List.of(contentBlock));
+			if (contentBlockDeltaEvent.delta() instanceof ContentBlockDeltaText txt) {
+				ContentBlock cb = new ContentBlock(Type.TEXT_DELTA, null, txt.text(), contentBlockDeltaEvent.index());
+				contentBlockReference.get().withType(event.type().name()).withContent(List.of(cb));
+			}
+			else if (contentBlockDeltaEvent.delta() instanceof ContentBlockDeltaThinking thinking) {
+				ContentBlock cb = new ContentBlock(Type.THINKING_DELTA, null, null, contentBlockDeltaEvent.index(),
+						null, null, null, null, null, null, thinking.thinking(), null);
+				contentBlockReference.get().withType(event.type().name()).withContent(List.of(cb));
+			}
+			else if (contentBlockDeltaEvent.delta() instanceof ContentBlockDeltaSignature sig) {
+				ContentBlock cb = new ContentBlock(Type.SIGNATURE_DELTA, null, null, contentBlockDeltaEvent.index(),
+						null, null, null, null, null, sig.signature(), null, null);
+				contentBlockReference.get().withType(event.type().name()).withContent(List.of(cb));
+			}
+			else {
+				throw new IllegalArgumentException(
+						"Unsupported content block delta type: " + contentBlockDeltaEvent.delta().type());
+			}
 		}
 		else if (event.type().equals(EventType.MESSAGE_DELTA)) {
 
@@ -173,7 +199,7 @@ else if (event.type().equals(EventType.MESSAGE_DELTA)) {
 			}
 
 			if (messageDeltaEvent.usage() != null) {
-				var totalUsage = new Usage(contentBlockReference.get().usage.inputTokens(),
+				Usage totalUsage = new Usage(contentBlockReference.get().usage.inputTokens(),
 						messageDeltaEvent.usage().outputTokens());
 				contentBlockReference.get().withUsage(totalUsage);
 			}
@@ -189,12 +215,17 @@ else if (event.type().equals(EventType.MESSAGE_STOP)) {
 				.withStopSequence(null);
 		}
 		else {
+			// Any other event types that should propagate upwards without content
 			contentBlockReference.get().withType(event.type().name()).withContent(List.of());
 		}
 
 		return contentBlockReference.get().build();
 	}
 
+	/**
+	 * Builder for {@link ChatCompletionResponse}. Used internally by {@link StreamHelper}
+	 * to aggregate stream events.
+	 */
 	public static class ChatCompletionResponseBuilder {
 
 		private String type;
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelIT.java
index cc773efb324..6570d5ee6a6 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelIT.java
@@ -88,8 +88,7 @@ private static void validateChatResponseMetadata(ChatResponse response, String m
 	}
 
 	@ParameterizedTest(name = "{0} : {displayName} ")
-	@ValueSource(strings = { "claude-3-7-sonnet-latest", "claude-3-5-sonnet-latest", "claude-3-5-haiku-latest",
-			"claude-3-opus-latest" })
+	@ValueSource(strings = { "claude-3-7-sonnet-latest" })
 	void roleTest(String modelName) {
 		UserMessage userMessage = new UserMessage(
 				"Tell me about 3 famous pirates from the Golden Age of Piracy and why they did.");
@@ -302,7 +301,7 @@ void functionCallTest() {
 		assertThat(generation.getOutput().getText()).contains("30", "10", "15");
 		assertThat(response.getMetadata()).isNotNull();
 		assertThat(response.getMetadata().getUsage()).isNotNull();
-		assertThat(response.getMetadata().getUsage().getTotalTokens()).isLessThan(4000).isGreaterThan(1800);
+		assertThat(response.getMetadata().getUsage().getTotalTokens()).isLessThan(4000).isGreaterThan(100);
 	}
 
 	@Test
@@ -429,6 +428,38 @@ else if (message.getMetadata().containsKey("data")) { // redacted thinking
 		}
 	}
 
+	@Test
+	void thinkingWithStreamingTest() {
+		UserMessage userMessage = new UserMessage(
+				"Are there an infinite number of prime numbers such that n mod 4 == 3?");
+
+		var promptOptions = AnthropicChatOptions.builder()
+			.model(AnthropicApi.ChatModel.CLAUDE_3_7_SONNET.getName())
+			.temperature(1.0) // Temperature should be set to 1 when thinking is enabled
+			.maxTokens(8192)
+			.thinking(AnthropicApi.ThinkingType.ENABLED, 2048) // Must be ≥1024 && <
+																// max_tokens
+			.build();
+
+		Flux<ChatResponse> responseFlux = this.streamingChatModel
+			.stream(new Prompt(List.of(userMessage), promptOptions));
+
+		String content = responseFlux.collectList()
+			.block()
+			.stream()
+			.map(ChatResponse::getResults)
+			.flatMap(List::stream)
+			.map(Generation::getOutput)
+			.map(AssistantMessage::getText)
+			.filter(text -> text != null && !text.isBlank())
+			.collect(Collectors.joining());
+
+		logger.info("Response: {}", content);
+
+		assertThat(content).isNotBlank();
+		assertThat(content).contains("prime numbers");
+	}
+
 	@Test
 	void testToolUseContentBlock() {
 		UserMessage userMessage = new UserMessage(
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiIT.java
index 35cf443866c..c78386fb7ce 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiIT.java
@@ -18,18 +18,24 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import reactor.core.publisher.Flux;
 
 import org.springframework.ai.anthropic.api.AnthropicApi.AnthropicMessage;
 import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest;
 import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
+import org.springframework.ai.anthropic.api.AnthropicApi.EventType;
 import org.springframework.ai.anthropic.api.AnthropicApi.Role;
 import org.springframework.ai.model.ModelOptionsUtils;
 import org.springframework.http.ResponseEntity;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -42,6 +48,8 @@
 @EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
 public class AnthropicApiIT {
 
+	private static final Logger logger = LoggerFactory.getLogger(AnthropicApiIT.class);
+
 	AnthropicApi anthropicApi = AnthropicApi.builder().apiKey(System.getenv("ANTHROPIC_API_KEY")).build();
 
 	List<AnthropicApi.Tool> tools = List.of(new AnthropicApi.Tool("getCurrentWeather",
@@ -68,17 +76,26 @@ void chatCompletionEntity() {
 		AnthropicMessage chatCompletionMessage = new AnthropicMessage(List.of(new ContentBlock("Tell me a Joke?")),
 				Role.USER);
 		ResponseEntity<ChatCompletionResponse> response = this.anthropicApi
-			.chatCompletionEntity(new ChatCompletionRequest(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue(),
-					List.of(chatCompletionMessage), null, 100, 0.8, false));
-
-		System.out.println(response);
+			.chatCompletionEntity(ChatCompletionRequest.builder()
+				.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue())
+				.messages(List.of(chatCompletionMessage))
+				.maxTokens(100)
+				.temperature(0.8)
+				.stream(false)
+				.build());
+
+		logger.info("Non-Streaming Response: {}", response.getBody());
 		assertThat(response).isNotNull();
 		assertThat(response.getBody()).isNotNull();
+		assertThat(response.getBody().content()).isNotEmpty();
+		assertThat(response.getBody().content().get(0).text()).isNotBlank();
+		assertThat(response.getBody().stopReason()).isEqualTo("end_turn");
 	}
 
 	@Test
 	void chatCompletionWithThinking() {
-		AnthropicMessage chatCompletionMessage = new AnthropicMessage(List.of(new ContentBlock("Tell me a Joke?")),
+		AnthropicMessage chatCompletionMessage = new AnthropicMessage(
+				List.of(new ContentBlock("Are there an infinite number of prime numbers such that n mod 4 == 3?")),
 				Role.USER);
 
 		ChatCompletionRequest request = ChatCompletionRequest.builder()
@@ -93,20 +110,31 @@ void chatCompletionWithThinking() {
 
 		assertThat(response).isNotNull();
 		assertThat(response.getBody()).isNotNull();
+		assertThat(response.getBody().content()).isNotEmpty();
+
+		boolean foundThinkingBlock = false;
+		boolean foundTextBlock = false;
 
 		List<ContentBlock> content = response.getBody().content();
 		for (ContentBlock block : content) {
 			if (block.type() == ContentBlock.Type.THINKING) {
 				assertThat(block.thinking()).isNotBlank();
 				assertThat(block.signature()).isNotBlank();
+				foundThinkingBlock = true;
 			}
+			// Note: Redacted thinking might occur if budget is exceeded or other reasons.
 			if (block.type() == ContentBlock.Type.REDACTED_THINKING) {
 				assertThat(block.data()).isNotBlank();
 			}
 			if (block.type() == ContentBlock.Type.TEXT) {
 				assertThat(block.text()).isNotBlank();
+				foundTextBlock = true;
 			}
 		}
+
+		assertThat(foundThinkingBlock).isTrue();
+		assertThat(foundTextBlock).isTrue();
+		assertThat(response.getBody().stopReason()).isEqualTo("end_turn");
 	}
 
 	@Test
@@ -115,15 +143,125 @@ void chatCompletionStream() {
 		AnthropicMessage chatCompletionMessage = new AnthropicMessage(List.of(new ContentBlock("Tell me a Joke?")),
 				Role.USER);
 
-		Flux<ChatCompletionResponse> response = this.anthropicApi.chatCompletionStream(new ChatCompletionRequest(
-				AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue(), List.of(chatCompletionMessage), null, 100, 0.8, true));
+		Flux<ChatCompletionResponse> response = this.anthropicApi.chatCompletionStream(ChatCompletionRequest.builder()
+			.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue())
+			.messages(List.of(chatCompletionMessage))
+			.maxTokens(100)
+			.temperature(0.8)
+			.stream(true)
+			.build());
 
 		assertThat(response).isNotNull();
 
-		List<ChatCompletionResponse> bla = response.collectList().block();
-		assertThat(bla).isNotNull();
+		List<ChatCompletionResponse> results = response.collectList().block();
+		assertThat(results).isNotNull().isNotEmpty();
+
+		results.forEach(chunk -> logger.info("Streaming Chunk: {}", chunk));
+
+		// Verify the stream contains actual text content deltas
+		String aggregatedText = results.stream()
+			.filter(r -> !CollectionUtils.isEmpty(r.content()))
+			.flatMap(r -> r.content().stream())
+			.filter(cb -> cb.type() == ContentBlock.Type.TEXT_DELTA)
+			.map(ContentBlock::text)
+			.collect(Collectors.joining());
+		assertThat(aggregatedText).isNotBlank();
+
+		// Verify the final state
+		ChatCompletionResponse lastMeaningfulResponse = results.stream()
+			.filter(r -> StringUtils.hasText(r.stopReason()))
+			.reduce((first, second) -> second)
+			.orElse(results.get(results.size() - 1)); // Fallback to very last if no stop
+
+		// StopReason found earlier
+		assertThat(lastMeaningfulResponse.stopReason()).isEqualTo("end_turn");
+		assertThat(lastMeaningfulResponse.usage()).isNotNull();
+		assertThat(lastMeaningfulResponse.usage().outputTokens()).isPositive();
+	}
 
-		bla.stream().forEach(r -> System.out.println(r));
+	@Test
+	void chatCompletionStreamWithThinking() {
+		AnthropicMessage chatCompletionMessage = new AnthropicMessage(
+				List.of(new ContentBlock("Are there an infinite number of prime numbers such that n mod 4 == 3?")),
+				Role.USER);
+
+		ChatCompletionRequest request = ChatCompletionRequest.builder()
+			.model(AnthropicApi.ChatModel.CLAUDE_3_7_SONNET.getValue())
+			.messages(List.of(chatCompletionMessage))
+			.maxTokens(2048)
+			.temperature(1.0)
+			.stream(true)
+			.thinking(new ChatCompletionRequest.ThinkingConfig(AnthropicApi.ThinkingType.ENABLED, 1024))
+			.build();
+
+		Flux<ChatCompletionResponse> responseFlux = this.anthropicApi.chatCompletionStream(request);
+
+		assertThat(responseFlux).isNotNull();
+
+		List<ChatCompletionResponse> results = responseFlux.collectList().block();
+		assertThat(results).isNotNull().isNotEmpty();
+
+		results.forEach(chunk -> logger.info("Streaming Thinking Chunk: {}", chunk));
+
+		// Verify MESSAGE_START event exists
+		assertThat(results.stream().anyMatch(r -> EventType.MESSAGE_START.name().equals(r.type()))).isTrue();
+		assertThat(results.get(0).id()).isNotBlank();
+		assertThat(results.get(0).role()).isEqualTo(Role.ASSISTANT);
+
+		// Verify presence of THINKING_DELTA content
+		boolean foundThinkingDelta = results.stream()
+			.filter(r -> !CollectionUtils.isEmpty(r.content()))
+			.flatMap(r -> r.content().stream())
+			.anyMatch(cb -> cb.type() == ContentBlock.Type.THINKING_DELTA && StringUtils.hasText(cb.thinking()));
+		assertThat(foundThinkingDelta).as("Should find THINKING_DELTA content").isTrue();
+
+		// Verify presence of SIGNATURE_DELTA content
+		boolean foundSignatureDelta = results.stream()
+			.filter(r -> !CollectionUtils.isEmpty(r.content()))
+			.flatMap(r -> r.content().stream())
+			.anyMatch(cb -> cb.type() == ContentBlock.Type.SIGNATURE_DELTA && StringUtils.hasText(cb.signature()));
+		assertThat(foundSignatureDelta).as("Should find SIGNATURE_DELTA content").isTrue();
+
+		// Verify presence of TEXT_DELTA content (the actual answer)
+		boolean foundTextDelta = results.stream()
+			.filter(r -> !CollectionUtils.isEmpty(r.content()))
+			.flatMap(r -> r.content().stream())
+			.anyMatch(cb -> cb.type() == ContentBlock.Type.TEXT_DELTA && StringUtils.hasText(cb.text()));
+		assertThat(foundTextDelta).as("Should find TEXT_DELTA content").isTrue();
+
+		// Combine text deltas to check final answer structure
+		String aggregatedText = results.stream()
+			.filter(r -> !CollectionUtils.isEmpty(r.content()))
+			.flatMap(r -> r.content().stream())
+			.filter(cb -> cb.type() == ContentBlock.Type.TEXT_DELTA)
+			.map(ContentBlock::text)
+			.collect(Collectors.joining());
+		assertThat(aggregatedText).as("Aggregated text response should not be blank").isNotBlank();
+		logger.info("Aggregated Text from Stream: {}", aggregatedText);
+
+		// Verify the final state (stop reason and usage)
+		ChatCompletionResponse finalStateEvent = results.stream()
+			.filter(r -> StringUtils.hasText(r.stopReason()))
+			.reduce((first, second) -> second)
+			.orElse(null);
+
+		assertThat(finalStateEvent).as("Should find an event with stopReason").isNotNull();
+		assertThat(finalStateEvent.stopReason()).isEqualTo("end_turn");
+		assertThat(finalStateEvent.usage()).isNotNull();
+		assertThat(finalStateEvent.usage().outputTokens()).isPositive();
+		assertThat(finalStateEvent.usage().inputTokens()).isPositive();
+
+		// Verify presence of key event types
+		assertThat(results.stream().anyMatch(r -> EventType.CONTENT_BLOCK_START.name().equals(r.type())))
+			.as("Should find CONTENT_BLOCK_START event")
+			.isTrue();
+		assertThat(results.stream().anyMatch(r -> EventType.CONTENT_BLOCK_STOP.name().equals(r.type())))
+			.as("Should find CONTENT_BLOCK_STOP event")
+			.isTrue();
+		assertThat(results.stream()
+			.anyMatch(r -> EventType.MESSAGE_STOP.name().equals(r.type()) || StringUtils.hasText(r.stopReason())))
+			.as("Should find MESSAGE_STOP or MESSAGE_DELTA with stopReason")
+			.isTrue();
 	}
 
 	@Test
@@ -173,8 +311,13 @@ void chatCompletionStreamError() {
 				Role.USER);
 		AnthropicApi api = AnthropicApi.builder().apiKey("FAKE_KEY_FOR_ERROR_RESPONSE").build();
 
-		Flux<ChatCompletionResponse> response = api.chatCompletionStream(new ChatCompletionRequest(
-				AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue(), List.of(chatCompletionMessage), null, 100, 0.8, true));
+		Flux<ChatCompletionResponse> response = api.chatCompletionStream(ChatCompletionRequest.builder()
+			.model(AnthropicApi.ChatModel.CLAUDE_3_OPUS.getValue())
+			.messages(List.of(chatCompletionMessage))
+			.maxTokens(100)
+			.temperature(0.8)
+			.stream(true)
+			.build());
 
 		assertThat(response).isNotNull();
 
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
index 5bd91be7ebb..13882775d3f 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
@@ -211,7 +211,7 @@ void functionCallTest() {
 
 		// @formatter:off
 		String response = ChatClient.create(this.chatModel).prompt()
-				.user("What's the weather like in San Francisco, Tokyo, and Paris?  Use Celsius.")
+				.user("What's the weather like in San Francisco (California, USA), Tokyo (Japan), and Paris (France)? Use Celsius.")
 				.toolCallbacks(FunctionToolCallback.builder("getCurrentWeather", new MockWeatherService())
 					.inputType(MockWeatherService.Request.class)
 					.build())
@@ -284,7 +284,7 @@ void streamFunctionCallTest() {
 	}
 
 	@ParameterizedTest(name = "{0} : {displayName} ")
-	@ValueSource(strings = { "claude-3-opus-latest", "claude-3-5-sonnet-latest", "claude-3-7-sonnet-latest" })
+	@ValueSource(strings = { "claude-3-7-sonnet-latest", "claude-sonnet-4-0" })
 	void multiModalityEmbeddedImage(String modelName) throws IOException {
 
 		// @formatter:off
@@ -301,7 +301,7 @@ void multiModalityEmbeddedImage(String modelName) throws IOException {
 	}
 
 	@ParameterizedTest(name = "{0} : {displayName} ")
-	@ValueSource(strings = { "claude-3-opus-latest", "claude-3-5-sonnet-latest", "claude-3-7-sonnet-latest" })
+	@ValueSource(strings = { "claude-3-7-sonnet-latest", "claude-sonnet-4-0" })
 	void multiModalityImageUrl(String modelName) throws IOException {
 
 		// TODO: add url method that wrapps the checked exception.
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
index c5385df9376..563f251178e 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc
@@ -14,7 +14,7 @@
 *** xref:api/chatmodel.adoc[Chat Models]
 **** xref:api/chat/comparison.adoc[Chat Models Comparison]
 **** xref:api/chat/bedrock-converse.adoc[Amazon Bedrock Converse]
-**** xref:api/chat/anthropic-chat.adoc[Anthropic 3]
+**** xref:api/chat/anthropic-chat.adoc[Anthropic]
 **** xref:api/chat/azure-openai-chat.adoc[Azure OpenAI]
 **** xref:api/chat/deepseek-chat.adoc[DeepSeek]
 **** xref:api/chat/dmr-chat.adoc[Docker Model Runner]
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
index db982d4dc95..1cd82665072 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
@@ -1,4 +1,4 @@
-= Anthropic 3 Chat
+= Anthropic Chat
 
 link:https://www.anthropic.com/[Anthropic Claude] is a family of foundational AI models that can be used in a variety of applications.
 For developers and businesses, you can leverage the API access and build directly on top of link:https://www.anthropic.com/api[Anthropic's AI infrastructure].
@@ -191,6 +191,166 @@ ChatResponse response = chatModel.call(
 
 TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java[AnthropicChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
 
+== Thinking
+
+Anthropic Claude models support a "thinking" feature that allows the model to show its reasoning process before providing a final answer. This feature enables more transparent and detailed problem-solving, particularly for complex questions that require step-by-step reasoning.
+
+[NOTE]
+====
+*Supported Models*
+
+The thinking feature is supported by the following Claude models:
+
+* Claude 4 models (`claude-opus-4-20250514`, `claude-sonnet-4-20250514`)
+* Claude 3.7 Sonnet (`claude-3-7-sonnet-20250219`)
+
+*Model capabilities:*
+
+* *Claude 3.7 Sonnet*: Returns full thinking output. Behavior is consistent but does not support summarized or interleaved thinking.
+* *Claude 4 models*: Support summarized thinking, interleaved thinking, and enhanced tool integration.
+
+API request structure is the same across all supported models, but output behavior varies.
+====
+
+=== Thinking Configuration
+
+To enable thinking on any supported Claude model, include the following configuration in your request:
+
+==== Required Configuration
+
+1. **Add the `thinking` object**:
+- `"type": "enabled"`
+- `budget_tokens`: Token limit for reasoning (recommend starting at 1024)
+
+2. **Token budget rules**:
+- `budget_tokens` must typically be less than `max_tokens`
+- Claude may use fewer tokens than allocated
+- Larger budgets increase depth of reasoning but may impact latency
+- When using tool use with interleaved thinking (Claude 4 only), this constraint is relaxed, but not yet supported in Spring AI.
+
+==== Key Considerations
+
+* **Claude 3.7** returns full thinking content in the response
+* **Claude 4** returns a *summarized* version of the model's internal reasoning to reduce latency and protect sensitive content
+* **Thinking tokens are billable** as part of output tokens (even if not all are visible in response)
+* **Interleaved Thinking** is only available on Claude 4 models and requires the beta header `interleaved-thinking-2025-05-14`
+
+==== Tool Integration and Interleaved Thinking
+
+Claude 4 models support interleaved thinking with tool use, allowing the model to reason between tool calls.
+
+[NOTE]
+====
+The current Spring AI implementation supports basic thinking and tool use separately, but does not yet support interleaved thinking with tool use (where thinking continues across multiple tool calls).
+====
+
+For details on interleaved thinking with tool use, see the https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#extended-thinking-with-tool-use[Anthropic documentation].
+
+=== Non-streaming Example
+
+Here's how to enable thinking in a non-streaming request using the ChatClient API:
+
+[source,java]
+----
+ChatClient chatClient = ChatClient.create(chatModel);
+
+// For Claude 3.7 Sonnet - explicit thinking configuration required
+ChatResponse response = chatClient.prompt()
+    .options(AnthropicChatOptions.builder()
+        .model("claude-3-7-sonnet-latest")
+        .temperature(1.0)  // Temperature should be set to 1 when thinking is enabled
+        .maxTokens(8192)
+        .thinking(AnthropicApi.ThinkingType.ENABLED, 2048)  // Must be ≥1024 && < max_tokens
+        .build())
+    .user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
+    .call()
+    .chatResponse();
+
+// For Claude 4 models - thinking is enabled by default
+ChatResponse response4 = chatClient.prompt()
+    .options(AnthropicChatOptions.builder()
+        .model("claude-opus-4-0")
+        .maxTokens(8192)
+        // No explicit thinking configuration needed
+        .build())
+    .user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
+    .call()
+    .chatResponse();
+
+// Process the response which may contain thinking content
+for (Generation generation : response.getResults()) {
+    AssistantMessage message = generation.getOutput();
+    if (message.getText() != null) {
+        // Regular text response
+        System.out.println("Text response: " + message.getText());
+    }
+    else if (message.getMetadata().containsKey("signature")) {
+        // Thinking content
+        System.out.println("Thinking: " + message.getMetadata().get("thinking"));
+        System.out.println("Signature: " + message.getMetadata().get("signature"));
+    }
+}
+----
+
+=== Streaming Example
+
+You can also use thinking with streaming responses:
+
+[source,java]
+----
+ChatClient chatClient = ChatClient.create(chatModel);
+
+// For Claude 3.7 Sonnet - explicit thinking configuration
+Flux<ChatResponse> responseFlux = chatClient.prompt()
+    .options(AnthropicChatOptions.builder()
+        .model("claude-3-7-sonnet-latest")
+        .temperature(1.0)
+        .maxTokens(8192)
+        .thinking(AnthropicApi.ThinkingType.ENABLED, 2048)
+        .build())
+    .user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
+    .stream();
+
+// For Claude 4 models - thinking is enabled by default
+Flux<ChatResponse> responseFlux4 = chatClient.prompt()
+    .options(AnthropicChatOptions.builder()
+        .model("claude-opus-4-0")
+        .maxTokens(8192)
+        // No explicit thinking configuration needed
+        .build())
+    .user("Are there an infinite number of prime numbers such that n mod 4 == 3?")
+    .stream();
+
+// For streaming, you might want to collect just the text responses
+String textContent = responseFlux.collectList()
+    .block()
+    .stream()
+    .map(ChatResponse::getResults)
+    .flatMap(List::stream)
+    .map(Generation::getOutput)
+    .map(AssistantMessage::getText)
+    .filter(text -> text != null && !text.isBlank())
+    .collect(Collectors.joining());
+----
+
+=== Tool Use Integration
+
+Claude 4 models integrate thinking and tool use capabilities:
+
+* *Claude 3.7 Sonnet*: Supports both thinking and tool use, but they operate separately and require more explicit configuration
+* *Claude 4 models*: Natively interleave thinking and tool use, providing deeper reasoning during tool interactions
+
+=== Benefits of Using Thinking
+
+The thinking feature provides several benefits:
+
+1. **Transparency**: See the model's reasoning process and how it arrived at its conclusion
+2. **Debugging**: Identify where the model might be making logical errors
+3. **Education**: Use the step-by-step reasoning as a teaching tool
+4. **Complex Problem Solving**: Better results on math, logic, and reasoning tasks
+
+Note that enabling thinking requires a higher token budget, as the thinking process itself consumes tokens from your allocation.
+
 == Tool/Function Calling
 
 You can register custom Java Tools with the `AnthropicChatModel` and have the Anthropic Claude model intelligently choose to output a JSON object containing arguments to call one or many of the registered functions.
@@ -378,5 +538,3 @@ Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-
 
 === Low-level API Examples
 * The link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/chat/api/AnthropicApiIT.java[AnthropicApiIT.java] test provides some general examples how to use the lightweight library.
-
-

From 8b9d93f852651d177de4c93488dc468a7635c33f Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 29 May 2025 17:31:45 +0100
Subject: [PATCH 067/165] Upgrade Spring Boot to 3.5.0

 - Update Spring Boot version to 3.5.0
 - Enforce elasticsearch-java client to use the explicit version 8.18.1
      - This is because of the incompatibility between Spring Boot 3.4.x and 3.5.0 in using the elastic search java client versions 8.15.1 vs 8.18.1

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 pom.xml                                                       | 3 ++-
 vector-stores/spring-ai-elasticsearch-store/pom.xml           | 1 +
 .../vectorstore/elasticsearch/ElasticsearchVectorStore.java   | 4 +++-
 3 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index e49c082fc64..bdf031ccc1b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -253,7 +253,7 @@
 		<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
 
 		<!-- production dependencies -->
-		<spring-boot.version>3.4.5</spring-boot.version>
+		<spring-boot.version>3.5.0</spring-boot.version>
 		<ST4.version>4.3.4</ST4.version>
 		<azure-open-ai-client.version>1.0.0-beta.16</azure-open-ai-client.version>
 		<jtokkit.version>1.1.0</jtokkit.version>
@@ -296,6 +296,7 @@
 		<azure-identity.version>1.15.4</azure-identity.version>
 		<azure-search.version>11.7.6</azure-search.version>
 		<azure-cosmos.version>5.22.0</azure-cosmos.version>
+		<elasticsearch-java.version>8.18.1</elasticsearch-java.version>
 		<weaviate-client.version>5.2.0</weaviate-client.version>
 		<qdrant.version>1.13.0</qdrant.version>
 		<typesense.version>1.3.0</typesense.version>
diff --git a/vector-stores/spring-ai-elasticsearch-store/pom.xml b/vector-stores/spring-ai-elasticsearch-store/pom.xml
index a75b4ad5518..c58f3f55ea6 100644
--- a/vector-stores/spring-ai-elasticsearch-store/pom.xml
+++ b/vector-stores/spring-ai-elasticsearch-store/pom.xml
@@ -53,6 +53,7 @@
 		<dependency>
 			<groupId>co.elastic.clients</groupId>
 			<artifactId>elasticsearch-java</artifactId>
+			<version>${elasticsearch-java.version}</version>
 		</dependency>
 
 		<!-- TESTING -->
diff --git a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
index 4a0ceff3dbf..f58479b9cae 100644
--- a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
+++ b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
@@ -24,6 +24,7 @@
 import java.util.stream.Collectors;
 
 import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.mapping.DenseVectorSimilarity;
 import co.elastic.clients.elasticsearch.core.BulkRequest;
 import co.elastic.clients.elasticsearch.core.BulkResponse;
 import co.elastic.clients.elasticsearch.core.SearchResponse;
@@ -329,7 +330,8 @@ private void createIndexMapping() {
 			this.elasticsearchClient.indices()
 				.create(cr -> cr.index(this.options.getIndexName())
 					.mappings(map -> map.properties(this.options.getEmbeddingFieldName(),
-							p -> p.denseVector(dv -> dv.similarity(this.options.getSimilarity().toString())
+							p -> p.denseVector(dv -> dv
+								.similarity(DenseVectorSimilarity.valueOf(this.options.getSimilarity().toString()))
 								.dims(this.options.getDimensions())))));
 		}
 		catch (IOException e) {

From c9ec39e370702ba077ec268e3973cb672f2c5fc7 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 12 Jun 2025 11:41:18 +0100
Subject: [PATCH 068/165] Fix ElasticsearchVectorStore to parse similarity
 option correctly

 - Check for valid DenseVectorSimilarity by using DenseVectorSimilarity's jsonValue with SimilarityFunction string
 - Update elasticsearch image elasticsearch:8.18.1

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 .../ElasticsearchVectorStore.java              | 18 ++++++++++++++----
 .../elasticsearch/ElasticsearchImage.java      |  3 +--
 2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
index f58479b9cae..06f0ba38a2d 100644
--- a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
+++ b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java
@@ -329,16 +329,26 @@ private void createIndexMapping() {
 		try {
 			this.elasticsearchClient.indices()
 				.create(cr -> cr.index(this.options.getIndexName())
-					.mappings(map -> map.properties(this.options.getEmbeddingFieldName(),
-							p -> p.denseVector(dv -> dv
-								.similarity(DenseVectorSimilarity.valueOf(this.options.getSimilarity().toString()))
-								.dims(this.options.getDimensions())))));
+					.mappings(
+							map -> map.properties(this.options.getEmbeddingFieldName(),
+									p -> p.denseVector(dv -> dv
+										.similarity(parseSimilarity(this.options.getSimilarity().toString()))
+										.dims(this.options.getDimensions())))));
 		}
 		catch (IOException e) {
 			throw new RuntimeException(e);
 		}
 	}
 
+	private DenseVectorSimilarity parseSimilarity(String similarity) {
+		for (DenseVectorSimilarity sim : DenseVectorSimilarity.values()) {
+			if (sim.jsonValue().equalsIgnoreCase(similarity)) {
+				return sim;
+			}
+		}
+		throw new IllegalArgumentException("Unsupported similarity: " + similarity);
+	}
+
 	@Override
 	public void afterPropertiesSet() {
 		if (!this.initializeSchema) {
diff --git a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchImage.java b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchImage.java
index 4af1decdc7c..a4f82c04dcb 100644
--- a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchImage.java
+++ b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchImage.java
@@ -23,8 +23,7 @@
  */
 public final class ElasticsearchImage {
 
-	public static final DockerImageName DEFAULT_IMAGE = DockerImageName
-		.parse("docker.elastic.co/elasticsearch/elasticsearch:8.16.1");
+	public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("elasticsearch:8.18.1");
 
 	private ElasticsearchImage() {
 

From 50f8fa7625a0bebe1f579c3e44ee250e74e4f316 Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <6052803+apappascs@users.noreply.github.com>
Date: Thu, 12 Jun 2025 16:57:57 +0200
Subject: [PATCH 069/165] feat: Add new Mistral AI chat models (#3523)

Adds MAGISTRAL_MEDIUM, MISTRAL_MEDIUM, MAGISTRAL_SMALL, and DEVSTRAL_SMALL to the available chat models.

Fixes: #3523

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
(cherry picked from commit 6ef15bdd7408396eebcc762e0f1943edc385a5ba)
---
 .../org/springframework/ai/mistralai/api/MistralAiApi.java    | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java
index 82937b2b957..c6bce7cae77 100644
--- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java
+++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java
@@ -280,12 +280,16 @@ public enum ChatModel implements ChatModelDescription {
 
 		// @formatter:off
 		// Premier Models
+		MAGISTRAL_MEDIUM("magistral-medium-latest"),
+		MISTRAL_MEDIUM("mistral-medium-latest"),
 		CODESTRAL("codestral-latest"),
 		LARGE("mistral-large-latest"),
 		PIXTRAL_LARGE("pixtral-large-latest"),
 		MINISTRAL_3B_LATEST("ministral-3b-latest"),
 		MINISTRAL_8B_LATEST("ministral-8b-latest"),
 		// Free Models
+		MAGISTRAL_SMALL("magistral-small-latest"),
+		DEVSTRAL_SMALL("devstral-small-latest"),
 		SMALL("mistral-small-latest"),
 		PIXTRAL("pixtral-12b-2409"),
 		// Free Models - Research

From f89530c3146db5635280e530b147a2812e481d76 Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <alexandros.pappas@yiluhub.com>
Date: Mon, 2 Dec 2024 16:10:36 +0100
Subject: [PATCH 070/165] feat(ollama): add retry template integration to
 OllamaChatModel

* Update tests that are supposed to fail to not use retry
* Upgrade ot use Ollama 0.6.7

Signed-off-by: Alexandros Pappas <alexandros.pappas@yiluhub.com>
---
 .../pom.xml                                   |   8 +
 .../OllamaChatAutoConfigurationTests.java     |   6 +-
 ...OllamaEmbeddingAutoConfigurationTests.java |   7 +-
 .../ai/ollama/OllamaChatModel.java            |  29 +++-
 .../ai/ollama/api/OllamaApi.java              |   7 +
 .../OllamaChatModelFunctionCallingIT.java     |   2 +
 .../ai/ollama/OllamaChatModelIT.java          |   5 +
 .../ollama/OllamaChatModelMultimodalIT.java   |  21 +++
 .../ollama/OllamaChatModelObservationIT.java  |   8 +-
 .../ai/ollama/OllamaChatModelTests.java       |   2 +
 .../ai/ollama/OllamaChatRequestTests.java     |   4 +
 .../ai/ollama/OllamaImage.java                |   2 +-
 .../ai/ollama/OllamaRetryTests.java           |  97 +++++++++++
 .../ollama/OllamaAutoConfiguration.java       | 153 ++++++++++++++++++
 14 files changed, 340 insertions(+), 11 deletions(-)
 create mode 100644 models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java
 create mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java

diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
index 3835b49709f..d2751f5c7a0 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
@@ -34,6 +34,14 @@
 		</dependency>
 
 		<!-- Spring AI auto configurations -->
+
+		<dependency>
+			<groupId>org.springframework.ai</groupId>
+			<artifactId>spring-ai-autoconfigure-retry</artifactId>
+			<version>${project.parent.version}</version>
+			<optional>true</optional>
+		</dependency>
+
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java
index de27c35c201..6e3fe39d870 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java
@@ -18,6 +18,7 @@
 
 import org.junit.jupiter.api.Test;
 
+import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -41,8 +42,9 @@ public void propertiesTest() {
 				"spring.ai.ollama.chat.options.topP=0.56",
 				"spring.ai.ollama.chat.options.topK=123")
 			// @formatter:on
-			.withConfiguration(
-					AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaChatAutoConfiguration.class))
+
+			.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
+					RestClientAutoConfiguration.class, OllamaChatAutoConfiguration.class))
 			.run(context -> {
 				var chatProperties = context.getBean(OllamaChatProperties.class);
 				var connectionProperties = context.getBean(OllamaConnectionProperties.class);
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java
index 6f24432780f..bc6f2a6d349 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java
@@ -18,6 +18,7 @@
 
 import org.junit.jupiter.api.Test;
 
+import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -26,6 +27,7 @@
 
 /**
  * @author Christian Tzolov
+ * @author Alexandros Pappas
  * @since 0.8.0
  */
 public class OllamaEmbeddingAutoConfigurationTests {
@@ -41,8 +43,9 @@ public void propertiesTest() {
 				"spring.ai.ollama.embedding.options.topK=13"
 				// @formatter:on
 		)
-			.withConfiguration(
-					AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class))
+
+			.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
+					RestClientAutoConfiguration.class, OllamaChatAutoConfiguration.class))
 			.run(context -> {
 				var embeddingProperties = context.getBean(OllamaEmbeddingProperties.class);
 				var connectionProperties = context.getBean(OllamaConnectionProperties.class);
diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java
index 8d22df6ddcc..44dc45347b6 100644
--- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java
+++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java
@@ -65,8 +65,13 @@
 import org.springframework.ai.ollama.management.ModelManagementOptions;
 import org.springframework.ai.ollama.management.OllamaModelManager;
 import org.springframework.ai.ollama.management.PullModelStrategy;
+
 import org.springframework.ai.tool.definition.ToolDefinition;
 import org.springframework.ai.util.json.JsonParser;
+
+import org.springframework.ai.retry.RetryUtils;
+import org.springframework.retry.support.RetryTemplate;
+
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
@@ -129,27 +134,32 @@ public class OllamaChatModel implements ChatModel {
 
 	private ChatModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION;
 
+	private final RetryTemplate retryTemplate;
+
 	public OllamaChatModel(OllamaApi ollamaApi, OllamaOptions defaultOptions, ToolCallingManager toolCallingManager,
 			ObservationRegistry observationRegistry, ModelManagementOptions modelManagementOptions) {
 		this(ollamaApi, defaultOptions, toolCallingManager, observationRegistry, modelManagementOptions,
-				new DefaultToolExecutionEligibilityPredicate());
+				new DefaultToolExecutionEligibilityPredicate(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
 	}
 
 	public OllamaChatModel(OllamaApi ollamaApi, OllamaOptions defaultOptions, ToolCallingManager toolCallingManager,
 			ObservationRegistry observationRegistry, ModelManagementOptions modelManagementOptions,
-			ToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate) {
+			ToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate, RetryTemplate retryTemplate) {
+
 		Assert.notNull(ollamaApi, "ollamaApi must not be null");
 		Assert.notNull(defaultOptions, "defaultOptions must not be null");
 		Assert.notNull(toolCallingManager, "toolCallingManager must not be null");
 		Assert.notNull(observationRegistry, "observationRegistry must not be null");
 		Assert.notNull(modelManagementOptions, "modelManagementOptions must not be null");
 		Assert.notNull(toolExecutionEligibilityPredicate, "toolExecutionEligibilityPredicate must not be null");
+		Assert.notNull(retryTemplate, "retryTemplate must not be null");
 		this.chatApi = ollamaApi;
 		this.defaultOptions = defaultOptions;
 		this.toolCallingManager = toolCallingManager;
 		this.observationRegistry = observationRegistry;
 		this.modelManager = new OllamaModelManager(this.chatApi, modelManagementOptions);
 		this.toolExecutionEligibilityPredicate = toolExecutionEligibilityPredicate;
+		this.retryTemplate = retryTemplate;
 		initializeModel(defaultOptions.getModel(), modelManagementOptions.pullModelStrategy());
 	}
 
@@ -237,7 +247,7 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon
 					this.observationRegistry)
 			.observe(() -> {
 
-				OllamaApi.ChatResponse ollamaResponse = this.chatApi.chat(request);
+				OllamaApi.ChatResponse ollamaResponse = this.retryTemplate.execute(ctx -> this.chatApi.chat(request));
 
 				List<AssistantMessage.ToolCall> toolCalls = ollamaResponse.message().toolCalls() == null ? List.of()
 						: ollamaResponse.message()
@@ -540,6 +550,8 @@ public static final class Builder {
 
 		private ModelManagementOptions modelManagementOptions = ModelManagementOptions.defaults();
 
+		private RetryTemplate retryTemplate = RetryUtils.DEFAULT_RETRY_TEMPLATE;
+
 		private Builder() {
 		}
 
@@ -574,13 +586,20 @@ public Builder modelManagementOptions(ModelManagementOptions modelManagementOpti
 			return this;
 		}
 
+		public Builder retryTemplate(RetryTemplate retryTemplate) {
+			this.retryTemplate = retryTemplate;
+			return this;
+		}
+
 		public OllamaChatModel build() {
 			if (this.toolCallingManager != null) {
 				return new OllamaChatModel(this.ollamaApi, this.defaultOptions, this.toolCallingManager,
-						this.observationRegistry, this.modelManagementOptions, this.toolExecutionEligibilityPredicate);
+						this.observationRegistry, this.modelManagementOptions, this.toolExecutionEligibilityPredicate,
+						this.retryTemplate);
 			}
 			return new OllamaChatModel(this.ollamaApi, this.defaultOptions, DEFAULT_TOOL_CALLING_MANAGER,
-					this.observationRegistry, this.modelManagementOptions, this.toolExecutionEligibilityPredicate);
+					this.observationRegistry, this.modelManagementOptions, this.toolExecutionEligibilityPredicate,
+					this.retryTemplate);
 		}
 
 	}
diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java
index e0ffc06c31d..b481386a479 100644
--- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java
+++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java
@@ -51,6 +51,7 @@
  * @author Christian Tzolov
  * @author Thomas Vitale
  * @author Jonghoon Park
+ * @author Alexandros Pappas
  * @since 0.8.0
  */
 // @formatter:off
@@ -64,6 +65,9 @@ public static Builder builder() {
 
 	private static final Log logger = LogFactory.getLog(OllamaApi.class);
 
+
+	private static final String DEFAULT_BASE_URL = "http://localhost:11434";
+
 	private final RestClient restClient;
 
 	private final WebClient webClient;
@@ -77,11 +81,13 @@ public static Builder builder() {
 	 */
 	private OllamaApi(String baseUrl, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
 
+
 		Consumer<HttpHeaders> defaultHeaders = headers -> {
 			headers.setContentType(MediaType.APPLICATION_JSON);
 			headers.setAccept(List.of(MediaType.APPLICATION_JSON));
 		};
 
+
 		this.restClient = restClientBuilder
 				.clone()
 				.baseUrl(baseUrl)
@@ -89,6 +95,7 @@ private OllamaApi(String baseUrl, RestClient.Builder restClientBuilder, WebClien
 				.defaultStatusHandler(responseErrorHandler)
 				.build();
 
+
 		this.webClient = webClientBuilder
 				.clone()
 				.baseUrl(baseUrl)
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java
index ef149203f65..f8ec3091c3f 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelFunctionCallingIT.java
@@ -36,6 +36,7 @@
 import org.springframework.ai.ollama.api.OllamaOptions;
 import org.springframework.ai.ollama.api.tool.MockWeatherService;
 import org.springframework.ai.tool.function.FunctionToolCallback;
+import org.springframework.ai.retry.RetryUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -120,6 +121,7 @@ public OllamaChatModel ollamaChat(OllamaApi ollamaApi) {
 			return OllamaChatModel.builder()
 				.ollamaApi(ollamaApi)
 				.defaultOptions(OllamaOptions.builder().model(MODEL).temperature(0.9).build())
+				.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
 				.build();
 		}
 
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java
index b322a82d764..ae79f67350e 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelIT.java
@@ -52,8 +52,12 @@
 import org.springframework.ai.ollama.management.ModelManagementOptions;
 import org.springframework.ai.ollama.management.OllamaModelManager;
 import org.springframework.ai.ollama.management.PullModelStrategy;
+
 import org.springframework.ai.support.ToolCallbacks;
 import org.springframework.ai.tool.annotation.Tool;
+
+import org.springframework.ai.retry.RetryUtils;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -371,6 +375,7 @@ public OllamaChatModel ollamaChat(OllamaApi ollamaApi) {
 					.pullModelStrategy(PullModelStrategy.WHEN_MISSING)
 					.additionalModels(List.of(ADDITIONAL_MODEL))
 					.build())
+				.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
 				.build();
 		}
 
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java
index 28064bb7732..3174c459afb 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java
@@ -16,6 +16,7 @@
 
 package org.springframework.ai.ollama;
 
+import java.time.Duration;
 import java.util.List;
 
 import org.junit.jupiter.api.Test;
@@ -27,11 +28,17 @@
 import org.springframework.ai.content.Media;
 import org.springframework.ai.ollama.api.OllamaApi;
 import org.springframework.ai.ollama.api.OllamaOptions;
+import org.springframework.ai.retry.RetryUtils;
+import org.springframework.ai.retry.TransientAiException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.io.ClassPathResource;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryListener;
+import org.springframework.retry.support.RetryTemplate;
 import org.springframework.util.MimeTypeUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -86,9 +93,23 @@ public OllamaApi ollamaApi() {
 
 		@Bean
 		public OllamaChatModel ollamaChat(OllamaApi ollamaApi) {
+			RetryTemplate retryTemplate = RetryTemplate.builder()
+				.maxAttempts(1)
+				.retryOn(TransientAiException.class)
+				.fixedBackoff(Duration.ofSeconds(1))
+				.withListener(new RetryListener() {
+
+					@Override
+					public <T extends Object, E extends Throwable> void onError(RetryContext context,
+							RetryCallback<T, E> callback, Throwable throwable) {
+						logger.warn("Retry error. Retry count:" + context.getRetryCount(), throwable);
+					}
+				})
+				.build();
 			return OllamaChatModel.builder()
 				.ollamaApi(ollamaApi)
 				.defaultOptions(OllamaOptions.builder().model(MODEL).temperature(0.9).build())
+				.retryTemplate(retryTemplate)
 				.build();
 		}
 
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java
index 916a364ba65..0d8b6a0b714 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelObservationIT.java
@@ -34,6 +34,7 @@
 import org.springframework.ai.ollama.api.OllamaApi;
 import org.springframework.ai.ollama.api.OllamaModel;
 import org.springframework.ai.ollama.api.OllamaOptions;
+import org.springframework.ai.retry.RetryUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
@@ -47,6 +48,7 @@
  * Integration tests for observation instrumentation in {@link OllamaChatModel}.
  *
  * @author Thomas Vitale
+ * @author Alexandros Pappas
  */
 @SpringBootTest(classes = OllamaChatModelObservationIT.Config.class)
 public class OllamaChatModelObservationIT extends BaseOllamaIT {
@@ -169,7 +171,11 @@ public OllamaApi openAiApi() {
 
 		@Bean
 		public OllamaChatModel openAiChatModel(OllamaApi ollamaApi, TestObservationRegistry observationRegistry) {
-			return OllamaChatModel.builder().ollamaApi(ollamaApi).observationRegistry(observationRegistry).build();
+			return OllamaChatModel.builder()
+				.ollamaApi(ollamaApi)
+				.observationRegistry(observationRegistry)
+				.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
+				.build();
 		}
 
 	}
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java
index fdb3c43cb68..1cb17781b0e 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelTests.java
@@ -35,6 +35,7 @@
 import org.springframework.ai.ollama.api.OllamaModel;
 import org.springframework.ai.ollama.api.OllamaOptions;
 import org.springframework.ai.ollama.management.ModelManagementOptions;
+import org.springframework.ai.retry.RetryUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.*;
@@ -82,6 +83,7 @@ void buildOllamaChatModel() {
 				() -> OllamaChatModel.builder()
 					.ollamaApi(this.ollamaApi)
 					.defaultOptions(OllamaOptions.builder().model(OllamaModel.LLAMA2).build())
+					.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
 					.modelManagementOptions(null)
 					.build());
 		assertEquals("modelManagementOptions must not be null", exception.getMessage());
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java
index 59baa37bec2..dbc65e1fb25 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatRequestTests.java
@@ -28,18 +28,21 @@
 import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
+import org.springframework.ai.retry.RetryUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
  * @author Christian Tzolov
  * @author Thomas Vitale
+ * @author Alexandros Pappas
  */
 class OllamaChatRequestTests {
 
 	OllamaChatModel chatModel = OllamaChatModel.builder()
 		.ollamaApi(OllamaApi.builder().build())
 		.defaultOptions(OllamaOptions.builder().model("MODEL_NAME").topK(99).temperature(66.6).numGPU(1).build())
+		.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
 		.build();
 
 	@Test
@@ -146,6 +149,7 @@ public void createRequestWithDefaultOptionsModelOverride() {
 		OllamaChatModel chatModel = OllamaChatModel.builder()
 			.ollamaApi(OllamaApi.builder().build())
 			.defaultOptions(OllamaOptions.builder().model("DEFAULT_OPTIONS_MODEL").build())
+			.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
 			.build();
 
 		var prompt1 = chatModel.buildRequestPrompt(new Prompt("Test message content"));
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java
index 2220bf22695..e82ecb9ab67 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaImage.java
@@ -23,7 +23,7 @@
  */
 public final class OllamaImage {
 
-	public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.5.2");
+	public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.6.7");
 
 	private OllamaImage() {
 
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java
new file mode 100644
index 00000000000..bb1c1bb22ee
--- /dev/null
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java
@@ -0,0 +1,97 @@
+package org.springframework.ai.ollama;
+
+import java.time.Instant;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.ollama.api.OllamaApi;
+import org.springframework.ai.ollama.api.OllamaModel;
+import org.springframework.ai.ollama.api.OllamaOptions;
+import org.springframework.ai.retry.RetryUtils;
+import org.springframework.ai.retry.TransientAiException;
+import org.springframework.retry.RetryCallback;
+import org.springframework.retry.RetryContext;
+import org.springframework.retry.RetryListener;
+import org.springframework.retry.support.RetryTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for the OllamaRetryTests class.
+ *
+ * @author Alexandros Pappas
+ */
+@ExtendWith(MockitoExtension.class)
+class OllamaRetryTests {
+
+	private static final String MODEL = OllamaModel.LLAMA3_2.getName();
+
+	private TestRetryListener retryListener;
+
+	private RetryTemplate retryTemplate;
+
+	@Mock
+	private OllamaApi ollamaApi;
+
+	private OllamaChatModel chatModel;
+
+	@BeforeEach
+	public void beforeEach() {
+		this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE;
+		this.retryListener = new TestRetryListener();
+		this.retryTemplate.registerListener(this.retryListener);
+
+		this.chatModel = OllamaChatModel.builder()
+			.ollamaApi(this.ollamaApi)
+			.defaultOptions(OllamaOptions.builder().model(MODEL).temperature(0.9).build())
+			.retryTemplate(this.retryTemplate)
+			.build();
+	}
+
+	@Test
+	void ollamaChatTransientError() {
+		String promptText = "What is the capital of Bulgaria and what is the size? What it the national anthem?";
+		var expectedChatResponse = new OllamaApi.ChatResponse("CHAT_COMPLETION_ID", Instant.now(),
+				OllamaApi.Message.builder(OllamaApi.Message.Role.ASSISTANT).content("Response").build(), null, true,
+				null, null, null, null, null, null);
+
+		when(this.ollamaApi.chat(isA(OllamaApi.ChatRequest.class)))
+			.thenThrow(new TransientAiException("Transient Error 1"))
+			.thenThrow(new TransientAiException("Transient Error 2"))
+			.thenReturn(expectedChatResponse);
+
+		var result = this.chatModel.call(new Prompt(promptText));
+
+		assertThat(result).isNotNull();
+		assertThat(result.getResult().getOutput().getText()).isSameAs("Response");
+		assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2);
+		assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2);
+	}
+
+	private static class TestRetryListener implements RetryListener {
+
+		int onErrorRetryCount = 0;
+
+		int onSuccessRetryCount = 0;
+
+		@Override
+		public <T, E extends Throwable> void onSuccess(RetryContext context, RetryCallback<T, E> callback, T result) {
+			this.onSuccessRetryCount = context.getRetryCount();
+		}
+
+		@Override
+		public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
+				Throwable throwable) {
+			this.onErrorRetryCount = context.getRetryCount();
+		}
+
+	}
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java
new file mode 100644
index 00000000000..5675e4c4a1a
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023-2025 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.autoconfigure.ollama;
+
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.ai.autoconfigure.chat.model.ToolCallingAutoConfiguration;
+import org.springframework.ai.chat.observation.ChatModelObservationConvention;
+import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
+import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
+import org.springframework.ai.model.function.FunctionCallbackResolver;
+import org.springframework.ai.model.tool.ToolCallingManager;
+import org.springframework.ai.ollama.OllamaChatModel;
+import org.springframework.ai.ollama.OllamaEmbeddingModel;
+import org.springframework.ai.ollama.api.OllamaApi;
+import org.springframework.ai.ollama.management.ModelManagementOptions;
+import org.springframework.ai.ollama.management.PullModelStrategy;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.retry.support.RetryTemplate;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * {@link AutoConfiguration Auto-configuration} for Ollama Chat Client.
+ *
+ * @author Christian Tzolov
+ * @author Eddú Meléndez
+ * @author Thomas Vitale
+ * @since 0.8.0
+ */
+@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
+@ConditionalOnClass(OllamaApi.class)
+@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaEmbeddingProperties.class,
+		OllamaConnectionProperties.class, OllamaInitializationProperties.class })
+@ImportAutoConfiguration(classes = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class,
+		WebClientAutoConfiguration.class })
+public class OllamaAutoConfiguration {
+
+	@Bean
+	@ConditionalOnMissingBean(OllamaConnectionDetails.class)
+	public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) {
+		return new PropertiesOllamaConnectionDetails(properties);
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails,
+			ObjectProvider<RestClient.Builder> restClientBuilderProvider,
+			ObjectProvider<WebClient.Builder> webClientBuilderProvider) {
+		return new OllamaApi(connectionDetails.getBaseUrl(),
+				restClientBuilderProvider.getIfAvailable(RestClient::builder),
+				webClientBuilderProvider.getIfAvailable(WebClient::builder));
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(prefix = OllamaChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+			matchIfMissing = true)
+	public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties,
+			OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager,
+			ObjectProvider<ObservationRegistry> observationRegistry,
+			ObjectProvider<ChatModelObservationConvention> observationConvention, RetryTemplate retryTemplate) {
+		var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy()
+				: PullModelStrategy.NEVER;
+
+		var chatModel = OllamaChatModel.builder()
+			.ollamaApi(ollamaApi)
+			.defaultOptions(properties.getOptions())
+			.toolCallingManager(toolCallingManager)
+			.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
+			.modelManagementOptions(
+					new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(),
+							initProperties.getTimeout(), initProperties.getMaxRetries()))
+			.retryTemplate(retryTemplate)
+			.build();
+
+		observationConvention.ifAvailable(chatModel::setObservationConvention);
+
+		return chatModel;
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(prefix = OllamaEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+			matchIfMissing = true)
+	public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties,
+			OllamaInitializationProperties initProperties, ObjectProvider<ObservationRegistry> observationRegistry,
+			ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
+		var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude()
+				? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER;
+
+		var embeddingModel = OllamaEmbeddingModel.builder()
+			.ollamaApi(ollamaApi)
+			.defaultOptions(properties.getOptions())
+			.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
+			.modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy,
+					initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(),
+					initProperties.getMaxRetries()))
+			.build();
+
+		observationConvention.ifAvailable(embeddingModel::setObservationConvention);
+
+		return embeddingModel;
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
+		DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
+		manager.setApplicationContext(context);
+		return manager;
+	}
+
+	static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails {
+
+		private final OllamaConnectionProperties properties;
+
+		PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) {
+			this.properties = properties;
+		}
+
+		@Override
+		public String getBaseUrl() {
+			return this.properties.getBaseUrl();
+		}
+
+	}
+
+}

From dc9ea17fc6760981d4d7d4f84f1ffd5b90a40940 Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <apappascs@gmail.com>
Date: Sun, 2 Mar 2025 14:22:18 +0100
Subject: [PATCH 071/165] feat: equals, hashCode, deep copy, and tests to
 MiniMaxChatOptions

This commit enhances MiniMaxChatOptions by:
- Updating `equals` and `hashCode` methods for proper object comparison.
- Updating `copy()` method, creating new instances of mutable collections (List, Set, Map, Metadata) to prevent shared state.
- Adding `MiniMaxChatOptionsTests` to verify `copy()`, builders, setters, and default values.
- Add more tests for objects not equal, defensive unmodifiable getters etc.

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
---
 .../ai/minimax/MiniMaxChatOptions.java        | 205 +++--------------
 .../ai/minimax/MiniMaxChatOptionsTests.java   | 208 ++++++++++++++++++
 2 files changed, 235 insertions(+), 178 deletions(-)
 create mode 100644 models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java

diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java
index 9d2614396c5..a8f1e62e77e 100644
--- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java
+++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java
@@ -18,10 +18,12 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -45,6 +47,7 @@
  * @author Geng Rong
  * @author Thomas Vitale
  * @author Ilayaperumal Gopinathan
+ * @author Alexandros Pappas
  * @since 1.0.0 M1
  */
 @JsonInclude(Include.NON_NULL)
@@ -167,11 +170,11 @@ public static MiniMaxChatOptions fromOptions(MiniMaxChatOptions fromOptions) {
 			.presencePenalty(fromOptions.getPresencePenalty())
 			.responseFormat(fromOptions.getResponseFormat())
 			.seed(fromOptions.getSeed())
-			.stop(fromOptions.getStop())
+			.stop(fromOptions.getStop() != null ? new ArrayList<>(fromOptions.getStop()) : null)
 			.temperature(fromOptions.getTemperature())
 			.topP(fromOptions.getTopP())
 			.maskSensitiveInfo(fromOptions.getMaskSensitiveInfo())
-			.tools(fromOptions.getTools())
+			.tools(fromOptions.getTools() != null ? new ArrayList<>(fromOptions.getTools()) : null)
 			.toolChoice(fromOptions.getToolChoice())
 			.toolCallbacks(fromOptions.getToolCallbacks())
 			.toolNames(fromOptions.getToolNames())
@@ -252,7 +255,7 @@ public void setStopSequences(List<String> stopSequences) {
 	}
 
 	public List<String> getStop() {
-		return this.stop;
+		return (this.stop != null) ? Collections.unmodifiableList(this.stop) : null;
 	}
 
 	public void setStop(List<String> stop) {
@@ -286,7 +289,7 @@ public void setMaskSensitiveInfo(Boolean maskSensitiveInfo) {
 	}
 
 	public List<MiniMaxApi.FunctionTool> getTools() {
-		return this.tools;
+		return (this.tools != null) ? Collections.unmodifiableList(this.tools) : null;
 	}
 
 	public void setTools(List<MiniMaxApi.FunctionTool> tools) {
@@ -310,7 +313,7 @@ public Integer getTopK() {
 	@Override
 	@JsonIgnore
 	public List<ToolCallback> getToolCallbacks() {
-		return this.toolCallbacks;
+		return Collections.unmodifiableList(this.toolCallbacks);
 	}
 
 	@Override
@@ -324,7 +327,7 @@ public void setToolCallbacks(List<ToolCallback> toolCallbacks) {
 	@Override
 	@JsonIgnore
 	public Set<String> getToolNames() {
-		return this.toolNames;
+		return Collections.unmodifiableSet(this.toolNames);
 	}
 
 	@Override
@@ -351,7 +354,7 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut
 
 	@Override
 	public Map<String, Object> getToolContext() {
-		return this.toolContext;
+		return (this.toolContext != null) ? Collections.unmodifiableMap(this.toolContext) : null;
 	}
 
 	@Override
@@ -361,182 +364,28 @@ public void setToolContext(Map<String, Object> toolContext) {
 
 	@Override
 	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((this.model == null) ? 0 : this.model.hashCode());
-		result = prime * result + ((this.frequencyPenalty == null) ? 0 : this.frequencyPenalty.hashCode());
-		result = prime * result + ((this.maxTokens == null) ? 0 : this.maxTokens.hashCode());
-		result = prime * result + ((this.n == null) ? 0 : this.n.hashCode());
-		result = prime * result + ((this.presencePenalty == null) ? 0 : this.presencePenalty.hashCode());
-		result = prime * result + ((this.responseFormat == null) ? 0 : this.responseFormat.hashCode());
-		result = prime * result + ((this.seed == null) ? 0 : this.seed.hashCode());
-		result = prime * result + ((this.stop == null) ? 0 : this.stop.hashCode());
-		result = prime * result + ((this.temperature == null) ? 0 : this.temperature.hashCode());
-		result = prime * result + ((this.topP == null) ? 0 : this.topP.hashCode());
-		result = prime * result + ((this.maskSensitiveInfo == null) ? 0 : this.maskSensitiveInfo.hashCode());
-		result = prime * result + ((this.tools == null) ? 0 : this.tools.hashCode());
-		result = prime * result + ((this.toolChoice == null) ? 0 : this.toolChoice.hashCode());
-		result = prime * result + ((this.toolCallbacks == null) ? 0 : this.toolCallbacks.hashCode());
-		result = prime * result + ((this.toolNames == null) ? 0 : this.toolNames.hashCode());
-		result = prime * result
-				+ ((this.internalToolExecutionEnabled == null) ? 0 : this.internalToolExecutionEnabled.hashCode());
-		result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode());
-		return result;
+		return Objects.hash(model, frequencyPenalty, maxTokens, n, presencePenalty, responseFormat, seed, stop,
+				temperature, topP, maskSensitiveInfo, tools, toolChoice, toolCallbacks, toolNames, toolContext,
+				internalToolExecutionEnabled);
 	}
 
 	@Override
-	public boolean equals(Object obj) {
-		if (this == obj) {
+	public boolean equals(Object o) {
+		if (this == o)
 			return true;
-		}
-		if (obj == null) {
-			return false;
-		}
-		if (getClass() != obj.getClass()) {
-			return false;
-		}
-		MiniMaxChatOptions other = (MiniMaxChatOptions) obj;
-		if (this.model == null) {
-			if (other.model != null) {
-				return false;
-			}
-		}
-		else if (!this.model.equals(other.model)) {
-			return false;
-		}
-		if (this.frequencyPenalty == null) {
-			if (other.frequencyPenalty != null) {
-				return false;
-			}
-		}
-		else if (!this.frequencyPenalty.equals(other.frequencyPenalty)) {
-			return false;
-		}
-		if (this.maxTokens == null) {
-			if (other.maxTokens != null) {
-				return false;
-			}
-		}
-		else if (!this.maxTokens.equals(other.maxTokens)) {
-			return false;
-		}
-		if (this.n == null) {
-			if (other.n != null) {
-				return false;
-			}
-		}
-		else if (!this.n.equals(other.n)) {
-			return false;
-		}
-		if (this.presencePenalty == null) {
-			if (other.presencePenalty != null) {
-				return false;
-			}
-		}
-		else if (!this.presencePenalty.equals(other.presencePenalty)) {
-			return false;
-		}
-		if (this.responseFormat == null) {
-			if (other.responseFormat != null) {
-				return false;
-			}
-		}
-		else if (!this.responseFormat.equals(other.responseFormat)) {
-			return false;
-		}
-		if (this.seed == null) {
-			if (other.seed != null) {
-				return false;
-			}
-		}
-		else if (!this.seed.equals(other.seed)) {
-			return false;
-		}
-		if (this.stop == null) {
-			if (other.stop != null) {
-				return false;
-			}
-		}
-		else if (!this.stop.equals(other.stop)) {
-			return false;
-		}
-		if (this.temperature == null) {
-			if (other.temperature != null) {
-				return false;
-			}
-		}
-		else if (!this.temperature.equals(other.temperature)) {
-			return false;
-		}
-		if (this.topP == null) {
-			if (other.topP != null) {
-				return false;
-			}
-		}
-		else if (!this.topP.equals(other.topP)) {
-			return false;
-		}
-		if (this.maskSensitiveInfo == null) {
-			if (other.maskSensitiveInfo != null) {
-				return false;
-			}
-		}
-		else if (!this.maskSensitiveInfo.equals(other.maskSensitiveInfo)) {
-			return false;
-		}
-		if (this.tools == null) {
-			if (other.tools != null) {
-				return false;
-			}
-		}
-		else if (!this.tools.equals(other.tools)) {
-			return false;
-		}
-		if (this.toolChoice == null) {
-			if (other.toolChoice != null) {
-				return false;
-			}
-		}
-		else if (!this.toolChoice.equals(other.toolChoice)) {
-			return false;
-		}
-		if (this.internalToolExecutionEnabled == null) {
-			if (other.internalToolExecutionEnabled != null) {
-				return false;
-			}
-		}
-		else if (!this.internalToolExecutionEnabled.equals(other.internalToolExecutionEnabled)) {
-			return false;
-		}
-
-		if (this.toolNames == null) {
-			if (other.toolNames != null) {
-				return false;
-			}
-		}
-		else if (!this.toolNames.equals(other.toolNames)) {
+		if (o == null || getClass() != o.getClass())
 			return false;
-		}
-
-		if (this.toolCallbacks == null) {
-			if (other.toolCallbacks != null) {
-				return false;
-			}
-		}
-		else if (!this.toolCallbacks.equals(other.toolCallbacks)) {
-			return false;
-		}
-
-		if (this.toolContext == null) {
-			if (other.toolContext != null) {
-				return false;
-			}
-		}
-		else if (!this.toolContext.equals(other.toolContext)) {
-			return false;
-		}
-
-		return true;
+		MiniMaxChatOptions that = (MiniMaxChatOptions) o;
+		return Objects.equals(model, that.model) && Objects.equals(frequencyPenalty, that.frequencyPenalty)
+				&& Objects.equals(maxTokens, that.maxTokens) && Objects.equals(n, that.n)
+				&& Objects.equals(presencePenalty, that.presencePenalty)
+				&& Objects.equals(responseFormat, that.responseFormat) && Objects.equals(seed, that.seed)
+				&& Objects.equals(stop, that.stop) && Objects.equals(temperature, that.temperature)
+				&& Objects.equals(topP, that.topP) && Objects.equals(maskSensitiveInfo, that.maskSensitiveInfo)
+				&& Objects.equals(tools, that.tools) && Objects.equals(toolChoice, that.toolChoice)
+				&& Objects.equals(toolCallbacks, that.toolCallbacks) && Objects.equals(toolNames, that.toolNames)
+				&& Objects.equals(toolContext, that.toolContext)
+				&& Objects.equals(internalToolExecutionEnabled, that.internalToolExecutionEnabled);
 	}
 
 	@Override
diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java
new file mode 100644
index 00000000000..cbcd25a87de
--- /dev/null
+++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/MiniMaxChatOptionsTests.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2025-2025 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.minimax;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.minimax.api.MiniMaxApi;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests for {@link MiniMaxChatOptions}.
+ *
+ * @author Alexandros Pappas
+ */
+class MiniMaxChatOptionsTests {
+
+	@Test
+	void testBuilderWithAllFields() {
+		MiniMaxChatOptions options = MiniMaxChatOptions.builder()
+			.model("test-model")
+			.frequencyPenalty(0.5)
+			.maxTokens(10)
+			.N(1)
+			.presencePenalty(0.5)
+			.responseFormat(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text"))
+			.seed(1)
+			.stop(List.of("test"))
+			.temperature(0.6)
+			.topP(0.6)
+			.maskSensitiveInfo(false)
+			.toolChoice("test")
+			.internalToolExecutionEnabled(true)
+			.toolContext(Map.of("key1", "value1"))
+			.build();
+
+		assertThat(options)
+			.extracting("model", "frequencyPenalty", "maxTokens", "N", "presencePenalty", "responseFormat", "seed",
+					"stop", "temperature", "topP", "maskSensitiveInfo", "toolChoice", "internalToolExecutionEnabled",
+					"toolContext")
+			.containsExactly("test-model", 0.5, 10, 1, 0.5, new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text"),
+					1, List.of("test"), 0.6, 0.6, false, "test", true, Map.of("key1", "value1"));
+	}
+
+	@Test
+	void testCopy() {
+		MiniMaxChatOptions original = MiniMaxChatOptions.builder()
+			.model("test-model")
+			.frequencyPenalty(0.5)
+			.maxTokens(10)
+			.N(1)
+			.presencePenalty(0.5)
+			.responseFormat(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text"))
+			.seed(1)
+			.stop(List.of("test"))
+			.temperature(0.6)
+			.topP(0.6)
+			.maskSensitiveInfo(false)
+			.toolChoice("test")
+			.internalToolExecutionEnabled(true)
+			.toolContext(Map.of("key1", "value1"))
+			.build();
+
+		MiniMaxChatOptions copied = original.copy();
+
+		assertThat(copied).isNotSameAs(original).isEqualTo(original);
+		// Ensure deep copy
+		assertThat(copied.getStop()).isNotSameAs(original.getStop());
+		assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext());
+	}
+
+	@Test
+	void testNotEquals() {
+		MiniMaxChatOptions options1 = MiniMaxChatOptions.builder().model("model1").build();
+		MiniMaxChatOptions options2 = MiniMaxChatOptions.builder().model("model2").build();
+
+		assertThat(options1).isNotEqualTo(options2);
+	}
+
+	@Test
+	void testSettersWithNulls() {
+		MiniMaxChatOptions options = new MiniMaxChatOptions();
+		options.setModel(null);
+		options.setFrequencyPenalty(null);
+		options.setMaxTokens(null);
+		options.setN(null);
+		options.setPresencePenalty(null);
+		options.setResponseFormat(null);
+		options.setSeed(null);
+		options.setStop(null);
+		options.setTemperature(null);
+		options.setTopP(null);
+		options.setMaskSensitiveInfo(null);
+		options.setTools(null);
+		options.setToolChoice(null);
+		options.setInternalToolExecutionEnabled(null);
+		options.setToolContext(null);
+
+		assertThat(options.getModel()).isNull();
+		assertThat(options.getFrequencyPenalty()).isNull();
+		assertThat(options.getMaxTokens()).isNull();
+		assertThat(options.getN()).isNull();
+		assertThat(options.getPresencePenalty()).isNull();
+		assertThat(options.getResponseFormat()).isNull();
+		assertThat(options.getSeed()).isNull();
+		assertThat(options.getStop()).isNull();
+		assertThat(options.getTemperature()).isNull();
+		assertThat(options.getTopP()).isNull();
+		assertThat(options.getMaskSensitiveInfo()).isNull();
+		assertThat(options.getTools()).isNull();
+		assertThat(options.getToolChoice()).isNull();
+		assertThat(options.getInternalToolExecutionEnabled()).isNull();
+		assertThat(options.getToolContext()).isNull();
+	}
+
+	@Test
+	void testImmutabilityOfCollections() {
+		MiniMaxChatOptions options = MiniMaxChatOptions.builder()
+			.stop(new java.util.ArrayList<>(List.of("stop")))
+			.tools(new java.util.ArrayList<>(List.of(new MiniMaxApi.FunctionTool(MiniMaxApi.FunctionTool.Type.FUNCTION,
+					new MiniMaxApi.FunctionTool.Function("name", "desc", (Map<String, Object>) null)))))
+			.toolCallbacks(new java.util.ArrayList<>(List.of()))
+			.toolNames(new java.util.HashSet<>(Set.of("tool")))
+			.toolContext(new java.util.HashMap<>(Map.of("key", "value")))
+			.build();
+
+		assertThatThrownBy(() -> options.getStop().add("another")).isInstanceOf(UnsupportedOperationException.class);
+		assertThatThrownBy(() -> options.getTools().add(null)).isInstanceOf(UnsupportedOperationException.class);
+		assertThatThrownBy(() -> options.getToolCallbacks().add(null))
+			.isInstanceOf(UnsupportedOperationException.class);
+		assertThatThrownBy(() -> options.getToolNames().add("another"))
+			.isInstanceOf(UnsupportedOperationException.class);
+		assertThatThrownBy(() -> options.getToolContext().put("another", "value"))
+			.isInstanceOf(UnsupportedOperationException.class);
+	}
+
+	@Test
+	void testSetters() {
+		MiniMaxChatOptions options = new MiniMaxChatOptions();
+		options.setModel("test-model");
+		options.setFrequencyPenalty(0.5);
+		options.setMaxTokens(10);
+		options.setN(1);
+		options.setPresencePenalty(0.5);
+		options.setResponseFormat(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text"));
+		options.setSeed(1);
+		options.setStop(List.of("test"));
+		options.setTemperature(0.6);
+		options.setTopP(0.6);
+		options.setMaskSensitiveInfo(false);
+		options.setToolChoice("test");
+		options.setInternalToolExecutionEnabled(true);
+		options.setToolContext(Map.of("key1", "value1"));
+
+		assertThat(options.getModel()).isEqualTo("test-model");
+		assertThat(options.getFrequencyPenalty()).isEqualTo(0.5);
+		assertThat(options.getMaxTokens()).isEqualTo(10);
+		assertThat(options.getN()).isEqualTo(1);
+		assertThat(options.getPresencePenalty()).isEqualTo(0.5);
+		assertThat(options.getResponseFormat()).isEqualTo(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("text"));
+		assertThat(options.getSeed()).isEqualTo(1);
+		assertThat(options.getStop()).isEqualTo(List.of("test"));
+		assertThat(options.getTemperature()).isEqualTo(0.6);
+		assertThat(options.getTopP()).isEqualTo(0.6);
+		assertThat(options.getMaskSensitiveInfo()).isEqualTo(false);
+		assertThat(options.getToolChoice()).isEqualTo("test");
+		assertThat(options.getInternalToolExecutionEnabled()).isEqualTo(true);
+		assertThat(options.getToolContext()).isEqualTo(Map.of("key1", "value1"));
+	}
+
+	@Test
+	void testDefaultValues() {
+		MiniMaxChatOptions options = new MiniMaxChatOptions();
+		assertThat(options.getModel()).isNull();
+		assertThat(options.getFrequencyPenalty()).isNull();
+		assertThat(options.getMaxTokens()).isNull();
+		assertThat(options.getN()).isNull();
+		assertThat(options.getPresencePenalty()).isNull();
+		assertThat(options.getResponseFormat()).isNull();
+		assertThat(options.getSeed()).isNull();
+		assertThat(options.getStop()).isNull();
+		assertThat(options.getTemperature()).isNull();
+		assertThat(options.getTopP()).isNull();
+		assertThat(options.getMaskSensitiveInfo()).isNull();
+		assertThat(options.getToolChoice()).isNull();
+		assertThat(options.getInternalToolExecutionEnabled()).isNull();
+		assertThat(options.getToolContext()).isEqualTo(new java.util.HashMap<>());
+	}
+
+}

From 330986998a31b5221bdd2de16a3bcdcb21f16a31 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 12 Jun 2025 18:12:22 +0100
Subject: [PATCH 072/165] Fix OllamaEmbeddingAutoConfigurationTests

 - Use OllamaEmbeddingAutoConfiguration for the test

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 .../autoconfigure/OllamaEmbeddingAutoConfigurationTests.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java
index bc6f2a6d349..2490e5258b6 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java
@@ -45,7 +45,7 @@ public void propertiesTest() {
 		)
 
 			.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
-					RestClientAutoConfiguration.class, OllamaChatAutoConfiguration.class))
+					RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class))
 			.run(context -> {
 				var embeddingProperties = context.getBean(OllamaEmbeddingProperties.class);
 				var connectionProperties = context.getBean(OllamaConnectionProperties.class);

From 89d8f6a20eabd11b570e871acbed6c4fcc2d38ef Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 12 Jun 2025 18:37:29 +0100
Subject: [PATCH 073/165] Revert "fix: corrected a logic error in the
 `validateToolContextSupport` method caused by incorrect parameter order."

This reverts commit 2ca1be2b838e87b9c0674b4b8b76f1010eb45951.
---
 .../ai/tool/method/MethodToolCallback.java    |  4 +-
 .../MethodToolCallbackGenericTypesTest.java   | 77 -------------------
 2 files changed, 2 insertions(+), 79 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
index d5f3970ea5a..cc320a54d5c 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
@@ -118,8 +118,8 @@ public String call(String toolInput, @Nullable ToolContext toolContext) {
 	private void validateToolContextSupport(@Nullable ToolContext toolContext) {
 		var isNonEmptyToolContextProvided = toolContext != null && !CollectionUtils.isEmpty(toolContext.getContext());
 		var isToolContextAcceptedByMethod = Stream.of(this.toolMethod.getParameterTypes())
-			.anyMatch(type -> ClassUtils.isAssignable(ToolContext.class, type));
-		if (isNonEmptyToolContextProvided && !isToolContextAcceptedByMethod) {
+			.anyMatch(type -> ClassUtils.isAssignable(type, ToolContext.class));
+		if (isToolContextAcceptedByMethod && !isNonEmptyToolContextProvided) {
 			throw new IllegalArgumentException("ToolContext is required by the method as an argument");
 		}
 	}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
index 544d440a9a9..b99faa71a3a 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
@@ -22,12 +22,10 @@
 
 import org.junit.jupiter.api.Test;
 
-import org.springframework.ai.chat.model.ToolContext;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
  * Tests for {@link MethodToolCallback} with generic types.
@@ -139,76 +137,6 @@ void testNestedGenericType() throws Exception {
 		assertThat(result).isEqualTo("2 maps processed: [{a=1, b=2}, {c=3, d=4}]");
 	}
 
-	@Test
-	void testToolContextType() throws Exception {
-		// Create a test object with a method that takes a List<Map<String, Integer>>
-		TestGenericClass testObject = new TestGenericClass();
-		Method method = TestGenericClass.class.getMethod("processStringListInToolContext", ToolContext.class);
-
-		// Create a tool definition
-		ToolDefinition toolDefinition = DefaultToolDefinition.builder()
-			.name("processToolContext")
-			.description("Process tool context")
-			.inputSchema("{}")
-			.build();
-
-		// Create a MethodToolCallback
-		MethodToolCallback callback = MethodToolCallback.builder()
-			.toolDefinition(toolDefinition)
-			.toolMethod(method)
-			.toolObject(testObject)
-			.build();
-
-		// Create an empty JSON input
-		String toolInput = """
-				{}
-				""";
-
-		// Create a toolContext
-		ToolContext toolContext = new ToolContext(Map.of("foo", "bar"));
-
-		// Call the tool
-		String result = callback.call(toolInput, toolContext);
-
-		// Verify the result
-		assertThat(result).isEqualTo("1 entries processed {foo=bar}");
-	}
-
-	@Test
-	void testToolContextTypeWithNonToolContextArgs() throws Exception {
-		// Create a test object with a method that takes a List<String>
-		TestGenericClass testObject = new TestGenericClass();
-		Method method = TestGenericClass.class.getMethod("processStringList", List.class);
-
-		// Create a tool definition
-		ToolDefinition toolDefinition = DefaultToolDefinition.builder()
-			.name("processStringList")
-			.description("Process a list of strings")
-			.inputSchema("{}")
-			.build();
-
-		// Create a MethodToolCallback
-		MethodToolCallback callback = MethodToolCallback.builder()
-			.toolDefinition(toolDefinition)
-			.toolMethod(method)
-			.toolObject(testObject)
-			.build();
-
-		// Create a JSON input with a list of strings
-		String toolInput = """
-				{
-					"strings": ["one", "two", "three"]
-				}
-				""";
-
-		// Create a toolContext
-		ToolContext toolContext = new ToolContext(Map.of("foo", "bar"));
-
-		// Call the tool and verify
-		assertThatThrownBy(() -> callback.call(toolInput, toolContext)).isInstanceOf(IllegalArgumentException.class)
-			.hasMessageContaining("ToolContext is required by the method as an argument");
-	}
-
 	/**
 	 * Test class with methods that use generic types.
 	 */
@@ -226,11 +154,6 @@ public String processListOfMaps(List<Map<String, Integer>> listOfMaps) {
 			return listOfMaps.size() + " maps processed: " + listOfMaps;
 		}
 
-		public String processStringListInToolContext(ToolContext toolContext) {
-			Map<String, Object> context = toolContext.getContext();
-			return context.size() + " entries processed " + context;
-		}
-
 	}
 
 }

From 80ffb4ce99ccad174f8d60ae22a0a0b735d760d1 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 12 Jun 2025 18:37:48 +0100
Subject: [PATCH 074/165] Revert "Fix
 OpenAiChatClientMethodInvokingFunctionCallbackIT"

This reverts commit bb52db5e7029bacced252711df5868dbca1ad93b.
---
 ...lientMethodInvokingFunctionCallbackIT.java | 31 ++-----------------
 1 file changed, 3 insertions(+), 28 deletions(-)

diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java
index c405aacadfb..2a766a6b055 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientMethodInvokingFunctionCallbackIT.java
@@ -37,7 +37,6 @@
 import org.springframework.util.ReflectionUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
 
 @SpringBootTest(classes = OpenAiTestConfiguration.class)
@@ -166,12 +165,12 @@ void methodGetWeatherToolContext() {
 	}
 
 	@Test
-	void methodGetWeatherNonStaticButWithToolContext() {
+	void methodGetWeatherToolContextButMissingContextArgument() {
 
 		TestFunctionClass targetObject = new TestFunctionClass();
 
-		var toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherNonStatic", String.class,
-				Unit.class);
+		var toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
+				Unit.class, ToolContext.class);
 
 		// @formatter:off
 		assertThatThrownBy(() -> ChatClient.create(this.chatModel).prompt()
@@ -183,7 +182,6 @@ void methodGetWeatherNonStaticButWithToolContext() {
 					.toolMethod(toolMethod)
 					.toolObject(targetObject)
 					.build())
-				.toolContext(Map.of("tool-context", "value"))
 				.call()
 				.content())
 				.isInstanceOf(IllegalArgumentException.class)
@@ -191,29 +189,6 @@ void methodGetWeatherNonStaticButWithToolContext() {
 		// @formatter:on
 	}
 
-	@Test
-	void methodGetWeatherToolContextButWithoutToolContext() {
-
-		TestFunctionClass targetObject = new TestFunctionClass();
-
-		var toolMethod = ReflectionUtils.findMethod(TestFunctionClass.class, "getWeatherWithContext", String.class,
-				Unit.class, ToolContext.class);
-
-		// @formatter:off
-		assertThatCode(() ->ChatClient.create(this.chatModel).prompt()
-				.user("What's the weather like in San Francisco, Tokyo, and Paris?  Use Celsius.")
-				.toolCallbacks(MethodToolCallback.builder()
-						.toolDefinition(ToolDefinitions.builder(toolMethod)
-								.description("Get the weather in location")
-								.build())
-						.toolMethod(toolMethod)
-						.toolObject(targetObject)
-						.build())
-				.call()
-				.content()).doesNotThrowAnyException();
-		// @formatter:on
-	}
-
 	@Test
 	void methodNoParameters() {
 

From 61c8044bbad576bfe3260a940ae63a603aff5a42 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Mon, 9 Jun 2025 10:48:16 +0800
Subject: [PATCH 075/165] fix: `validateToolContextSupport` method to use right
 order in ClassUtils.isAssignable

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
---
 .../ai/tool/method/MethodToolCallback.java    |  2 +-
 .../MethodToolCallbackGenericTypesTest.java   | 41 +++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
index cc320a54d5c..7c303f3a693 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallback.java
@@ -118,7 +118,7 @@ public String call(String toolInput, @Nullable ToolContext toolContext) {
 	private void validateToolContextSupport(@Nullable ToolContext toolContext) {
 		var isNonEmptyToolContextProvided = toolContext != null && !CollectionUtils.isEmpty(toolContext.getContext());
 		var isToolContextAcceptedByMethod = Stream.of(this.toolMethod.getParameterTypes())
-			.anyMatch(type -> ClassUtils.isAssignable(type, ToolContext.class));
+			.anyMatch(type -> ClassUtils.isAssignable(ToolContext.class, type));
 		if (isToolContextAcceptedByMethod && !isNonEmptyToolContextProvided) {
 			throw new IllegalArgumentException("ToolContext is required by the method as an argument");
 		}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
index b99faa71a3a..6e05fd80c59 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackGenericTypesTest.java
@@ -22,6 +22,7 @@
 
 import org.junit.jupiter.api.Test;
 
+import org.springframework.ai.chat.model.ToolContext;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
 
@@ -137,6 +138,41 @@ void testNestedGenericType() throws Exception {
 		assertThat(result).isEqualTo("2 maps processed: [{a=1, b=2}, {c=3, d=4}]");
 	}
 
+	@Test
+	void testToolContextType() throws Exception {
+		// Create a test object with a method that takes a List<Map<String, Integer>>
+		TestGenericClass testObject = new TestGenericClass();
+		Method method = TestGenericClass.class.getMethod("processStringListInToolContext", ToolContext.class);
+
+		// Create a tool definition
+		ToolDefinition toolDefinition = DefaultToolDefinition.builder()
+			.name("processToolContext")
+			.description("Process tool context")
+			.inputSchema("{}")
+			.build();
+
+		// Create a MethodToolCallback
+		MethodToolCallback callback = MethodToolCallback.builder()
+			.toolDefinition(toolDefinition)
+			.toolMethod(method)
+			.toolObject(testObject)
+			.build();
+
+		// Create an empty JSON input
+		String toolInput = """
+				{}
+				""";
+
+		// Create a toolContext
+		ToolContext toolContext = new ToolContext(Map.of("foo", "bar"));
+
+		// Call the tool
+		String result = callback.call(toolInput, toolContext);
+
+		// Verify the result
+		assertThat(result).isEqualTo("1 entries processed {foo=bar}");
+	}
+
 	/**
 	 * Test class with methods that use generic types.
 	 */
@@ -154,6 +190,11 @@ public String processListOfMaps(List<Map<String, Integer>> listOfMaps) {
 			return listOfMaps.size() + " maps processed: " + listOfMaps;
 		}
 
+		public String processStringListInToolContext(ToolContext toolContext) {
+			Map<String, Object> context = toolContext.getContext();
+			return context.size() + " entries processed " + context;
+		}
+
 	}
 
 }

From b84d123cd41fd4a4495c218002f9b2a48bc60b4f Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 12 Jun 2025 19:53:17 +0100
Subject: [PATCH 076/165] Remove explicit test dependency version for
 jackson-dataformat-xml

 - Use the dependency from Spring Boot BOM

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 models/spring-ai-anthropic/pom.xml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/models/spring-ai-anthropic/pom.xml b/models/spring-ai-anthropic/pom.xml
index 8614ebb7f59..8519252fa9e 100644
--- a/models/spring-ai-anthropic/pom.xml
+++ b/models/spring-ai-anthropic/pom.xml
@@ -104,7 +104,6 @@
 		<dependency>
 			<groupId>com.fasterxml.jackson.dataformat</groupId>
 			<artifactId>jackson-dataformat-xml</artifactId>
-			<version>2.11.1</version>
 			<scope>test</scope>
 		</dependency>
 	</dependencies>

From d8e507f72457b1545faa7169b3adf3ece8c7f156 Mon Sep 17 00:00:00 2001
From: pavan kumar punna <punnapavankumar9@gmail.com>
Date: Fri, 13 Jun 2025 11:46:37 +0530
Subject: [PATCH 077/165] docs(web): fix links to ChatOptions and
 ChatOptions#builder() (#3447)

use DefaultChatOptionsBuilder for link to ChatOptions#builder()

Signed-off-by: punnapavankumar9 <punnapavankumar9@gmail.com>
(cherry picked from commit 16a7084faa321b347b566f3b8414c2c145712852)
---
 .../antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc    | 2 +-
 .../antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc | 4 ++--
 .../antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc     | 2 +-
 .../src/main/antora/modules/ROOT/pages/api/chat/dmr-chat.adoc | 2 +-
 .../main/antora/modules/ROOT/pages/api/chat/groq-chat.adoc    | 2 +-
 .../main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc | 2 +-
 .../antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc    | 2 +-
 .../main/antora/modules/ROOT/pages/api/chat/nvidia-chat.adoc  | 2 +-
 .../main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc  | 2 +-
 .../main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc  | 2 +-
 .../antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc   | 2 +-
 .../modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc     | 3 +--
 .../main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc | 2 +-
 13 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
index 1cd82665072..affb5d7dcfa 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
@@ -189,7 +189,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java[AnthropicChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java[AnthropicChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Thinking
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
index 636b2c4951d..b6576d85684 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/azure-openai-chat.adoc
@@ -199,7 +199,7 @@ The prefix `spring.ai.azure.openai` is the property prefix to configure the conn
 | spring.ai.azure.openai.endpoint | The endpoint from the Azure AI OpenAI `Keys and Endpoint` section under `Resource Management` | -
 | spring.ai.azure.openai.openai-api-key |  (non Azure) OpenAI API key. Used to authenticate with the OpenAI service, instead of Azure OpenAI. 
 This automatically sets the endpoint to https://api.openai.com/v1. Use either `api-key` or `openai-api-key` property. 
-With this configuration the `spring.ai.azure.openai.chat.options.deployment-name` is threated as an https://platform.openai.com/docs/models[OpenAi Model] name.| -
+With this configuration the `spring.ai.azure.openai.chat.options.deployment-name` is treated as an https://platform.openai.com/docs/models[OpenAi Model] name.| -
 | spring.ai.azure.openai.custom-headers | A map of custom headers to be included in the API requests. Each entry in the map represents a header, where the key is the header name and the value is the header value. | Empty map
 |====
 
@@ -267,7 +267,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java[AzureOpenAiChatOptions.java] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java[AzureOpenAiChatOptions.java] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 
 == Function Calling
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc
index 4666d081da5..e48469e3af8 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/deepseek-chat.adoc
@@ -157,7 +157,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model-specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java[DeepSeekChatOptions], you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model-specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java[DeepSeekChatOptions], you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Sample Controller (Auto-configuration)
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/dmr-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/dmr-chat.adoc
index 9358a936ccb..4cbdd1de0a3 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/dmr-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/dmr-chat.adoc
@@ -162,7 +162,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions#builder()].
+TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Function Calling
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/groq-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/groq-chat.adoc
index ab1d63b1da2..1dc8dc758ec 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/groq-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/groq-chat.adoc
@@ -208,7 +208,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions#builder()].
+TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Function Calling
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc
index 5b98b1f7e6a..47f7cecd3d3 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/minimax-chat.adoc
@@ -173,7 +173,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java[MiniMaxChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java[MiniMaxChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Sample Controller
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc
index c5d37107a87..cd28497b33c 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/mistralai-chat.adoc
@@ -178,7 +178,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java[MistralAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java[MistralAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Function Calling
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/nvidia-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/nvidia-chat.adoc
index ffc359f2809..ec51ba941ec 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/nvidia-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/nvidia-chat.adoc
@@ -145,7 +145,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions#builder()].
+TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Function Calling
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc
index 4a4c1c50cb4..b8b9895ab3c 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/ollama-chat.adoc
@@ -177,7 +177,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java[OllamaOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaOptions.java[OllamaOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 [[auto-pulling-models]]
 == Auto-pulling Models
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
index b6aec282423..424fe8966bd 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
@@ -206,7 +206,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Function Calling
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc
index c330292be4d..cfb7b62a819 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/perplexity-chat.adoc
@@ -204,7 +204,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions#builder()].
+TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatOptions.java[OpenAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 
 == Function Calling
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc
index 9d9182c224a..bc6c7682ac3 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc
@@ -128,8 +128,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific `VertexAiGeminiChatOptions` you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the
-https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific `VertexAiGeminiChatOptions` you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Tool Calling
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc
index dc4d9be2a07..0be82d1d220 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc
@@ -173,7 +173,7 @@ ChatResponse response = chatModel.call(
     ));
 ----
 
-TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java[ZhiPuAiChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()].
+TIP: In addition to the model specific link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java[ZhiPuAiChatOptions] you can use a portable link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/DefaultChatOptionsBuilder.java[ChatOptions#builder()].
 
 == Sample Controller
 

From 0ce919238336fe6f2bea8d7aeda93f7994067e22 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Thu, 12 Jun 2025 18:13:25 +0800
Subject: [PATCH 078/165] test: Add unit test to verify multiple method
 toolcallbacks with toolcontext

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit b9a683460d17a381be2e67cd9ac2b4589d728c19)
---
 .../tool/DefaultToolCallingManagerTests.java  | 74 +++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingManagerTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingManagerTests.java
index 7dba4ad2518..c25e221eb09 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingManagerTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingManagerTests.java
@@ -16,6 +16,7 @@
 
 package org.springframework.ai.model.tool;
 
+import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Map;
 
@@ -27,6 +28,7 @@
 import org.springframework.ai.chat.messages.UserMessage;
 import org.springframework.ai.chat.model.ChatResponse;
 import org.springframework.ai.chat.model.Generation;
+import org.springframework.ai.chat.model.ToolContext;
 import org.springframework.ai.chat.prompt.Prompt;
 import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
@@ -34,6 +36,7 @@
 import org.springframework.ai.tool.execution.ToolExecutionException;
 import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor;
 import org.springframework.ai.tool.metadata.ToolMetadata;
+import org.springframework.ai.tool.method.MethodToolCallback;
 import org.springframework.ai.tool.resolution.StaticToolCallbackResolver;
 import org.springframework.ai.tool.resolution.ToolCallbackResolver;
 
@@ -45,6 +48,7 @@
  * Unit tests for {@link DefaultToolCallingManager}.
  *
  * @author Thomas Vitale
+ * @author Sun Yuhan
  */
 class DefaultToolCallingManagerTests {
 
@@ -317,6 +321,49 @@ void whenToolCallWithExceptionThenReturnError() {
 		assertThat(toolExecutionResult.conversationHistory()).contains(expectedToolResponse);
 	}
 
+	@Test
+	void whenMixedMethodToolCallsInChatResponseThenExecute() throws NoSuchMethodException {
+		ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder().build();
+
+		ToolDefinition toolDefinitionA = ToolDefinition.builder().name("toolA").inputSchema("{}").build();
+		Method methodA = TestGenericClass.class.getMethod("call", String.class);
+		MethodToolCallback methodToolCallback = MethodToolCallback.builder()
+			.toolDefinition(toolDefinitionA)
+			.toolMethod(methodA)
+			.toolObject(new TestGenericClass())
+			.build();
+
+		ToolDefinition toolDefinitionB = ToolDefinition.builder().name("toolB").inputSchema("{}").build();
+		Method methodB = TestGenericClass.class.getMethod("callWithToolContext", ToolContext.class);
+		MethodToolCallback methodToolCallbackNeedToolContext = MethodToolCallback.builder()
+			.toolDefinition(toolDefinitionB)
+			.toolMethod(methodB)
+			.toolObject(new TestGenericClass())
+			.build();
+
+		Prompt prompt = new Prompt(new UserMessage("Hello"),
+				ToolCallingChatOptions.builder()
+					.toolCallbacks(methodToolCallback, methodToolCallbackNeedToolContext)
+					.toolNames("toolA", "toolB")
+					.toolContext("key", "value")
+					.build());
+
+		ChatResponse chatResponse = ChatResponse.builder()
+			.generations(List.of(new Generation(new AssistantMessage("", Map.of(),
+					List.of(new AssistantMessage.ToolCall("toolA", "function", "toolA", "{}"),
+							new AssistantMessage.ToolCall("toolB", "function", "toolB", "{}"))))))
+			.build();
+
+		ToolResponseMessage expectedToolResponse = new ToolResponseMessage(
+				List.of(new ToolResponseMessage.ToolResponse("toolA", "toolA", TestGenericClass.CALL_RESULT_JSON),
+						new ToolResponseMessage.ToolResponse("toolB", "toolB",
+								TestGenericClass.CALL_WITH_TOOL_CONTEXT_RESULT_JSON)));
+
+		ToolExecutionResult toolExecutionResult = toolCallingManager.executeToolCalls(prompt, chatResponse);
+
+		assertThat(toolExecutionResult.conversationHistory()).contains(expectedToolResponse);
+	}
+
 	static class TestToolCallback implements ToolCallback {
 
 		private final ToolDefinition toolDefinition;
@@ -370,4 +417,31 @@ public String call(String toolInput) {
 
 	}
 
+	/**
+	 * Test class with methods that use generic types.
+	 */
+	static class TestGenericClass {
+
+		public final static String CALL_RESULT_JSON = """
+				{
+					"result": "Mission accomplished!"
+				}
+				""";
+
+		public final static String CALL_WITH_TOOL_CONTEXT_RESULT_JSON = """
+				{
+					"result": "ToolContext mission accomplished!"
+				}
+				""";
+
+		public String call(String toolInput) {
+			return CALL_RESULT_JSON;
+		}
+
+		public String callWithToolContext(ToolContext toolContext) {
+			return CALL_WITH_TOOL_CONTEXT_RESULT_JSON;
+		}
+
+	}
+
 }

From dd84b7558d3b3515cbae95568302814edbc2d3f1 Mon Sep 17 00:00:00 2001
From: Jemin Huh <hjm1980@gmail.com>
Date: Fri, 13 Jun 2025 18:37:31 +0900
Subject: [PATCH 079/165] docs: fix incorrect usage of tools and defaultTools
 (#3533)

Signed-off-by: Jemin Huh <hjm1980@gmail.com>
(cherry picked from commit e4788044bbf1c27cf48e010909989c0ac3307d68)
---
 .../antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc    | 2 +-
 .../modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc       | 2 +-
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc           | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc
index 29c24d18cb4..44966528e06 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock-converse.adoc
@@ -168,7 +168,7 @@ public Function<Request, Response> weatherFunction() {
 
 String response = ChatClient.create(this.chatModel)
         .prompt("What's the weather like in Boston?")
-        .tools("weatherFunction")
+        .toolNames("weatherFunction")
         .inputType(Request.class)
         .call()
         .content();
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc
index bc6c7682ac3..32c41a7ac67 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/vertexai-gemini-chat.adoc
@@ -165,7 +165,7 @@ public Function<Request, Response> weatherFunction() {
 
 String response = ChatClient.create(this.chatModel)
         .prompt("What's the weather like in Boston?")
-        .tools("weatherFunction")
+        .toolNames("weatherFunction")
         .inputType(Request.class)
         .call()
         .content();
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index d649452b1f0..28b6f0919d5 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -641,7 +641,7 @@ WARNING: Default tools are shared across all the chat requests performed by all
 ----
 ChatModel chatModel = ...
 ChatClient chatClient = ChatClient.builder(chatModel)
-    .defaultTools("currentWeather")
+    .defaultToolNames("currentWeather")
     .build();
 ----
 

From 219cf9a65d2cf86a4ee3602975093c2bbcee5171 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E8=99=8E=E6=98=AF=E6=9D=A1=E5=A4=A7=E7=8B=BC?=
 =?UTF-8?q?=E7=8B=97?= <494509580@qq.com>
Date: Fri, 13 Jun 2025 22:19:34 +0800
Subject: [PATCH 080/165] Update zhipuai-chat.adoc (#3531)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Update zhipuai-chat docs example with "produces = MediaType.TEXT_EVENT_STREAM_VALUE"  ( Content-Type =  "text/event-stream" ) in the "@GetMapping" annotation.

Signed-off-by: 老虎是条大狼狗 <494509580@qq.com>
(cherry picked from commit ed4fb29a74ed1611b126f5ac5f10b46c6d2b5b5c)
---
 .../main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc
index 0be82d1d220..c13552b190e 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/zhipuai-chat.adoc
@@ -210,7 +210,7 @@ public class ChatController {
         return Map.of("generation", this.chatModel.call(message));
     }
 
-    @GetMapping("/ai/generateStream")
+    @GetMapping(value = "/ai/generateStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
 	public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
         var prompt = new Prompt(new UserMessage(message));
         return this.chatModel.stream(prompt);

From fe002adcbba884730f02961ef85b8e1efd94e6d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EB=82=9C=EA=B8=B0=EC=88=98?= <nankisu0301@naver.com>
Date: Fri, 13 Jun 2025 23:52:30 +0900
Subject: [PATCH 081/165] docs: update OpenAI page Vision section (#3490)

Signed-off-by: nankisu <nankisu0301@naver.com>
---
 .../ROOT/pages/api/chat/openai-chat.adoc      | 28 +++++++++++--------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
index 424fe8966bd..2765dd33590 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/openai-chat.adoc
@@ -228,17 +228,19 @@ The OpenAI link:https://platform.openai.com/docs/api-reference/chat/create#chat-
 Spring AI’s link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java[Message] interface facilitates multimodal AI models by introducing the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-commons/src/main/java/org/springframework/ai/content/Media.java[Media] type.
 This type encompasses data and details regarding media attachments in messages, utilizing Spring’s `org.springframework.util.MimeType` and a `org.springframework.core.io.Resource` for the raw media data.
 
-Below is a code example excerpted from link:https://github.com/spring-projects/spring-ai/blob/c9a3e66f90187ce7eae7eb78c462ec622685de6c/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java#L293[OpenAiChatModelIT.java], illustrating the fusion of user text with an image using the `gpt-4o` model.
+Below is a code example excerpted from link:https://github.com/spring-projects/spring-ai/blob/v1.0.0/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java#L469[OpenAiChatModelIT.java], illustrating the fusion of user text with an image using the `gpt-4o` model.
 
 [source,java]
 ----
-var imageResource = new ClassPathResource("/multimodal.test.png");
+var imageData = new ClassPathResource("/test.png");
 
-var userMessage = new UserMessage("Explain what do you see on this picture?",
-        new Media(MimeTypeUtils.IMAGE_PNG, this.imageResource));
+var userMessage = UserMessage.builder()
+    .text("Explain what do you see on this picture?")
+    .media(List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData)))
+    .build();
 
-ChatResponse response = chatModel.call(new Prompt(this.userMessage,
-        OpenAiChatOptions.builder().model(OpenAiApi.ChatModel.GPT_4_O.getValue()).build()));
+var response = this.chatModel
+    .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(modelName).build()));
 ----
 
 TIP: GPT_4_VISION_PREVIEW will continue to be available only to existing users of this model starting June 17, 2024. If you are not an existing user, please use the GPT_4_O or GPT_4_TURBO models. More details https://platform.openai.com/docs/deprecations/2024-06-06-gpt-4-32k-and-vision-preview-models[here]
@@ -247,12 +249,16 @@ or the image URL equivalent using the `gpt-4o` model:
 
 [source,java]
 ----
-var userMessage = new UserMessage("Explain what do you see on this picture?",
-        new Media(MimeTypeUtils.IMAGE_PNG,
-                URI.create("https://docs.spring.io/spring-ai/reference/_images/multimodal.test.png")));
+var userMessage = UserMessage.builder()
+    .text("Explain what do you see on this picture?")
+    .media(List.of(Media.builder()
+        .mimeType(MimeTypeUtils.IMAGE_PNG)
+        .data(URI.create("https://docs.spring.io/spring-ai/reference/_images/multimodal.test.png"))
+        .build()))
+    .build();
 
-ChatResponse response = chatModel.call(new Prompt(this.userMessage,
-        OpenAiChatOptions.builder().model(OpenAiApi.ChatModel.GPT_4_O.getValue()).build()));
+ChatResponse response = this.chatModel
+    .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().model(modelName).build()));
 ----
 
 TIP: You can pass multiple images as well.

From 530ef30caf3f0f50ec506d70bc1cc703bac3e172 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Sat, 14 Jun 2025 03:36:38 +0800
Subject: [PATCH 082/165] doc: Documentation improvements in `MCP Server Boot
 Starter` selection

- Improved the documentation in the `MCP Server Boot Starter` regarding `Starter` selection,
   and added descriptions of precautions for `spring-ai-starter-mcp-server-webflux`.

Fixes: #3511

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit 384478edbf99e28c0a89cd5740d13a4e3f8483e7)
---
 .../ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc   | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
index 9d718c50089..dae75fbc143 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
@@ -80,7 +80,12 @@ The starter activates the `McpWebFluxServerAutoConfiguration` and `McpServerAuto
 * Optional `STDIO` transport (enabled by setting `spring.ai.mcp.server.stdio=true`)
 * Included `spring-boot-starter-webflux` and `mcp-spring-webflux` dependencies
 
-== Configuration Properties
+[NOTE]
+====
+Due to Spring Boot's default behavior, when both `org.springframework.web.servlet.DispatcherServlet` and `org.springframework.web.reactive.DispatcherHandler` are present on the classpath, Spring Boot will prioritize `DispatcherServlet`. As a result, if your project uses `spring-boot-starter-web`, it is recommended to use `spring-ai-starter-mcp-server-webmvc` instead of `spring-ai-starter-mcp-server-webflux`.
+====
+
+Configuration Properties
 
 All properties are prefixed with `spring.ai.mcp.server`:
 

From a5fa66642006be58bb32971b40acbec44f4ec402 Mon Sep 17 00:00:00 2001
From: Hudson Luiz Sales Schumaker <hudson.schumaker@me.com>
Date: Mon, 16 Jun 2025 17:10:02 +0200
Subject: [PATCH 083/165] Update advisors.adoc (#3552)

Signed-off-by: Hudson Luiz Sales Schumaker <hudson.schumaker@me.com>
(cherry picked from commit 97b91f5e6c1ddb19b34b4aceb00263c6ccd2b938)
---
 .../src/main/antora/modules/ROOT/pages/api/advisors.adoc      | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc
index 6b67ea9b27a..4a70fbc8e5e 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc
@@ -14,7 +14,7 @@ You can configure existing advisors using the xref:api/chatclient.adoc#_advisor_
 var chatClient = ChatClient.builder(chatModel)
     .defaultAdvisors(
         MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
-        QuestionAnswerAdvisor.builder((vectorStore).builder() // RAG advisor
+        QuestionAnswerAdvisor.builder(vectorStore).build()    // RAG advisor
     )
     .build();
 
@@ -421,4 +421,4 @@ public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvis
 
     // Method implementation continues...
 }
-----
\ No newline at end of file
+----

From 7361ed9d3341ab823047a457434c5e0d619ad41c Mon Sep 17 00:00:00 2001
From: Guan Huo <9uanhuo@gmail.com>
Date: Mon, 16 Jun 2025 23:14:53 +0800
Subject: [PATCH 084/165] fix: failed to generate
 `spring.ai.tools.observations` metadata (#3539)

Signed-off-by: Jionghui Zheng <9uanhuo@gmail.com>
(cherry picked from commit aada198b4603a6879cc044ff6627f83011135bc3)
---
 .../ai/model/tool/autoconfigure/ToolCallingProperties.java    | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java
index 9ac33eb620a..d34c126e72d 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingProperties.java
@@ -31,6 +31,10 @@ public class ToolCallingProperties {
 
 	private final Observations observations = new Observations();
 
+	public Observations getObservations() {
+		return this.observations;
+	}
+
 	/**
 	 * If true, tool calling errors are thrown as exceptions for the caller to handle. If
 	 * false, errors are converted to messages and sent back to the AI model, allowing it

From 65f4f8ccff4c140be506fe825bf1ea49d370679f Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Tue, 17 Jun 2025 16:57:09 +0900
Subject: [PATCH 085/165] Reuse DocumentRowMapper instance to reduce redundant
 object creation

Fixes #3564

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit af1ce67c8697f4f3ee2d8c80cb9e82cd2cf161bb)
---
 .../ai/vectorstore/pgvector/PgVectorStore.java            | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java b/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java
index cc69b06ab45..ded27db7c73 100644
--- a/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java
+++ b/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java
@@ -153,6 +153,7 @@
  * @author Sebastien Deleuze
  * @author Jihoon Kim
  * @author YeongMin Song
+ * @author Jonghoon Park
  * @since 1.0.0
  */
 public class PgVectorStore extends AbstractObservationVectorStore implements InitializingBean {
@@ -202,6 +203,8 @@ public class PgVectorStore extends AbstractObservationVectorStore implements Ini
 
 	private final ObjectMapper objectMapper;
 
+	private final DocumentRowMapper documentRowMapper;
+
 	private final boolean removeExistingVectorStoreTable;
 
 	private final PgIndexType createIndexMethod;
@@ -219,6 +222,7 @@ protected PgVectorStore(PgVectorStoreBuilder builder) {
 		Assert.notNull(builder.jdbcTemplate, "JdbcTemplate must not be null");
 
 		this.objectMapper = JsonMapper.builder().addModules(JacksonUtils.instantiateAvailableModules()).build();
+		this.documentRowMapper = new DocumentRowMapper(this.objectMapper);
 
 		String vectorTable = builder.vectorTableName;
 		this.vectorTableName = vectorTable.isEmpty() ? DEFAULT_TABLE_NAME : vectorTable.trim();
@@ -372,7 +376,7 @@ public List<Document> doSimilaritySearch(SearchRequest request) {
 		return this.jdbcTemplate.query(
 				String.format(this.getDistanceType().similaritySearchSqlTemplate, getFullyQualifiedTableName(),
 						jsonPathFilter),
-				new DocumentRowMapper(this.objectMapper), queryEmbedding, queryEmbedding, distance, request.getTopK());
+				this.documentRowMapper, queryEmbedding, queryEmbedding, distance, request.getTopK());
 	}
 
 	public List<Double> embeddingDistance(String query) {
@@ -599,8 +603,6 @@ public enum PgDistanceType {
 
 	private static class DocumentRowMapper implements RowMapper<Document> {
 
-		private static final String COLUMN_EMBEDDING = "embedding";
-
 		private static final String COLUMN_METADATA = "metadata";
 
 		private static final String COLUMN_ID = "id";

From 3f9f34df7992119449e64870f341ea5457aaad91 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Sun, 8 Jun 2025 02:09:46 +0800
Subject: [PATCH 086/165] fix: Incorrect outputModalities judgment in stream
 mode

Fixes #3469

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit d9a53378ecccf89352c2750d015b811e8f446bff)
---
 .../springframework/ai/openai/OpenAiChatModel.java  | 10 +++++-----
 .../ai/openai/chat/OpenAiChatModelIT.java           | 13 +++++++++++--
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
index 7da34176c15..a29b1f751a7 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
@@ -271,12 +271,12 @@ public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousCha
 		return Flux.deferContextual(contextView -> {
 			ChatCompletionRequest request = createRequest(prompt, true);
 
-			if (request.outputModalities() != null) {
-				if (request.outputModalities().stream().anyMatch(m -> m.equals("audio"))) {
-					logger.warn("Audio output is not supported for streaming requests. Removing audio output.");
-					throw new IllegalArgumentException("Audio output is not supported for streaming requests.");
-				}
+			if (request.outputModalities() != null
+					&& request.outputModalities().contains(OpenAiApi.OutputModality.AUDIO)) {
+				logger.warn("Audio output is not supported for streaming requests. Removing audio output.");
+				throw new IllegalArgumentException("Audio output is not supported for streaming requests.");
 			}
+
 			if (request.audioParameters() != null) {
 				logger.warn("Audio parameters are not supported for streaming requests. Removing audio parameters.");
 				throw new IllegalArgumentException("Audio parameters are not supported for streaming requests.");
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
index 79f39bd118b..d36d6b89a4c 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
@@ -551,8 +551,7 @@ void multiModalityOutputAudio(String modelName) throws IOException {
 
 	@ParameterizedTest(name = "{0} : {displayName} ")
 	@ValueSource(strings = { "gpt-4o-audio-preview" })
-	void streamingMultiModalityOutputAudio(String modelName) throws IOException {
-		// var audioResource = new ClassPathResource("speech1.mp3");
+	void streamingMultiModalityOutputAudio(String modelName) {
 		var userMessage = new UserMessage("Tell me joke about Spring Framework");
 
 		assertThatThrownBy(() -> this.chatModel
@@ -564,6 +563,16 @@ void streamingMultiModalityOutputAudio(String modelName) throws IOException {
 						.build()))
 			.collectList()
 			.block()).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("Audio output is not supported for streaming requests.");
+
+		assertThatThrownBy(() -> this.chatModel
+			.stream(new Prompt(List.of(userMessage),
+					OpenAiChatOptions.builder()
+						.model(modelName)
+						.outputAudio(new AudioParameters(Voice.ALLOY, AudioResponseFormat.WAV))
+						.build()))
+			.collectList()
+			.block()).isInstanceOf(IllegalArgumentException.class)
 			.hasMessageContaining("Audio parameters are not supported for streaming requests.");
 	}
 

From e0d3703f7321ea18f22d03658e574cbae6475b05 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Tue, 17 Jun 2025 11:53:52 +0100
Subject: [PATCH 087/165] Fix logging of ChromaDB collection creation

Fixes #3542

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
(cherry picked from commit 0d8eebdf4e10fb3b4b4aa8556b7f410ed07d2a24)
---
 .../ai/chroma/vectorstore/ChromaVectorStore.java               | 3 ++-
 .../org/springframework/ai/chroma/vectorstore/ChromaApiIT.java | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java
index b56b99673a5..a6f6628f6c6 100644
--- a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java
+++ b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java
@@ -135,7 +135,8 @@ public void afterPropertiesSet() throws Exception {
 							new ChromaApi.CreateCollectionRequest(this.collectionName));
 				}
 				else {
-					throw new RuntimeException("Collection " + this.collectionName
+					throw new RuntimeException("Collection " + this.collectionName + " with the tenant: "
+							+ this.tenantName + " and the database: " + this.databaseName
 							+ " doesn't exist and won't be created as the initializeSchema is set to false.");
 				}
 			}
diff --git a/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java b/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java
index dc8ccde275e..9c0db87551e 100644
--- a/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java
+++ b/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/chroma/vectorstore/ChromaApiIT.java
@@ -274,7 +274,7 @@ void shouldFailWhenCollectionDoesNotExist() {
 			.hasMessage("Failed to initialize ChromaVectorStore")
 			.hasCauseInstanceOf(RuntimeException.class)
 			.hasRootCauseMessage(
-					"Collection non-existent doesn't exist and won't be created as the initializeSchema is set to false.");
+					"Collection non-existent with the tenant: SpringAiTenant and the database: SpringAiDatabase doesn't exist and won't be created as the initializeSchema is set to false.");
 	}
 
 	@Test

From fb1746b6bd745b9750e4422629faa24f285211b9 Mon Sep 17 00:00:00 2001
From: Daniel Garnier-Moiroux <git@garnier.wf>
Date: Mon, 16 Jun 2025 16:50:23 +0200
Subject: [PATCH 088/165] fix: handle root exceptions in McpToolCallback
 (#3553)

- Sync and Async McpToolCallback can now handle exceptions where
  cause == null instead of throwing a NPE.

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
---
 .../ai/mcp/AsyncMcpToolCallback.java          | 28 +++++-----
 .../ai/mcp/SyncMcpToolCallback.java           | 27 +++++-----
 .../ai/mcp/AsyncMcpToolCallbackTest.java      | 54 +++++++++++++++++++
 .../ai/mcp/SyncMcpToolCallbackTests.java      | 37 +++++++++++++
 4 files changed, 117 insertions(+), 29 deletions(-)
 create mode 100644 mcp/common/src/test/java/org/springframework/ai/mcp/AsyncMcpToolCallbackTest.java

diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
index 9d634b73999..8fb5f745745 100644
--- a/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
+++ b/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
@@ -16,11 +16,12 @@
 
 package org.springframework.ai.mcp;
 
-import java.util.Map;
-
 import io.modelcontextprotocol.client.McpAsyncClient;
+import io.modelcontextprotocol.spec.McpSchema;
 import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
 import io.modelcontextprotocol.spec.McpSchema.Tool;
+import java.util.Map;
+import reactor.core.publisher.Mono;
 
 import org.springframework.ai.chat.model.ToolContext;
 import org.springframework.ai.model.ModelOptionsUtils;
@@ -112,19 +113,16 @@ public String call(String functionInput) {
 		Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
 		// Note that we use the original tool name here, not the adapted one from
 		// getToolDefinition
-		try {
-			return this.asyncMcpClient.callTool(new CallToolRequest(this.tool.name(), arguments)).map(response -> {
-				if (response.isError() != null && response.isError()) {
-					throw new ToolExecutionException(this.getToolDefinition(),
-							new IllegalStateException("Error calling tool: " + response.content()));
-				}
-				return ModelOptionsUtils.toJsonString(response.content());
-			}).block();
-		}
-		catch (Exception ex) {
-			throw new ToolExecutionException(this.getToolDefinition(), ex.getCause());
-		}
-
+		return this.asyncMcpClient.callTool(new CallToolRequest(this.tool.name(), arguments)).onErrorMap(exception -> {
+			// If the tool throws an error during execution
+			throw new ToolExecutionException(this.getToolDefinition(), exception);
+		}).map(response -> {
+			if (response.isError() != null && response.isError()) {
+				throw new ToolExecutionException(this.getToolDefinition(),
+						new IllegalStateException("Error calling tool: " + response.content()));
+			}
+			return ModelOptionsUtils.toJsonString(response.content());
+		}).block();
 	}
 
 	@Override
diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
index 442f21eb89a..fc61d801df1 100644
--- a/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
+++ b/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
@@ -16,13 +16,11 @@
 
 package org.springframework.ai.mcp;
 
-import java.lang.reflect.InvocationTargetException;
-import java.util.Map;
-
 import io.modelcontextprotocol.client.McpSyncClient;
 import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
 import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
 import io.modelcontextprotocol.spec.McpSchema.Tool;
+import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,7 +30,6 @@
 import org.springframework.ai.tool.definition.DefaultToolDefinition;
 import org.springframework.ai.tool.definition.ToolDefinition;
 import org.springframework.ai.tool.execution.ToolExecutionException;
-import org.springframework.core.log.LogAccessor;
 
 /**
  * Implementation of {@link ToolCallback} that adapts MCP tools to Spring AI's tool
@@ -118,22 +115,24 @@ public ToolDefinition getToolDefinition() {
 	@Override
 	public String call(String functionInput) {
 		Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput);
-		// Note that we use the original tool name here, not the adapted one from
-		// getToolDefinition
+
+		CallToolResult response;
 		try {
-			CallToolResult response = this.mcpClient.callTool(new CallToolRequest(this.tool.name(), arguments));
-			if (response.isError() != null && response.isError()) {
-				logger.error("Error calling tool: {}", response.content());
-				throw new ToolExecutionException(this.getToolDefinition(),
-						new IllegalStateException("Error calling tool: " + response.content()));
-			}
-			return ModelOptionsUtils.toJsonString(response.content());
+			// Note that we use the original tool name here, not the adapted one from
+			// getToolDefinition
+			response = this.mcpClient.callTool(new CallToolRequest(this.tool.name(), arguments));
 		}
 		catch (Exception ex) {
 			logger.error("Exception while tool calling: ", ex);
-			throw new ToolExecutionException(this.getToolDefinition(), ex.getCause());
+			throw new ToolExecutionException(this.getToolDefinition(), ex);
 		}
 
+		if (response.isError() != null && response.isError()) {
+			logger.error("Error calling tool: {}", response.content());
+			throw new ToolExecutionException(this.getToolDefinition(),
+					new IllegalStateException("Error calling tool: " + response.content()));
+		}
+		return ModelOptionsUtils.toJsonString(response.content());
 	}
 
 	@Override
diff --git a/mcp/common/src/test/java/org/springframework/ai/mcp/AsyncMcpToolCallbackTest.java b/mcp/common/src/test/java/org/springframework/ai/mcp/AsyncMcpToolCallbackTest.java
new file mode 100644
index 00000000000..e4ceb618efd
--- /dev/null
+++ b/mcp/common/src/test/java/org/springframework/ai/mcp/AsyncMcpToolCallbackTest.java
@@ -0,0 +1,54 @@
+package org.springframework.ai.mcp;
+
+import io.modelcontextprotocol.client.McpAsyncClient;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import reactor.core.publisher.Mono;
+
+import org.springframework.ai.tool.execution.ToolExecutionException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class AsyncMcpToolCallbackTest {
+
+	@Mock
+	private McpAsyncClient mcpClient;
+
+	@Mock
+	private McpSchema.Tool tool;
+
+	@Test
+	void callShouldThrowOnError() {
+		when(this.tool.name()).thenReturn("testTool");
+		var clientInfo = new McpSchema.Implementation("testClient", "1.0.0");
+		when(this.mcpClient.getClientInfo()).thenReturn(clientInfo);
+		var callToolResult = McpSchema.CallToolResult.builder().addTextContent("Some error data").isError(true).build();
+		when(this.mcpClient.callTool(any(McpSchema.CallToolRequest.class))).thenReturn(Mono.just(callToolResult));
+
+		var callback = new AsyncMcpToolCallback(this.mcpClient, this.tool);
+		assertThatThrownBy(() -> callback.call("{\"param\":\"value\"}")).isInstanceOf(ToolExecutionException.class)
+			.cause()
+			.isInstanceOf(IllegalStateException.class)
+			.hasMessage("Error calling tool: [TextContent[audience=null, priority=null, text=Some error data]]");
+	}
+
+	@Test
+	void callShouldWrapReactiveErrors() {
+		when(this.tool.name()).thenReturn("testTool");
+		var clientInfo = new McpSchema.Implementation("testClient", "1.0.0");
+		when(this.mcpClient.getClientInfo()).thenReturn(clientInfo);
+		when(this.mcpClient.callTool(any(McpSchema.CallToolRequest.class)))
+			.thenReturn(Mono.error(new Exception("Testing tool error")));
+
+		var callback = new AsyncMcpToolCallback(this.mcpClient, this.tool);
+		assertThatThrownBy(() -> callback.call("{\"param\":\"value\"}")).isInstanceOf(ToolExecutionException.class)
+			.rootCause()
+			.hasMessage("Testing tool error");
+	}
+
+}
diff --git a/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java b/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java
index 4ed2483e64f..72b04394a49 100644
--- a/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java
+++ b/mcp/common/src/test/java/org/springframework/ai/mcp/SyncMcpToolCallbackTests.java
@@ -16,6 +16,8 @@
 
 package org.springframework.ai.mcp;
 
+import io.modelcontextprotocol.spec.McpSchema;
+import java.util.List;
 import java.util.Map;
 
 import io.modelcontextprotocol.client.McpSyncClient;
@@ -29,8 +31,11 @@
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import org.springframework.ai.chat.model.ToolContext;
+import org.springframework.ai.content.Content;
+import org.springframework.ai.tool.execution.ToolExecutionException;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -94,4 +99,36 @@ void callShouldIgnoreToolContext() {
 		assertThat(response).isNotNull();
 	}
 
+	@Test
+	void callShouldThrowOnError() {
+		when(this.tool.name()).thenReturn("testTool");
+		var clientInfo = new Implementation("testClient", "1.0.0");
+		when(this.mcpClient.getClientInfo()).thenReturn(clientInfo);
+		CallToolResult callResult = mock(CallToolResult.class);
+		when(callResult.isError()).thenReturn(true);
+		when(callResult.content()).thenReturn(List.of(new McpSchema.TextContent("Some error data")));
+		when(this.mcpClient.callTool(any(CallToolRequest.class))).thenReturn(callResult);
+
+		SyncMcpToolCallback callback = new SyncMcpToolCallback(this.mcpClient, this.tool);
+
+		assertThatThrownBy(() -> callback.call("{\"param\":\"value\"}")).isInstanceOf(ToolExecutionException.class)
+			.cause()
+			.isInstanceOf(IllegalStateException.class)
+			.hasMessage("Error calling tool: [TextContent[audience=null, priority=null, text=Some error data]]");
+	}
+
+	@Test
+	void callShouldWrapExceptions() {
+		when(this.tool.name()).thenReturn("testTool");
+		var clientInfo = new Implementation("testClient", "1.0.0");
+		when(this.mcpClient.getClientInfo()).thenReturn(clientInfo);
+		when(this.mcpClient.callTool(any(CallToolRequest.class))).thenThrow(new RuntimeException("Testing tool error"));
+
+		SyncMcpToolCallback callback = new SyncMcpToolCallback(this.mcpClient, this.tool);
+
+		assertThatThrownBy(() -> callback.call("{\"param\":\"value\"}")).isInstanceOf(ToolExecutionException.class)
+			.rootCause()
+			.hasMessage("Testing tool error");
+	}
+
 }

From b6b069cc35fdde86480713fbbd0dabcc4df2f4a9 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Wed, 18 Jun 2025 00:03:31 +0800
Subject: [PATCH 089/165] Fix infinite recursion in `getMimeType(Path)` method

Fixes: #3576

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>

Add missing copyrights

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
(cherry picked from commit ab04e2718f73f36bf179a75a3ff4df92df21ebd2)
---
 .../ai/vertexai/gemini/MimeTypeDetector.java  |  5 +-
 .../gemini/MimeTypeDetectorTests.java         | 92 +++++++++++++++++++
 2 files changed, 95 insertions(+), 2 deletions(-)
 create mode 100644 models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/MimeTypeDetectorTests.java

diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/MimeTypeDetector.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/MimeTypeDetector.java
index fe5e8e52e6e..52b341fec8e 100644
--- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/MimeTypeDetector.java
+++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/MimeTypeDetector.java
@@ -55,7 +55,8 @@ public abstract class MimeTypeDetector {
 	/**
 	 * List of all MIME types supported by the Vertex Gemini API.
 	 */
-	private static final Map<String, MimeType> GEMINI_MIME_TYPES = new HashMap<>();
+	// exposed for testing purposes
+	static final Map<String, MimeType> GEMINI_MIME_TYPES = new HashMap<>();
 
 	public static MimeType getMimeType(URL url) {
 		return getMimeType(url.getFile());
@@ -70,7 +71,7 @@ public static MimeType getMimeType(File file) {
 	}
 
 	public static MimeType getMimeType(Path path) {
-		return getMimeType(path.getFileName());
+		return getMimeType(path.toUri());
 	}
 
 	public static MimeType getMimeType(Resource resource) {
diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/MimeTypeDetectorTests.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/MimeTypeDetectorTests.java
new file mode 100644
index 00000000000..8883c9b598c
--- /dev/null
+++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/MimeTypeDetectorTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2025-2025 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.vertexai.gemini;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import org.springframework.core.io.PathResource;
+import org.springframework.util.MimeType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.ai.vertexai.gemini.MimeTypeDetector.GEMINI_MIME_TYPES;
+
+/**
+ * @author YunKui Lu
+ */
+class MimeTypeDetectorTests {
+
+	private static Stream<Arguments> provideMimeTypes() {
+		return GEMINI_MIME_TYPES.entrySet().stream().map(entry -> Arguments.of(entry.getKey(), entry.getValue()));
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideMimeTypes")
+	void getMimeTypeByURLPath(String extension, MimeType expectedMimeType) throws MalformedURLException {
+		String path = "https://testhost/test." + extension;
+		MimeType mimeType = MimeTypeDetector.getMimeType(URI.create(path).toURL());
+		assertThat(mimeType).isEqualTo(expectedMimeType);
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideMimeTypes")
+	void getMimeTypeByURI(String extension, MimeType expectedMimeType) {
+		String path = "https://testhost/test." + extension;
+		MimeType mimeType = MimeTypeDetector.getMimeType(URI.create(path));
+		assertThat(mimeType).isEqualTo(expectedMimeType);
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideMimeTypes")
+	void getMimeTypeByFile(String extension, MimeType expectedMimeType) {
+		String path = "test." + extension;
+		MimeType mimeType = MimeTypeDetector.getMimeType(new File(path));
+		assertThat(mimeType).isEqualTo(expectedMimeType);
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideMimeTypes")
+	void getMimeTypeByPath(String extension, MimeType expectedMimeType) {
+		String path = "test." + extension;
+		MimeType mimeType = MimeTypeDetector.getMimeType(Path.of(path));
+		assertThat(mimeType).isEqualTo(expectedMimeType);
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideMimeTypes")
+	void getMimeTypeByResource(String extension, MimeType expectedMimeType) {
+		String path = "test." + extension;
+		MimeType mimeType = MimeTypeDetector.getMimeType(new PathResource(path));
+		assertThat(mimeType).isEqualTo(expectedMimeType);
+	}
+
+	@ParameterizedTest
+	@MethodSource("provideMimeTypes")
+	void getMimeTypeByString(String extension, MimeType expectedMimeType) {
+		String path = "test." + extension;
+		MimeType mimeType = MimeTypeDetector.getMimeType(path);
+		assertThat(mimeType).isEqualTo(expectedMimeType);
+	}
+
+}

From 3c050b4f59dfec85af41f288ec6773e19b78545e Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Wed, 18 Jun 2025 07:36:17 +0800
Subject: [PATCH 090/165] Fix exception in
 `OpenAiAudioTranscriptionResponseMetadata.toString` (#3575)

Fixes: #3575

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit 1cb584a9f3fdc17077d8e86a138a2afa7735da36)
---
 .../audio/OpenAiAudioTranscriptionResponseMetadata.java         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java
index 106c9d7264e..005bbb5c422 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/audio/OpenAiAudioTranscriptionResponseMetadata.java
@@ -38,7 +38,7 @@ public class OpenAiAudioTranscriptionResponseMetadata extends AudioTranscription
 
 	};
 
-	protected static final String AI_METADATA_STRING = "{ @type: %1$s, rateLimit: %4$s }";
+	protected static final String AI_METADATA_STRING = "{ @type: %1$s, rateLimit: %2$s }";
 
 	@Nullable
 	private RateLimit rateLimit;

From 7200580ec16d063fa228baec19ebcadd984e9922 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Wed, 18 Jun 2025 15:36:09 +0900
Subject: [PATCH 091/165] Fix Error in ChromaVectorStoreAutoConfigurationIT Due
 to Updated Exception Message (#3581)

Fixes #3581

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit 17c1156b06c15cba7c728231c14a9203617efa4d)
---
 .../autoconfigure/ChromaVectorStoreAutoConfigurationIT.java  | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java
index a96073bfbdf..4a3a9ec6ebd 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/src/test/java/org/springframework/ai/vectorstore/chroma/autoconfigure/ChromaVectorStoreAutoConfigurationIT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -60,6 +60,7 @@
  * @author Eddú Meléndez
  * @author Soby Chacko
  * @author Thomas Vitale
+ * @author Jonghoon Park
  */
 @Testcontainers
 public class ChromaVectorStoreAutoConfigurationIT {
@@ -182,7 +183,7 @@ public void throwExceptionOnMissingCollectionAndDisabledInitializedSchema() {
 				.hasCauseInstanceOf(BeanCreationException.class)
 				.hasRootCauseExactlyInstanceOf(RuntimeException.class)
 				.hasRootCauseMessage(
-						"Collection TestCollection doesn't exist and won't be created as the initializeSchema is set to false."));
+						"Collection TestCollection with the tenant: SpringAiTenant and the database: SpringAiDatabase doesn't exist and won't be created as the initializeSchema is set to false."));
 	}
 
 	@Test

From 8928da4640508bec8bb34b446d8adbb424563f83 Mon Sep 17 00:00:00 2001
From: Guan Huo <9uanhuo@gmail.com>
Date: Wed, 18 Jun 2025 17:34:38 +0800
Subject: [PATCH 092/165] docs: fix broken link to `ChatModel` (#3583)

Fixes #3583
Signed-off-by: Jionghui Zheng <9uanhuo@gmail.com>
(cherry picked from commit 968270280c6833ec3188bda18b279a30ae2ebddd)
---
 .../antora/modules/ROOT/pages/api/chatmodel.adoc   | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc
index 04afcc46eca..766dbd7736c 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc
@@ -19,11 +19,11 @@ This section provides a guide to the Spring AI Chat Model API interface and asso
 
 === ChatModel
 
-Here is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat//model/ChatModel.java[ChatModel] interface definition:
+Here is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ChatModel.java[ChatModel] interface definition:
 
 [source,java]
 ----
-public interface ChatModel extends Model<Prompt, ChatResponse> {
+public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {
 
 	default String call(String message) {...}
 
@@ -82,7 +82,8 @@ The `Message` interface encapsulates a `Prompt` textual content, a collection of
 
 The interface is defined as follows:
 
-```java
+[source,java]
+----
 public interface Content {
 
 	String getText();
@@ -94,17 +95,18 @@ public interface Message extends Content {
 
 	MessageType getMessageType();
 }
-```
+----
 
 The multimodal message types implement also the `MediaContent` interface providing a list of `Media` content objects.
 
-```java
+[source,java]
+----
 public interface MediaContent extends Content {
 
 	Collection<Media> getMedia();
 
 }
-```
+----
 
 The `Message` interface has various implementations that correspond to the categories of messages that an AI model can process:
 

From eae78b71b1ba8fd35e539d60587b208deb1ae436 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Wed, 18 Jun 2025 17:47:27 +0800
Subject: [PATCH 093/165] Remove code that's actually unused. (#3574)

Fixes #3574

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit 3b94838fb7b149041490e1e518a0ce8d27533321)
---
 .../ai/model/tool/DefaultToolCallingManager.java              | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java
index 887ba56bb72..5149a98a85c 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java
@@ -153,10 +153,6 @@ private static ToolContext buildToolContext(Prompt prompt, AssistantMessage assi
 				&& !CollectionUtils.isEmpty(toolCallingChatOptions.getToolContext())) {
 			toolContextMap = new HashMap<>(toolCallingChatOptions.getToolContext());
 
-			List<Message> messageHistory = new ArrayList<>(prompt.copy().getInstructions());
-			messageHistory.add(new AssistantMessage(assistantMessage.getText(), assistantMessage.getMetadata(),
-					assistantMessage.getToolCalls()));
-
 			toolContextMap.put(ToolContext.TOOL_CALL_HISTORY,
 					buildConversationHistoryBeforeToolExecution(prompt, assistantMessage));
 		}

From 4e25038e708de9d56a9cc07268236406234f81da Mon Sep 17 00:00:00 2001
From: qwp_p <70431503+RobinElysia@users.noreply.github.com>
Date: Wed, 18 Jun 2025 17:59:10 +0800
Subject: [PATCH 094/165] Update tools.adoc (#3468)

There shouldn't be a method here.

Fixes #3468

Signed-off-by: qwp_p <70431503+RobinElysia@users.noreply.github.com>
(cherry picked from commit 23c38df7d7fb18e0a64158db8ef9eae04c00be82)
---
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index 28b6f0919d5..a945f395574 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -316,7 +316,7 @@ The `ToolMetadata.Builder` allows you to build a `ToolMetadata` instance and def
 ----
 Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
 ToolCallback toolCallback = MethodToolCallback.builder()
-    .toolDefinition(ToolDefinitions.builder(method)
+    .toolDefinition(ToolDefinitions.builder()
             .description("Get the current date and time in the user's timezone")
             .build())
     .toolMethod(method)

From 42eab958fd786c8a1f65f5b0447579f33d41d4ed Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Wed, 18 Jun 2025 11:27:25 +0100
Subject: [PATCH 095/165] Revert "Update tools.adoc (#3468)"

This reverts commit 23c38df7d7fb18e0a64158db8ef9eae04c00be82.

(cherry picked from commit 5036ed37e5da9a02fbe0c5ed96e0887e5955e23e)
---
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index a945f395574..28b6f0919d5 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -316,7 +316,7 @@ The `ToolMetadata.Builder` allows you to build a `ToolMetadata` instance and def
 ----
 Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
 ToolCallback toolCallback = MethodToolCallback.builder()
-    .toolDefinition(ToolDefinitions.builder()
+    .toolDefinition(ToolDefinitions.builder(method)
             .description("Get the current date and time in the user's timezone")
             .build())
     .toolMethod(method)

From ccfaaf1f4823906e46d64b3bd44122fb9c0e016d Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Fri, 6 Jun 2025 18:42:55 +0800
Subject: [PATCH 096/165] feat: `ChatClient#mutate` adds copies of `advisors`
 and `advisorParams`.

Fixes #3459

- The `mutate()` method adds copies of `advisors` and `advisorParams`.
- Add unit test to validate `mutate()` behavior.

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit f391ff2e98f577e247edbd4f8f0f1a08bfbeab16)
---
 .../ai/chat/client/DefaultChatClient.java     |  6 +++
 .../chat/client/DefaultChatClientTests.java   | 47 +++++++++++++++++++
 2 files changed, 53 insertions(+)

diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
index f5a1e8cd11a..28089504925 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
@@ -61,6 +61,7 @@
 import org.springframework.core.io.Resource;
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
 import org.springframework.util.MimeType;
 import org.springframework.util.StringUtils;
 
@@ -705,6 +706,7 @@ public TemplateRenderer getTemplateRenderer() {
 		 * Return a {@code ChatClient2Builder} to create a new {@code ChatClient2} whose
 		 * settings are replicated from this {@code ChatClientRequest}.
 		 */
+		@Override
 		public Builder mutate() {
 			DefaultChatClientBuilder builder = (DefaultChatClientBuilder) ChatClient
 				.builder(this.chatModel, this.observationRegistry, this.observationConvention)
@@ -713,6 +715,10 @@ public Builder mutate() {
 				.defaultToolContext(this.toolContext)
 				.defaultToolNames(StringUtils.toStringArray(this.toolNames));
 
+			if (!CollectionUtils.isEmpty(this.advisors)) {
+				builder.defaultAdvisors(a -> a.advisors(this.advisors).params(this.advisorParams));
+			}
+
 			if (StringUtils.hasText(this.userText)) {
 				builder.defaultUser(
 						u -> u.text(this.userText).params(this.userParams).media(this.media.toArray(new Media[0])));
diff --git a/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java b/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java
index fd795971a2e..1c72596f490 100644
--- a/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java
+++ b/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/DefaultChatClientTests.java
@@ -48,6 +48,7 @@
 import org.springframework.ai.content.Media;
 import org.springframework.ai.converter.ListOutputConverter;
 import org.springframework.ai.converter.StructuredOutputConverter;
+import org.springframework.ai.template.TemplateRenderer;
 import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.function.FunctionToolCallback;
 import org.springframework.core.ParameterizedTypeReference;
@@ -60,6 +61,7 @@
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * Unit tests for {@link DefaultChatClient}.
@@ -124,6 +126,51 @@ void whenPromptWithOptionsThenReturn() {
 		assertThat(spec.getChatOptions()).isEqualTo(chatOptions);
 	}
 
+	@Test
+	void testMutate() {
+		var media = mock(Media.class);
+		var toolCallback = mock(ToolCallback.class);
+		var advisor = mock(Advisor.class);
+		var templateRenderer = mock(TemplateRenderer.class);
+		var chatOptions = mock(ChatOptions.class);
+		var copyChatOptions = mock(ChatOptions.class);
+		when(chatOptions.copy()).thenReturn(copyChatOptions);
+		var toolContext = new HashMap<String, Object>();
+		var userMessage1 = mock(UserMessage.class);
+		var userMessage2 = mock(UserMessage.class);
+
+		DefaultChatClientBuilder defaultChatClientBuilder = new DefaultChatClientBuilder(mock(ChatModel.class));
+		defaultChatClientBuilder.addMessages(List.of(userMessage1, userMessage2));
+		ChatClient originalChatClient = defaultChatClientBuilder.defaultAdvisors(advisor)
+			.defaultOptions(chatOptions)
+			.defaultUser(u -> u.text("original user {userParams}").param("userParams", "user value2").media(media))
+			.defaultSystem(s -> s.text("original system {sysParams}").param("sysParams", "system value1"))
+			.defaultTemplateRenderer(templateRenderer)
+			.defaultToolNames("toolName1", "toolName2")
+			.defaultToolCallbacks(toolCallback)
+			.defaultToolContext(toolContext)
+			.build();
+		var originalSpec = (DefaultChatClient.DefaultChatClientRequestSpec) originalChatClient.prompt();
+
+		ChatClient mutateChatClient = originalChatClient.mutate().build();
+		var mutateSpec = (DefaultChatClient.DefaultChatClientRequestSpec) mutateChatClient.prompt();
+
+		assertThat(mutateSpec).isNotSameAs(originalSpec);
+
+		assertThat(mutateSpec.getMessages()).hasSize(2).containsOnly(userMessage1, userMessage2);
+		assertThat(mutateSpec.getAdvisors()).hasSize(1).containsOnly(advisor);
+		assertThat(mutateSpec.getChatOptions()).isEqualTo(copyChatOptions);
+		assertThat(mutateSpec.getUserText()).isEqualTo("original user {userParams}");
+		assertThat(mutateSpec.getUserParams()).containsEntry("userParams", "user value2");
+		assertThat(mutateSpec.getMedia()).hasSize(1).containsOnly(media);
+		assertThat(mutateSpec.getSystemText()).isEqualTo("original system {sysParams}");
+		assertThat(mutateSpec.getSystemParams()).containsEntry("sysParams", "system value1");
+		assertThat(mutateSpec.getTemplateRenderer()).isEqualTo(templateRenderer);
+		assertThat(mutateSpec.getToolNames()).containsExactly("toolName1", "toolName2");
+		assertThat(mutateSpec.getToolCallbacks()).containsExactly(toolCallback);
+		assertThat(mutateSpec.getToolContext()).isEqualTo(toolContext);
+	}
+
 	@Test
 	void whenMutateChatClientRequest() {
 		ChatClient chatClient = new DefaultChatClientBuilder(mock(ChatModel.class)).build();

From 99822a3a3171ba0a243ce981bb1e02833781fde5 Mon Sep 17 00:00:00 2001
From: yiangjm <32185689+yangjm-41@users.noreply.github.com>
Date: Thu, 29 May 2025 22:05:10 +0800
Subject: [PATCH 097/165] Update OpenAiChatModel.java

Fixes #3376

Fix NullPointerException caused by empty choice.index() and Map.of() with null value

- Resolve the issue where choice.index() returns an empty value, leading to a NullPointerException.
- Ensure that Map.of() does not accept null values to prevent runtime errors.
- Implement null checks and provide default values to handle edge cases gracefully.
- Update unit tests to cover scenarios where choice.index() is empty or null.

Signed-off-by: yiangjm <32185689+yangjm-41@users.noreply.github.com>
(cherry picked from commit 37b83fd9c2474ded5bae918e286588574d18e94a)
---
 .../java/org/springframework/ai/openai/OpenAiChatModel.java   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
index a29b1f751a7..a4a84a78054 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java
@@ -216,7 +216,7 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons
 					Map<String, Object> metadata = Map.of(
 							"id", chatCompletion.id() != null ? chatCompletion.id() : "",
 							"role", choice.message().role() != null ? choice.message().role().name() : "",
-							"index", choice.index(),
+							"index", choice.index() != null ? choice.index() : 0,
 							"finishReason", choice.finishReason() != null ? choice.finishReason().name() : "",
 							"refusal", StringUtils.hasText(choice.message().refusal()) ? choice.message().refusal() : "",
 							"annotations", choice.message().annotations() != null ? choice.message().annotations() : List.of(Map.of()));
@@ -315,7 +315,7 @@ public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousCha
 							Map<String, Object> metadata = Map.of(
 									"id", id,
 									"role", roleMap.getOrDefault(id, ""),
-									"index", choice.index(),
+									"index", choice.index() != null ? choice.index() : 0,
 									"finishReason", choice.finishReason() != null ? choice.finishReason().name() : "",
 									"refusal", StringUtils.hasText(choice.message().refusal()) ? choice.message().refusal() : "",
 									"annotations", choice.message().annotations() != null ? choice.message().annotations() : List.of());

From e39c0cc7157aeaf58e79987ee13ce63f696e0853 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Thu, 19 Jun 2025 14:38:46 +0800
Subject: [PATCH 098/165] refactor: Replace `System.err.println` with log
 output (#3600)

Fixes #3600

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Co-authored-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit 49df56cfbd626b68a8c1b3d9d9c5b6447ce6d57b)
---
 .../ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java     | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java
index 25903d880e1..dcc10f09c84 100644
--- a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java
+++ b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java
@@ -27,6 +27,8 @@
 import org.apache.pdfbox.text.PDFTextStripper;
 import org.apache.pdfbox.text.TextPosition;
 import org.apache.pdfbox.text.TextPositionComparator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * This class extends PDFTextStripper to provide custom text extraction and formatting
@@ -38,6 +40,8 @@
  */
 public class ForkPDFLayoutTextStripper extends PDFTextStripper {
 
+	private final static Logger logger = LoggerFactory.getLogger(ForkPDFLayoutTextStripper.class);
+
 	public static final boolean DEBUG = false;
 
 	public static final int OUTPUT_SPACE_CHARACTER_WIDTH_IN_PT = 4;
@@ -80,7 +84,7 @@ protected void writePage() throws IOException {
 				this.sortTextPositionList(textList);
 			}
 			catch (java.lang.IllegalArgumentException e) {
-				System.err.println(e);
+				logger.error("Error sorting text positions", e);
 			}
 			this.iterateThroughTextList(textList.iterator());
 		}

From ff5855d4896b30a31f39776744a4c9e8c471e571 Mon Sep 17 00:00:00 2001
From: Guo <ggndnn@163.com>
Date: Fri, 23 May 2025 10:14:57 +0800
Subject: [PATCH 099/165] fix(deepseek): prevent incorrect ToolCall merging
 caused by empty id and name strings

Fixes #3534

Signed-off-by: Guo <ggndnn@163.com>
(cherry picked from commit d61192a63598223ca3b8035fe2ab243b3e691345)
---
 .../deepseek/api/DeepSeekStreamFunctionCallingHelper.java  | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekStreamFunctionCallingHelper.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekStreamFunctionCallingHelper.java
index 68cbe2a4b93..063808c2a1d 100644
--- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekStreamFunctionCallingHelper.java
+++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekStreamFunctionCallingHelper.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionMessage.Role;
 import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionMessage.ToolCall;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 /**
  * Helper class to support Streaming function calling. It can merge the streamed
@@ -95,7 +96,7 @@ private ChatCompletionMessage merge(ChatCompletionMessage previous, ChatCompleti
 				throw new IllegalStateException("Currently only one tool call is supported per message!");
 			}
 			var currentToolCall = current.toolCalls().iterator().next();
-			if (currentToolCall.id() != null) {
+			if (StringUtils.hasText(currentToolCall.id())) {
 				if (lastPreviousTooCall != null) {
 					toolCalls.add(lastPreviousTooCall);
 				}
@@ -117,7 +118,7 @@ private ToolCall merge(ToolCall previous, ToolCall current) {
 		if (previous == null) {
 			return current;
 		}
-		String id = (current.id() != null ? current.id() : previous.id());
+		String id = (StringUtils.hasText(current.id()) ? current.id() : previous.id());
 		String type = (current.type() != null ? current.type() : previous.type());
 		ChatCompletionFunction function = merge(previous.function(), current.function());
 		return new ToolCall(id, type, function);
@@ -127,7 +128,7 @@ private ChatCompletionFunction merge(ChatCompletionFunction previous, ChatComple
 		if (previous == null) {
 			return current;
 		}
-		String name = (current.name() != null ? current.name() : previous.name());
+		String name = (StringUtils.hasText(current.name()) ? current.name() : previous.name());
 		StringBuilder arguments = new StringBuilder();
 		if (previous.arguments() != null) {
 			arguments.append(previous.arguments());

From 4f520f8e68741c000bbed12261946d36c1c83eca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=BE=90=E5=8A=9F=E6=98=8E?=
 <35555791+XuGongming@users.noreply.github.com>
Date: Thu, 19 Jun 2025 18:35:56 +0800
Subject: [PATCH 100/165] Update OpenAiStreamFunctionCallingHelper.java
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fixes #3534

When deploying qwen3 using vllm version 0.9.1, the ‘current. toolCalls()’ will result in an empty array

Signed-off-by: 徐功明 <35555791+XuGongming@users.noreply.github.com>
(cherry picked from commit a221a3784e6d8b1d4c6cdb5be50f127286f6312d)
---
 .../ai/openai/api/OpenAiStreamFunctionCallingHelper.java        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelper.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelper.java
index e159d0362c9..96c43655d3f 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelper.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiStreamFunctionCallingHelper.java
@@ -110,7 +110,7 @@ private ChatCompletionMessage merge(ChatCompletionMessage previous, ChatCompleti
 				toolCalls.addAll(previous.toolCalls().subList(0, previous.toolCalls().size() - 1));
 			}
 		}
-		if (current.toolCalls() != null) {
+		if (current.toolCalls() != null && current.toolCalls().size() > 0) {
 			if (current.toolCalls().size() > 1) {
 				throw new IllegalStateException("Currently only one tool call is supported per message!");
 			}

From 1805ca07df968f4f38db06b88f9f36afe9aa6d7f Mon Sep 17 00:00:00 2001
From: WOONBE <kepull2918@naver.com>
Date: Mon, 16 Jun 2025 15:39:56 +0900
Subject: [PATCH 101/165] refactor : refactor MessageAggregator to include
 toolCalls

test: Add unit test for MessageAggregator tool call aggregation

Fixes #3366
Signed-off-by: WOONBE <kepull2918@naver.com>
(cherry picked from commit df90b9c1b31079c2ee9249689fc0bc05d3e04e18)
---
 .../ai/chat/model/MessageAggregator.java      | 36 ++++++++++++-
 .../ai/chat/model/ChatResponseTests.java      | 50 ++++++++++++++++++-
 2 files changed, 82 insertions(+), 4 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java
index 839d99e23d8..222d53d426f 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java
@@ -16,6 +16,7 @@
 
 package org.springframework.ai.chat.model;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -24,6 +25,7 @@
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
 import reactor.core.publisher.Flux;
 
 import org.springframework.ai.chat.messages.AssistantMessage;
@@ -35,6 +37,8 @@
 import org.springframework.ai.chat.metadata.Usage;
 import org.springframework.util.StringUtils;
 
+import static org.springframework.ai.chat.messages.AssistantMessage.*;
+
 /**
  * Helper that for streaming chat responses, aggregate the chat response messages into a
  * single AssistantMessage. Job is performed in parallel to the chat response processing.
@@ -42,6 +46,7 @@
  * @author Christian Tzolov
  * @author Alexandros Pappas
  * @author Thomas Vitale
+ * @author Heonwoo Kim
  * @since 1.0.0
  */
 public class MessageAggregator {
@@ -54,6 +59,7 @@ public Flux<ChatResponse> aggregate(Flux<ChatResponse> fluxChatResponse,
 		// Assistant Message
 		AtomicReference<StringBuilder> messageTextContentRef = new AtomicReference<>(new StringBuilder());
 		AtomicReference<Map<String, Object>> messageMetadataMapRef = new AtomicReference<>();
+		AtomicReference<List<ToolCall>> toolCallsRef = new AtomicReference<>(new ArrayList<>());
 
 		// ChatGeneration Metadata
 		AtomicReference<ChatGenerationMetadata> generationMetadataRef = new AtomicReference<>(
@@ -73,6 +79,7 @@ public Flux<ChatResponse> aggregate(Flux<ChatResponse> fluxChatResponse,
 		return fluxChatResponse.doOnSubscribe(subscription -> {
 			messageTextContentRef.set(new StringBuilder());
 			messageMetadataMapRef.set(new HashMap<>());
+			toolCallsRef.set(new ArrayList<>());
 			metadataIdRef.set("");
 			metadataModelRef.set("");
 			metadataUsagePromptTokensRef.set(0);
@@ -94,6 +101,11 @@ public Flux<ChatResponse> aggregate(Flux<ChatResponse> fluxChatResponse,
 				if (chatResponse.getResult().getOutput().getMetadata() != null) {
 					messageMetadataMapRef.get().putAll(chatResponse.getResult().getOutput().getMetadata());
 				}
+				AssistantMessage outputMessage = chatResponse.getResult().getOutput();
+				if (!CollectionUtils.isEmpty(outputMessage.getToolCalls())) {
+					toolCallsRef.get().addAll(outputMessage.getToolCalls());
+				}
+
 			}
 			if (chatResponse.getMetadata() != null) {
 				if (chatResponse.getMetadata().getUsage() != null) {
@@ -119,6 +131,13 @@ public Flux<ChatResponse> aggregate(Flux<ChatResponse> fluxChatResponse,
 				if (StringUtils.hasText(chatResponse.getMetadata().getModel())) {
 					metadataModelRef.set(chatResponse.getMetadata().getModel());
 				}
+				Object toolCallsFromMetadata = chatResponse.getMetadata().get("toolCalls");
+				if (toolCallsFromMetadata instanceof List) {
+					@SuppressWarnings("unchecked")
+					List<ToolCall> toolCallsList = (List<ToolCall>) toolCallsFromMetadata;
+					toolCallsRef.get().addAll(toolCallsList);
+				}
+
 			}
 		}).doOnComplete(() -> {
 
@@ -133,12 +152,25 @@ public Flux<ChatResponse> aggregate(Flux<ChatResponse> fluxChatResponse,
 				.promptMetadata(metadataPromptMetadataRef.get())
 				.build();
 
-			onAggregationComplete.accept(new ChatResponse(List.of(new Generation(
-					new AssistantMessage(messageTextContentRef.get().toString(), messageMetadataMapRef.get()),
+			AssistantMessage finalAssistantMessage;
+			List<ToolCall> collectedToolCalls = toolCallsRef.get();
+
+			if (!CollectionUtils.isEmpty(collectedToolCalls)) {
+
+				finalAssistantMessage = new AssistantMessage(messageTextContentRef.get().toString(),
+						messageMetadataMapRef.get(), collectedToolCalls);
+			}
+			else {
+				finalAssistantMessage = new AssistantMessage(messageTextContentRef.get().toString(),
+						messageMetadataMapRef.get());
+			}
+			onAggregationComplete.accept(new ChatResponse(List.of(new Generation(finalAssistantMessage,
+
 					generationMetadataRef.get())), chatResponseMetadata));
 
 			messageTextContentRef.set(new StringBuilder());
 			messageMetadataMapRef.set(new HashMap<>());
+			toolCallsRef.set(new ArrayList<>());
 			metadataIdRef.set("");
 			metadataModelRef.set("");
 			metadataUsagePromptTokensRef.set(0);
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/chat/model/ChatResponseTests.java b/spring-ai-model/src/test/java/org/springframework/ai/chat/model/ChatResponseTests.java
index a9d673e23be..08cdc993fba 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/chat/model/ChatResponseTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/chat/model/ChatResponseTests.java
@@ -19,27 +19,32 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.junit.jupiter.api.Test;
 
 import org.springframework.ai.chat.messages.AssistantMessage;
 import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
+import org.springframework.ai.chat.metadata.ChatResponseMetadata;
+import reactor.core.publisher.Flux;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.springframework.ai.chat.messages.AssistantMessage.*;
 
 /**
  * Unit tests for {@link ChatResponse}.
  *
  * @author Thomas Vitale
+ * @author Heonwoo Kim
  */
 class ChatResponseTests {
 
 	@Test
 	void whenToolCallsArePresentThenReturnTrue() {
 		ChatResponse chatResponse = ChatResponse.builder()
-			.generations(List.of(new Generation(new AssistantMessage("", Map.of(),
-					List.of(new AssistantMessage.ToolCall("toolA", "function", "toolA", "{}"))))))
+			.generations(List.of(new Generation(
+					new AssistantMessage("", Map.of(), List.of(new ToolCall("toolA", "function", "toolA", "{}"))))))
 			.build();
 		assertThat(chatResponse.hasToolCalls()).isTrue();
 	}
@@ -80,4 +85,45 @@ void whenFinishReasonIsNotPresent() {
 		assertThat(chatResponse.hasFinishReasons(Set.of("completed"))).isFalse();
 	}
 
+	@Test
+	void messageAggregatorShouldCorrectlyAggregateToolCallsFromStream() {
+
+		MessageAggregator aggregator = new MessageAggregator();
+
+		ChatResponse chunk1 = new ChatResponse(
+				List.of(new Generation(new AssistantMessage("Thinking about the weather... "))));
+
+		ToolCall weatherToolCall = new ToolCall("tool-id-123", "function", "getCurrentWeather",
+				"{\"location\": \"Seoul\"}");
+
+		Map<String, Object> metadataWithToolCall = Map.of("toolCalls", List.of(weatherToolCall));
+		ChatResponseMetadata responseMetadataForChunk2 = ChatResponseMetadata.builder()
+			.metadata(metadataWithToolCall)
+			.build();
+
+		ChatResponse chunk2 = new ChatResponse(List.of(new Generation(new AssistantMessage(""))),
+				responseMetadataForChunk2);
+
+		Flux<ChatResponse> streamingResponse = Flux.just(chunk1, chunk2);
+
+		AtomicReference<ChatResponse> aggregatedResponseRef = new AtomicReference<>();
+
+		aggregator.aggregate(streamingResponse, aggregatedResponseRef::set).blockLast();
+
+		ChatResponse finalResponse = aggregatedResponseRef.get();
+		assertThat(finalResponse).isNotNull();
+
+		AssistantMessage finalAssistantMessage = finalResponse.getResult().getOutput();
+
+		assertThat(finalAssistantMessage).isNotNull();
+		assertThat(finalAssistantMessage.getText()).isEqualTo("Thinking about the weather... ");
+		assertThat(finalAssistantMessage.hasToolCalls()).isTrue();
+		assertThat(finalAssistantMessage.getToolCalls()).hasSize(1);
+
+		ToolCall resultToolCall = finalAssistantMessage.getToolCalls().get(0);
+		assertThat(resultToolCall.id()).isEqualTo("tool-id-123");
+		assertThat(resultToolCall.name()).isEqualTo("getCurrentWeather");
+		assertThat(resultToolCall.arguments()).isEqualTo("{\"location\": \"Seoul\"}");
+	}
+
 }

From d1eb9be6546854e5869385f22d0716bf4b489d8b Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Fri, 20 Jun 2025 01:25:44 +0900
Subject: [PATCH 102/165] chore: Remove unnecessary environment variable
 validation in QdrantVectorStoreIT (#3615)

Fixes #3615
Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit c47320dbd1ee64d2b2af4199844debb5d47ca631)
---
 .../ai/vectorstore/qdrant/QdrantVectorStoreIT.java         | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
index b2af2ab758c..2a943e895a1 100644
--- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
+++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
@@ -58,11 +58,11 @@
  * @author Eddú Meléndez
  * @author Thomas Vitale
  * @author Soby Chacko
+ * @author Jonghoon Park
  * @since 0.8.1
  */
 @Testcontainers
-@EnabledIfEnvironmentVariables({ @EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".+"),
-		@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") })
+@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".+")
 public class QdrantVectorStoreIT extends BaseVectorStoreTests {
 
 	private static final String COLLECTION_NAME = "test_collection";
@@ -73,8 +73,7 @@ public class QdrantVectorStoreIT extends BaseVectorStoreTests {
 	static QdrantContainer qdrantContainer = new QdrantContainer(QdrantImage.DEFAULT_IMAGE);
 
 	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
-		.withUserConfiguration(TestApplication.class)
-		.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"));
+		.withUserConfiguration(TestApplication.class);
 
 	List<Document> documents = List.of(
 			new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!",

From 5a66d4967b1b5c5899330eac2819ca33cd694f13 Mon Sep 17 00:00:00 2001
From: Piotr Kubowicz <piotr.kubowicz@gmail.com>
Date: Tue, 10 Jun 2025 15:33:02 +0200
Subject: [PATCH 103/165] build: Use swagger-annotations-jakarta instead of
 swagger-annotations

Functionality is the same, but modern applications won't use javax
variant of Swagger JARs, so the dependency on swagger-annotations caused
duplicate classes on the classpath.

Signed-off-by: Piotr Kubowicz <piotr.kubowicz@gmail.com>
---
 models/spring-ai-huggingface/pom.xml | 6 +++---
 spring-ai-client-chat/pom.xml        | 2 +-
 spring-ai-model/pom.xml              | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/models/spring-ai-huggingface/pom.xml b/models/spring-ai-huggingface/pom.xml
index 0fa5852c9a8..31f96931bcd 100644
--- a/models/spring-ai-huggingface/pom.xml
+++ b/models/spring-ai-huggingface/pom.xml
@@ -47,11 +47,11 @@
 			<version>${project.parent.version}</version>
 		</dependency>
 
-		<!-- https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-annotations -->
+		<!-- https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-annotations-jakarta -->
 		<dependency>
 			<groupId>io.swagger.core.v3</groupId>
-			<artifactId>swagger-annotations</artifactId>
-			<version>2.2.15</version>
+			<artifactId>swagger-annotations-jakarta</artifactId>
+			<version>${swagger-annotations.version}</version>
 		</dependency>
 
 		<dependency>
diff --git a/spring-ai-client-chat/pom.xml b/spring-ai-client-chat/pom.xml
index 282991ac160..47c39410654 100644
--- a/spring-ai-client-chat/pom.xml
+++ b/spring-ai-client-chat/pom.xml
@@ -51,7 +51,7 @@
 
 		<dependency>
 			<groupId>io.swagger.core.v3</groupId>
-			<artifactId>swagger-annotations</artifactId>
+			<artifactId>swagger-annotations-jakarta</artifactId>
 			<version>${swagger-annotations.version}</version>
 		</dependency>
 
diff --git a/spring-ai-model/pom.xml b/spring-ai-model/pom.xml
index a5333673b04..ebd7be65fb1 100644
--- a/spring-ai-model/pom.xml
+++ b/spring-ai-model/pom.xml
@@ -112,7 +112,7 @@
 
 		<dependency>
 			<groupId>io.swagger.core.v3</groupId>
-			<artifactId>swagger-annotations</artifactId>
+			<artifactId>swagger-annotations-jakarta</artifactId>
 			<version>${swagger-annotations.version}</version>
 		</dependency>
 

From d1dac6c32a13cfd2daa4c3a2e6a01950bca10dd7 Mon Sep 17 00:00:00 2001
From: Christian Tzolov <christian.tzolov@broadcom.com>
Date: Thu, 19 Jun 2025 21:27:11 +0200
Subject: [PATCH 104/165] fix(bedrock): Handle streaming tool calling with no
 input arguments

- Fix isEmpty() check in ConverseApiUtils to properly handle null partialJson
- Add workaround for streaming tool calls with empty arguments by defaulting to {}
- Add parameterized tests for multiple Bedrock models (Nova Pro and Claude Sonnet)
- Update AWS SDK and Bedrock runtime versions to 2.31.65

Fixes #1878

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
---
 .../ai/bedrock/converse/api/ConverseApiUtils.java      |  7 ++++---
 .../converse/client/BedrockNovaChatClientIT.java       | 10 ++++++++--
 pom.xml                                                |  4 ++--
 3 files changed, 14 insertions(+), 7 deletions(-)

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 a19de831a7e..d58fdbad8cf 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
@@ -421,8 +421,7 @@ public List<ToolUseEntry> toolUseEntries() {
 		}
 
 		public boolean isEmpty() {
-			return (this.index == null || this.id == null || this.name == null
-					|| !StringUtils.hasText(this.partialJson));
+			return (this.index == null || this.id == null || this.name == null || this.partialJson == null);
 		}
 
 		ToolUseAggregationEvent withIndex(Integer index) {
@@ -451,7 +450,9 @@ ToolUseAggregationEvent appendPartialJson(String partialJson) {
 		}
 
 		void squashIntoContentBlock() {
-			this.toolUseEntries.add(new ToolUseEntry(this.index, this.id, this.name, this.partialJson, this.usage));
+			// Workaround to handle streaming tool calling with no input arguments.
+			String json = StringUtils.hasText(this.partialJson) ? this.partialJson : "{}";
+			this.toolUseEntries.add(new ToolUseEntry(this.index, this.id, this.name, json, this.usage));
 			this.index = null;
 			this.id = null;
 			this.name = null;
diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java
index d582d676833..61d3b1da882 100644
--- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java
+++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/client/BedrockNovaChatClientIT.java
@@ -23,6 +23,8 @@
 import java.util.stream.Collectors;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import reactor.core.publisher.Flux;
@@ -186,12 +188,15 @@ void toolAnnotationWeatherForecast() {
 		assertThat(response).contains("20 degrees");
 	}
 
-	@Test
-	void toolAnnotationWeatherForecastStreaming() {
+	// https://github.com/spring-projects/spring-ai/issues/1878
+	@ParameterizedTest
+	@ValueSource(strings = { "amazon.nova-pro-v1:0", "us.anthropic.claude-3-7-sonnet-20250219-v1:0" })
+	void toolAnnotationWeatherForecastStreaming(String modelName) {
 
 		ChatClient chatClient = ChatClient.builder(this.chatModel).build();
 
 		Flux<ChatResponse> responses = chatClient.prompt()
+			.options(ToolCallingChatOptions.builder().model(modelName).build())
 			.tools(new DummyWeatherForecastTools())
 			.user("Get current weather in Amsterdam")
 			.stream()
@@ -257,6 +262,7 @@ public static class Config {
 		public BedrockProxyChatModel bedrockConverseChatModel() {
 
 			String modelId = "amazon.nova-pro-v1:0";
+			// String modelId = "us.anthropic.claude-3-7-sonnet-20250219-v1:0";
 
 			return BedrockProxyChatModel.builder()
 				.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
diff --git a/pom.xml b/pom.xml
index bdf031ccc1b..b027a0fb7f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -261,8 +261,8 @@
 		<kotlin.version>1.9.25</kotlin.version>
 
 		<!-- NOTE: keep bedrockruntime and awssdk versions aligned -->
-		<bedrockruntime.version>2.31.26</bedrockruntime.version>
-		<awssdk.version>2.29.29</awssdk.version>
+		<bedrockruntime.version>2.31.65</bedrockruntime.version>
+		<awssdk.version>2.31.65</awssdk.version>
 
 		<djl.version>0.32.0</djl.version>
 		<onnxruntime.version>1.19.2</onnxruntime.version>

From d1baefb4b768e1139f09abf0f3b9036b1adb17a0 Mon Sep 17 00:00:00 2001
From: Daniel Garnier-Moiroux <git@garnier.wf>
Date: Wed, 18 Jun 2025 17:11:36 +0200
Subject: [PATCH 105/165] feat: add selective exception rethrowing to
 DefaultToolExecutionExceptionProcessor (#3595)

This allows certain exceptions (like OAuth2 authorization errors) to bubble up
properly instead of being wrapped, enabling correct authentication flows.

- Add support for configuring allowlist of exceptions to be rethrown directly
- Enhance DefaultToolExecutionExceptionProcessor with builder pattern
- Add automatic detection and handling of OAuth2 ClientAuthorizationException
- Update ToolCallingAutoConfiguration to configure OAuth2 exception rethrowing
- Add unit tests for new exception handling behavior

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
---
 .../ToolCallingAutoConfiguration.java         | 31 +++++-
 ...efaultToolExecutionExceptionProcessor.java | 40 +++++++-
 ...tToolExecutionExceptionProcessorTests.java | 99 +++++++++++++++++++
 3 files changed, 165 insertions(+), 5 deletions(-)
 create mode 100644 spring-ai-model/src/test/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessorTests.java

diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
index 5ab883df2b4..eeef83b940c 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
@@ -16,10 +16,9 @@
 
 package org.springframework.ai.model.tool.autoconfigure;
 
+import io.micrometer.observation.ObservationRegistry;
 import java.util.ArrayList;
 import java.util.List;
-
-import io.micrometer.observation.ObservationRegistry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,12 +42,14 @@
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.util.ClassUtils;
 
 /**
  * Auto-configuration for common tool calling features of {@link ChatModel}.
  *
  * @author Thomas Vitale
  * @author Christian Tzolov
+ * @author Daniel Garnier-Moiroux
  * @since 1.0.0
  */
 @AutoConfiguration
@@ -78,7 +79,21 @@ ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationC
 	@Bean
 	@ConditionalOnMissingBean
 	ToolExecutionExceptionProcessor toolExecutionExceptionProcessor(ToolCallingProperties properties) {
-		return new DefaultToolExecutionExceptionProcessor(properties.isThrowExceptionOnError());
+		ArrayList<Class<? extends RuntimeException>> rethrownExceptions = new ArrayList<>();
+
+		// ClientAuthorizationException is used by Spring Security in oauth2 flows,
+		// for example with ServletOAuth2AuthorizedClientExchangeFilterFunction and
+		// OAuth2ClientHttpRequestInterceptor.
+		Class<? extends RuntimeException> oauth2Exception = getClassOrNull(
+				"org.springframework.security.oauth2.client.ClientAuthorizationException");
+		if (oauth2Exception != null) {
+			rethrownExceptions.add(oauth2Exception);
+		}
+
+		return DefaultToolExecutionExceptionProcessor.builder()
+			.alwaysThrow(properties.isThrowExceptionOnError())
+			.rethrowExceptions(rethrownExceptions)
+			.build();
 	}
 
 	@Bean
@@ -108,4 +123,14 @@ ToolCallingContentObservationFilter toolCallingContentObservationFilter() {
 		return new ToolCallingContentObservationFilter();
 	}
 
+	private static Class<? extends RuntimeException> getClassOrNull(String className) {
+		try {
+			return (Class<? extends RuntimeException>) ClassUtils.forName(className, null);
+		}
+		catch (ClassNotFoundException e) {
+			logger.debug("Cannot load class", e);
+		}
+		return null;
+	}
+
 }
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessor.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessor.java
index cd6ee28ccd0..6e2aa03ad4a 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessor.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessor.java
@@ -16,15 +16,20 @@
 
 package org.springframework.ai.tool.execution;
 
+import java.util.Collections;
+import java.util.List;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.springframework.util.Assert;
 
 /**
- * Default implementation of {@link ToolExecutionExceptionProcessor}.
+ * Default implementation of {@link ToolExecutionExceptionProcessor}. Can be configured
+ * with an allowlist of exceptions that will be unwrapped from the
+ * {@link ToolExecutionException} and rethrown as is.
  *
  * @author Thomas Vitale
+ * @author Daniel Garnier-Moiroux
  * @since 1.0.0
  */
 public class DefaultToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {
@@ -35,13 +40,26 @@ public class DefaultToolExecutionExceptionProcessor implements ToolExecutionExce
 
 	private final boolean alwaysThrow;
 
+	private final List<Class<? extends RuntimeException>> rethrownExceptions;
+
 	public DefaultToolExecutionExceptionProcessor(boolean alwaysThrow) {
+		this(alwaysThrow, Collections.emptyList());
+	}
+
+	public DefaultToolExecutionExceptionProcessor(boolean alwaysThrow,
+			List<Class<? extends RuntimeException>> rethrownExceptions) {
 		this.alwaysThrow = alwaysThrow;
+		this.rethrownExceptions = Collections.unmodifiableList(rethrownExceptions);
 	}
 
 	@Override
 	public String process(ToolExecutionException exception) {
 		Assert.notNull(exception, "exception cannot be null");
+		Throwable cause = exception.getCause();
+		if (cause instanceof RuntimeException runtimeException
+				&& this.rethrownExceptions.stream().anyMatch(rethrown -> rethrown.isAssignableFrom(cause.getClass()))) {
+			throw runtimeException;
+		}
 		if (this.alwaysThrow) {
 			throw exception;
 		}
@@ -58,13 +76,31 @@ public static class Builder {
 
 		private boolean alwaysThrow = DEFAULT_ALWAYS_THROW;
 
+		private List<Class<? extends RuntimeException>> exceptions = Collections.emptyList();
+
+		/**
+		 * Rethrow the {@link ToolExecutionException}
+		 * @param alwaysThrow when true, throws; when false, returns the exception message
+		 * @return the builder instance
+		 */
 		public Builder alwaysThrow(boolean alwaysThrow) {
 			this.alwaysThrow = alwaysThrow;
 			return this;
 		}
 
+		/**
+		 * An allowlist of exceptions thrown by tools, which will be unwrapped and
+		 * re-thrown without further processing.
+		 * @param exceptions the list of exceptions
+		 * @return the builder instance
+		 */
+		public Builder rethrowExceptions(List<Class<? extends RuntimeException>> exceptions) {
+			this.exceptions = exceptions;
+			return this;
+		}
+
 		public DefaultToolExecutionExceptionProcessor build() {
-			return new DefaultToolExecutionExceptionProcessor(this.alwaysThrow);
+			return new DefaultToolExecutionExceptionProcessor(this.alwaysThrow, exceptions);
 		}
 
 	}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessorTests.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessorTests.java
new file mode 100644
index 00000000000..955194dd2a3
--- /dev/null
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/execution/DefaultToolExecutionExceptionProcessorTests.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023-2025 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.tool.execution;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.ai.tool.definition.DefaultToolDefinition;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.InstanceOfAssertFactories.type;
+
+/**
+ * Unit tests for {@link DefaultToolExecutionExceptionProcessor}.
+ *
+ * @author Daniel Garnier-Moiroux
+ */
+class DefaultToolExecutionExceptionProcessorTests {
+
+	private final IllegalStateException toolException = new IllegalStateException("Inner exception");
+
+	private final DefaultToolDefinition toolDefinition = new DefaultToolDefinition("toolName", "toolDescription",
+			"inputSchema");
+
+	private final ToolExecutionException toolExecutionException = new ToolExecutionException(toolDefinition,
+			toolException);
+
+	@Test
+	void processReturnsMessage() {
+		DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder().build();
+
+		String result = processor.process(this.toolExecutionException);
+
+		assertThat(result).isEqualTo(this.toolException.getMessage());
+	}
+
+	@Test
+	void processAlwaysThrows() {
+		DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder()
+			.alwaysThrow(true)
+			.build();
+
+		assertThatThrownBy(() -> processor.process(this.toolExecutionException))
+			.hasMessage(this.toolException.getMessage())
+			.hasCauseInstanceOf(this.toolException.getClass())
+			.asInstanceOf(type(ToolExecutionException.class))
+			.extracting(ToolExecutionException::getToolDefinition)
+			.isEqualTo(this.toolDefinition);
+	}
+
+	@Test
+	void processRethrows() {
+		DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder()
+			.alwaysThrow(false)
+			.rethrowExceptions(List.of(IllegalStateException.class))
+			.build();
+
+		assertThatThrownBy(() -> processor.process(this.toolExecutionException)).isEqualTo(this.toolException);
+	}
+
+	@Test
+	void processRethrowsExceptionSubclasses() {
+		DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder()
+			.alwaysThrow(false)
+			.rethrowExceptions(List.of(RuntimeException.class))
+			.build();
+
+		assertThatThrownBy(() -> processor.process(this.toolExecutionException)).isEqualTo(this.toolException);
+	}
+
+	@Test
+	void processRethrowsOnlySelectExceptions() {
+		DefaultToolExecutionExceptionProcessor processor = DefaultToolExecutionExceptionProcessor.builder()
+			.alwaysThrow(false)
+			.rethrowExceptions(List.of(IllegalStateException.class))
+			.build();
+
+		ToolExecutionException exception = new ToolExecutionException(this.toolDefinition,
+				new RuntimeException("This exception was not rethrown"));
+		String result = processor.process(exception);
+
+		assertThat(result).isEqualTo("This exception was not rethrown");
+	}
+
+}

From c18a241c4ad8c4fa844781c0624b8ea5d7d9f53d Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <apappascs@gmail.com>
Date: Sun, 2 Mar 2025 14:38:14 +0100
Subject: [PATCH 106/165] feat: Update MistralChatOptions

- Updating `copy()` method, creating new instances of mutable collections (List, Set, Map, Metadata) to prevent shared state.
- Adding `MistralChatOptionsTests` to verify `copy()`, builders, setters, and default values.

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
---
 .../ai/mistralai/MistralAiChatOptions.java    |  12 +-
 .../ai/mistralai/api/MistralAiApi.java        |   8 +-
 .../mistralai/MistralAiChatOptionsTests.java  | 127 ++++++++++++++++++
 3 files changed, 140 insertions(+), 7 deletions(-)
 create mode 100644 models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java

diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java
index 2b392d5176a..801c35f2118 100644
--- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java
+++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java
@@ -171,16 +171,17 @@ public static MistralAiChatOptions fromOptions(MistralAiChatOptions fromOptions)
 			.temperature(fromOptions.getTemperature())
 			.topP(fromOptions.getTopP())
 			.responseFormat(fromOptions.getResponseFormat())
-			.stop(fromOptions.getStop())
+			.stop(fromOptions.getStop() != null ? new ArrayList<>(fromOptions.getStop()) : null)
 			.frequencyPenalty(fromOptions.getFrequencyPenalty())
 			.presencePenalty(fromOptions.getPresencePenalty())
 			.n(fromOptions.getN())
-			.tools(fromOptions.getTools())
+			.tools(fromOptions.getTools() != null ? new ArrayList<>(fromOptions.getTools()) : null)
 			.toolChoice(fromOptions.getToolChoice())
-			.toolCallbacks(fromOptions.getToolCallbacks())
-			.toolNames(fromOptions.getToolNames())
+			.toolCallbacks(
+					fromOptions.getToolCallbacks() != null ? new ArrayList<>(fromOptions.getToolCallbacks()) : null)
+			.toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null)
 			.internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled())
-			.toolContext(fromOptions.getToolContext())
+			.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
 			.build();
 	}
 
@@ -366,6 +367,7 @@ public void setToolContext(Map<String, Object> toolContext) {
 	}
 
 	@Override
+	@SuppressWarnings("unchecked")
 	public MistralAiChatOptions copy() {
 		return fromOptions(this);
 	}
diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java
index c6bce7cae77..e2702a3a0af 100644
--- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java
+++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java
@@ -754,12 +754,16 @@ public enum ToolChoice {
 		/**
 		 * An object specifying the format that the model must output.
 		 *
-		 * @param type Must be one of 'text' or 'json_object'.
-		 * @param jsonSchema A specific JSON schema to match, if 'type' is 'json_object'.
+		 * @param type Must be one of 'text', 'json_object' or 'json_schema'.
+		 * @param jsonSchema A specific JSON schema to match, if 'type' is 'json_schema'.
 		 */
 		@JsonInclude(Include.NON_NULL)
 		public record ResponseFormat(@JsonProperty("type") String type,
 				@JsonProperty("json_schema") Map<String, Object> jsonSchema) {
+
+			public ResponseFormat(String type) {
+				this(type, null);
+			}
 		}
 
 	}
diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java
new file mode 100644
index 00000000000..3177e85a442
--- /dev/null
+++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatOptionsTests.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2025-2025 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.mistralai;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ResponseFormat;
+
+import org.springframework.ai.mistralai.api.MistralAiApi;
+
+/**
+ * Tests for {@link MistralAiChatOptions}.
+ *
+ * @author Alexandros Pappas
+ */
+class MistralAiChatOptionsTests {
+
+	@Test
+	void testBuilderWithAllFields() {
+		MistralAiChatOptions options = MistralAiChatOptions.builder()
+			.model("test-model")
+			.temperature(0.7)
+			.topP(0.9)
+			.maxTokens(100)
+			.safePrompt(true)
+			.randomSeed(123)
+			.stop(List.of("stop1", "stop2"))
+			.responseFormat(new ResponseFormat("json_object"))
+			.toolChoice(MistralAiApi.ChatCompletionRequest.ToolChoice.AUTO)
+			.internalToolExecutionEnabled(true)
+			.toolContext(Map.of("key1", "value1"))
+			.build();
+
+		assertThat(options)
+			.extracting("model", "temperature", "topP", "maxTokens", "safePrompt", "randomSeed", "stop",
+					"responseFormat", "toolChoice", "internalToolExecutionEnabled", "toolContext")
+			.containsExactly("test-model", 0.7, 0.9, 100, true, 123, List.of("stop1", "stop2"),
+					new ResponseFormat("json_object"), MistralAiApi.ChatCompletionRequest.ToolChoice.AUTO, true,
+					Map.of("key1", "value1"));
+	}
+
+	@Test
+	void testBuilderWithEnum() {
+		MistralAiChatOptions optionsWithEnum = MistralAiChatOptions.builder()
+			.model(MistralAiApi.ChatModel.MINISTRAL_8B_LATEST)
+			.build();
+		assertThat(optionsWithEnum.getModel()).isEqualTo(MistralAiApi.ChatModel.MINISTRAL_8B_LATEST.getValue());
+	}
+
+	@Test
+	void testCopy() {
+		MistralAiChatOptions options = MistralAiChatOptions.builder()
+			.model("test-model")
+			.temperature(0.7)
+			.topP(0.9)
+			.maxTokens(100)
+			.safePrompt(true)
+			.randomSeed(123)
+			.stop(List.of("stop1", "stop2"))
+			.responseFormat(new ResponseFormat("json_object"))
+			.toolChoice(MistralAiApi.ChatCompletionRequest.ToolChoice.AUTO)
+			.internalToolExecutionEnabled(true)
+			.toolContext(Map.of("key1", "value1"))
+			.build();
+
+		MistralAiChatOptions copiedOptions = options.copy();
+		assertThat(copiedOptions).isNotSameAs(options).isEqualTo(options);
+		// Ensure deep copy
+		assertThat(copiedOptions.getStop()).isNotSameAs(options.getStop());
+		assertThat(copiedOptions.getToolContext()).isNotSameAs(options.getToolContext());
+	}
+
+	@Test
+	void testSetters() {
+		ResponseFormat responseFormat = new ResponseFormat("json_object");
+		MistralAiChatOptions options = new MistralAiChatOptions();
+		options.setModel("test-model");
+		options.setTemperature(0.7);
+		options.setTopP(0.9);
+		options.setMaxTokens(100);
+		options.setSafePrompt(true);
+		options.setRandomSeed(123);
+		options.setResponseFormat(responseFormat);
+		options.setStopSequences(List.of("stop1", "stop2"));
+
+		assertThat(options.getModel()).isEqualTo("test-model");
+		assertThat(options.getTemperature()).isEqualTo(0.7);
+		assertThat(options.getTopP()).isEqualTo(0.9);
+		assertThat(options.getMaxTokens()).isEqualTo(100);
+		assertThat(options.getSafePrompt()).isEqualTo(true);
+		assertThat(options.getRandomSeed()).isEqualTo(123);
+		assertThat(options.getStopSequences()).isEqualTo(List.of("stop1", "stop2"));
+		assertThat(options.getResponseFormat()).isEqualTo(responseFormat);
+	}
+
+	@Test
+	void testDefaultValues() {
+		MistralAiChatOptions options = new MistralAiChatOptions();
+		assertThat(options.getModel()).isNull();
+		assertThat(options.getTemperature()).isNull();
+		assertThat(options.getTopP()).isNull();
+		assertThat(options.getMaxTokens()).isNull();
+		assertThat(options.getSafePrompt()).isNull();
+		assertThat(options.getRandomSeed()).isNull();
+		assertThat(options.getStopSequences()).isNull();
+		assertThat(options.getResponseFormat()).isNull();
+	}
+
+}

From 209927830d99fa3bb66ca2fda04bc95025911b0a Mon Sep 17 00:00:00 2001
From: Christian Tzolov <christian.tzolov@broadcom.com>
Date: Fri, 20 Jun 2025 07:56:52 +0200
Subject: [PATCH 107/165] fix(anthropic): Add streaming parameter-less tool
 support

- Remove partialJson text check from isEmpty() method in AnthropicApi
- Add parameterized test for streaming parameter-less tools across Claude models
- Addresses issue #1878 with parameter-less tool streaming functionality

Fixes #1878

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
(cherry picked from commit f5750517b4e3aa379d43b32489d7d1208f61b8f0)
---
 .../ai/anthropic/api/AnthropicApi.java        |  3 +-
 ...lientMethodInvokingFunctionCallbackIT.java | 40 +++++++++++++++++++
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
index e3485574948..768690e8f0f 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
@@ -1060,8 +1060,7 @@ public List<ContentBlockStartEvent.ContentBlockToolUse> getToolContentBlocks() {
 		  * @return True if the event is empty, false otherwise.
 		*/
 		public boolean isEmpty() {
-			return (this.index == null || this.id == null || this.name == null
-					|| !StringUtils.hasText(this.partialJson));
+			return (this.index == null || this.id == null || this.name == null);
 		}
 
 		ToolUseAggregationEvent withIndex(Integer index) {
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java
index d6f396f7af8..2fa9bccf6bf 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java
@@ -19,10 +19,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,7 +33,9 @@
 import org.springframework.ai.chat.client.ChatClient;
 import org.springframework.ai.chat.messages.Message;
 import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
 import org.springframework.ai.chat.model.ToolContext;
+import org.springframework.ai.model.tool.ToolCallingChatOptions;
 import org.springframework.ai.tool.annotation.Tool;
 import org.springframework.ai.tool.method.MethodToolCallback;
 import org.springframework.ai.tool.support.ToolDefinitions;
@@ -39,6 +44,8 @@
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.util.ReflectionUtils;
 
+import reactor.core.publisher.Flux;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
 
@@ -262,6 +269,39 @@ void toolAnnotation() {
 			.containsEntry("color", TestFunctionClass.LightColor.RED);
 	}
 
+	// https://github.com/spring-projects/spring-ai/issues/1878
+	@ParameterizedTest
+	@ValueSource(strings = { "claude-opus-4-20250514", "claude-sonnet-4-20250514", "claude-3-7-sonnet-latest" })
+	void streamingParameterLessTool(String modelName) {
+
+		ChatClient chatClient = ChatClient.builder(this.chatModel).build();
+
+		Flux<ChatResponse> responses = chatClient.prompt()
+			.options(ToolCallingChatOptions.builder().model(modelName).build())
+			.tools(new ParameterLessTools())
+			.user("Get current weather in Amsterdam")
+			.stream()
+			.chatResponse();
+
+		String content = responses.collectList()
+			.block()
+			.stream()
+			.filter(cr -> cr.getResult() != null)
+			.map(cr -> cr.getResult().getOutput().getText())
+			.collect(Collectors.joining());
+
+		assertThat(content).contains("20 degrees");
+	}
+
+	public static class ParameterLessTools {
+
+		@Tool(description = "Get the current weather forecast in Amsterdam")
+		String getCurrentDateTime() {
+			return "Weather is hot and sunny with a temperature of 20 degrees";
+		}
+
+	}
+
 	@Autowired
 	ChatModel chatModel;
 

From 2512f22b0da98e884dc9c2a1c58a096adc48e08e Mon Sep 17 00:00:00 2001
From: Soby Chacko <soby.chacko@broadcom.com>
Date: Fri, 20 Jun 2025 05:49:16 -0400
Subject: [PATCH 108/165] Remove hardcoded topK in
 BedrockConverseProxyChatProperties (#3621)

Fixes: #3621

Bedrock does not support topK and setting it via BedrockConverseProxyChatProperties
does not have any effect.

Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
(cherry picked from commit 8c411abe6ef0bfd18fe42cb8795d228ef7cf7600)
---
 .../autoconfigure/BedrockConverseProxyChatProperties.java   | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java
index acf9fdd9a00..370e826bd17 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java
@@ -33,11 +33,7 @@ public class BedrockConverseProxyChatProperties {
 	public static final String CONFIG_PREFIX = "spring.ai.bedrock.converse.chat";
 
 	@NestedConfigurationProperty
-	private ToolCallingChatOptions options = ToolCallingChatOptions.builder()
-		.temperature(0.7)
-		.maxTokens(300)
-		.topK(10)
-		.build();
+	private ToolCallingChatOptions options = ToolCallingChatOptions.builder().temperature(0.7).maxTokens(300).build();
 
 	public ToolCallingChatOptions getOptions() {
 		return this.options;

From f981ff8c4cb0e5efff411c26580fd85556033618 Mon Sep 17 00:00:00 2001
From: "chen.xue" <huihaoweishui@163.com>
Date: Fri, 20 Jun 2025 17:59:41 +0800
Subject: [PATCH 109/165] Update tools.adoc (#3624)

Fixes #3624
Signed-off-by: chen.xue <huihaoweishui@163.com>
(cherry picked from commit 1485c346edb768c9f1569a90157bc50f7aa85e23)
---
 .../src/main/antora/modules/ROOT/pages/api/tools.adoc           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index 28b6f0919d5..8d9377688f6 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -515,7 +515,7 @@ When using the programmatic specification approach, you can pass the `FunctionTo
 ToolCallback toolCallback = ...
 ChatClient.create(chatModel)
     .prompt("What's the weather like in Copenhagen?")
-    .tools(toolCallback)
+    .toolCallbacks(toolCallback)
     .call()
     .content();
 ----

From 406c6f11ce086b647dfde9e83ca8aa58cf78085f Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Fri, 20 Jun 2025 11:06:02 +0100
Subject: [PATCH 110/165] Fix tools documentation for the correct usage

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
(cherry picked from commit 585f554f31eba45d9578e08faf5c99fad277907a)
---
 .../antora/modules/ROOT/pages/api/tools.adoc     | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
index 8d9377688f6..c285ef58dbc 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/tools.adoc
@@ -385,7 +385,7 @@ Besides the `@ToolParam` annotation, you can also use the `@Schema` annotation f
 
 ==== Adding Tools to `ChatClient` and `ChatModel`
 
-When using the programmatic specification approach, you can pass the `MethodToolCallback` instance to the `tools()` method of `ChatClient`.
+When using the programmatic specification approach, you can pass the `MethodToolCallback` instance to the `toolCallbacks()` method of `ChatClient`.
 The tool will only be available for the specific chat request it's added to.
 
 [source,java]
@@ -393,14 +393,14 @@ The tool will only be available for the specific chat request it's added to.
 ToolCallback toolCallback = ...
 ChatClient.create(chatModel)
     .prompt("What day is tomorrow?")
-    .tools(toolCallback)
+    .toolCallbacks(toolCallback)
     .call()
     .content();
 ----
 
 ==== Adding Default Tools to `ChatClient`
 
-When using the programmatic specification approach, you can add default tools to a `ChatClient.Builder` by passing the `MethodToolCallback` instance to the `defaultTools()` method.
+When using the programmatic specification approach, you can add default tools to a `ChatClient.Builder` by passing the `MethodToolCallback` instance to the `defaultToolCallbacks()` method.
 If both default and runtime tools are provided, the runtime tools will completely override the default tools.
 
 WARNING: Default tools are shared across all the chat requests performed by all the `ChatClient` instances built from the same `ChatClient.Builder`. They are useful for tools that are commonly used across different chat requests, but they can also be dangerous if not used carefully, risking to make them available when they shouldn't.
@@ -410,7 +410,7 @@ WARNING: Default tools are shared across all the chat requests performed by all
 ChatModel chatModel = ...
 ToolCallback toolCallback = ...
 ChatClient chatClient = ChatClient.builder(chatModel)
-    .defaultTools(toolCallback)
+    .defaultToolCallbacks(toolCallback)
     .build();
 ----
 
@@ -508,7 +508,7 @@ NOTE: Some types are not supported. See xref:_function_tool_limitations[] for mo
 
 ==== Adding Tools to `ChatClient`
 
-When using the programmatic specification approach, you can pass the `FunctionToolCallback` instance to the `tools()` method of `ChatClient`. The tool will only be available for the specific chat request it's added to.
+When using the programmatic specification approach, you can pass the `FunctionToolCallback` instance to the `toolCallbacks()` method of `ChatClient`. The tool will only be available for the specific chat request it's added to.
 
 [source,java]
 ----
@@ -522,7 +522,7 @@ ChatClient.create(chatModel)
 
 ==== Adding Default Tools to `ChatClient`
 
-When using the programmatic specification approach, you can add default tools to a `ChatClient.Builder` by passing the `FunctionToolCallback` instance to the `defaultTools()` method.
+When using the programmatic specification approach, you can add default tools to a `ChatClient.Builder` by passing the `FunctionToolCallback` instance to the `defaultToolCallbacks()` method.
 If both default and runtime tools are provided, the runtime tools will completely override the default tools.
 
 WARNING: Default tools are shared across all the chat requests performed by all the `ChatClient` instances built from the same `ChatClient.Builder`. They are useful for tools that are commonly used across different chat requests, but they can also be dangerous if not used carefully, risking to make them available when they shouldn't.
@@ -532,7 +532,7 @@ WARNING: Default tools are shared across all the chat requests performed by all
 ChatModel chatModel = ...
 ToolCallback toolCallback = ...
 ChatClient chatClient = ChatClient.builder(chatModel)
-    .defaultTools(toolCallback)
+    .defaultToolCallbacks(toolCallback)
     .build();
 ----
 
@@ -632,7 +632,7 @@ ChatClient.create(chatModel)
 
 ==== Adding Default Tools to `ChatClient`
 
-When using the dynamic specification approach, you can add default tools to a `ChatClient.Builder` by passing the tool name to the `defaultTools()` method.
+When using the dynamic specification approach, you can add default tools to a `ChatClient.Builder` by passing the tool name to the `defaultToolNames()` method.
 If both default and runtime tools are provided, the runtime tools will completely override the default tools.
 
 WARNING: Default tools are shared across all the chat requests performed by all the `ChatClient` instances built from the same `ChatClient.Builder`. They are useful for tools that are commonly used across different chat requests, but they can also be dangerous if not used carefully, risking to make them available when they shouldn't.

From 62160d330df5e7e5512120e97a1e8e7c7c0d0a74 Mon Sep 17 00:00:00 2001
From: loong-coder <garen.mao@gmail.com>
Date: Sun, 15 Jun 2025 22:22:12 +0800
Subject: [PATCH 111/165] Enhanced function capabilities through @Tool
 annotation improvements

Signed-off-by: loong-coder <garen.mao@gmail.com>
---
 .../method/MethodToolCallbackProvider.java    | 11 ++-
 .../ai/tool/support/ToolUtils.java            |  9 +--
 ...ToolBeanRegistrationAotProcessorTests.java | 43 +++++++++++
 .../MethodToolCallbackProviderTests.java      | 71 +++++++++++++++++++
 4 files changed, 128 insertions(+), 6 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
index 65a42dfea87..264c29c53fd 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
@@ -19,6 +19,7 @@
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -35,6 +36,7 @@
 import org.springframework.ai.tool.support.ToolDefinitions;
 import org.springframework.ai.tool.support.ToolUtils;
 import org.springframework.aop.support.AopUtils;
+import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.ReflectionUtils;
@@ -67,7 +69,7 @@ private void assertToolAnnotatedMethodsPresent(List<Object> toolObjects) {
 			List<Method> toolMethods = Stream
 				.of(ReflectionUtils.getDeclaredMethods(
 						AopUtils.isAopProxy(toolObject) ? AopUtils.getTargetClass(toolObject) : toolObject.getClass()))
-				.filter(toolMethod -> toolMethod.isAnnotationPresent(Tool.class))
+				.filter(this::isToolAnnotatedMethod)
 				.filter(toolMethod -> !isFunctionalType(toolMethod))
 				.toList();
 
@@ -84,7 +86,7 @@ public ToolCallback[] getToolCallbacks() {
 			.map(toolObject -> Stream
 				.of(ReflectionUtils.getDeclaredMethods(
 						AopUtils.isAopProxy(toolObject) ? AopUtils.getTargetClass(toolObject) : toolObject.getClass()))
-				.filter(toolMethod -> toolMethod.isAnnotationPresent(Tool.class))
+				.filter(this::isToolAnnotatedMethod)
 				.filter(toolMethod -> !isFunctionalType(toolMethod))
 				.map(toolMethod -> MethodToolCallback.builder()
 					.toolDefinition(ToolDefinitions.from(toolMethod))
@@ -115,6 +117,11 @@ private boolean isFunctionalType(Method toolMethod) {
 		return isFunction;
 	}
 
+	private boolean isToolAnnotatedMethod(Method method) {
+		Tool annotation = AnnotationUtils.findAnnotation(method, Tool.class);
+		return Objects.nonNull(annotation);
+	}
+
 	private void validateToolCallbacks(ToolCallback[] toolCallbacks) {
 		List<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);
 		if (!duplicateToolNames.isEmpty()) {
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/support/ToolUtils.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/support/ToolUtils.java
index c49b9c6ff71..4186d935acc 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/support/ToolUtils.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/support/ToolUtils.java
@@ -27,6 +27,7 @@
 import org.springframework.ai.tool.execution.DefaultToolCallResultConverter;
 import org.springframework.ai.tool.execution.ToolCallResultConverter;
 import org.springframework.ai.util.ParsingUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
@@ -42,7 +43,7 @@ private ToolUtils() {
 
 	public static String getToolName(Method method) {
 		Assert.notNull(method, "method cannot be null");
-		var tool = method.getAnnotation(Tool.class);
+		var tool = AnnotatedElementUtils.findMergedAnnotation(method, Tool.class);
 		if (tool == null) {
 			return method.getName();
 		}
@@ -56,7 +57,7 @@ public static String getToolDescriptionFromName(String toolName) {
 
 	public static String getToolDescription(Method method) {
 		Assert.notNull(method, "method cannot be null");
-		var tool = method.getAnnotation(Tool.class);
+		var tool = AnnotatedElementUtils.findMergedAnnotation(method, Tool.class);
 		if (tool == null) {
 			return ParsingUtils.reConcatenateCamelCase(method.getName(), " ");
 		}
@@ -65,13 +66,13 @@ public static String getToolDescription(Method method) {
 
 	public static boolean getToolReturnDirect(Method method) {
 		Assert.notNull(method, "method cannot be null");
-		var tool = method.getAnnotation(Tool.class);
+		var tool = AnnotatedElementUtils.findMergedAnnotation(method, Tool.class);
 		return tool != null && tool.returnDirect();
 	}
 
 	public static ToolCallResultConverter getToolCallResultConverter(Method method) {
 		Assert.notNull(method, "method cannot be null");
-		var tool = method.getAnnotation(Tool.class);
+		var tool = AnnotatedElementUtils.findMergedAnnotation(method, Tool.class);
 		if (tool == null) {
 			return new DefaultToolCallResultConverter();
 		}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolBeanRegistrationAotProcessorTests.java b/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolBeanRegistrationAotProcessorTests.java
index ab67165a0bf..7affe701a02 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolBeanRegistrationAotProcessorTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolBeanRegistrationAotProcessorTests.java
@@ -19,14 +19,19 @@
 import org.junit.jupiter.api.Test;
 
 import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.execution.DefaultToolCallResultConverter;
+import org.springframework.ai.tool.execution.ToolCallResultConverter;
 import org.springframework.aot.generate.GenerationContext;
 import org.springframework.aot.hint.RuntimeHints;
 import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
 import org.springframework.beans.factory.support.RegisteredBean;
 import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.annotation.AliasFor;
 import org.springframework.lang.Nullable;
 
+import java.lang.annotation.*;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -55,6 +60,12 @@ void shouldProcessAnnotatedClass() {
 		assertThat(reflection().onType(TestTools.class)).accepts(this.runtimeHints);
 	}
 
+	@Test
+	void shouldProcessEnhanceAnnotatedClass() {
+		process(TestEnhanceToolTools.class);
+		assertThat(reflection().onType(TestEnhanceToolTools.class)).accepts(this.runtimeHints);
+	}
+
 	private void process(Class<?> beanClass) {
 		when(this.generationContext.getRuntimeHints()).thenReturn(this.runtimeHints);
 		BeanRegistrationAotContribution contribution = createContribution(beanClass);
@@ -87,4 +98,36 @@ String nonTool() {
 
 	}
 
+	@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+	@Retention(RetentionPolicy.RUNTIME)
+	@Documented
+	@Tool
+	@Inherited
+	@interface EnhanceTool {
+
+		@AliasFor(annotation = Tool.class)
+		String name() default "";
+
+		@AliasFor(annotation = Tool.class)
+		String description() default "";
+
+		@AliasFor(annotation = Tool.class)
+		boolean returnDirect() default false;
+
+		@AliasFor(annotation = Tool.class)
+		Class<? extends ToolCallResultConverter> resultConverter() default DefaultToolCallResultConverter.class;
+
+		String enhanceValue() default "";
+
+	}
+
+	static class TestEnhanceToolTools {
+
+		@EnhanceTool
+		String testTool() {
+			return "Testing EnhanceTool";
+		}
+
+	}
+
 }
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
index df52c92a6fc..a5659a9cc9d 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
@@ -16,6 +16,7 @@
 
 package org.springframework.ai.tool.method;
 
+import java.lang.annotation.*;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -23,6 +24,9 @@
 import org.junit.jupiter.api.Test;
 
 import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.execution.DefaultToolCallResultConverter;
+import org.springframework.ai.tool.execution.ToolCallResultConverter;
+import org.springframework.core.annotation.AliasFor;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -87,6 +91,27 @@ void whenToolObjectHasObjectTypeMethodThenSuccess() {
 		assertThat(provider.getToolCallbacks()[0].getToolDefinition().name()).isEqualTo("objectTool");
 	}
 
+	@Test
+	void whenToolObjectHasEnhanceToolAnnotatedMethodThenSucceed() {
+		MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
+			.toolObjects(new ToolUseEnhanceToolObject())
+			.build();
+
+		assertThat(provider.getToolCallbacks()).hasSize(1);
+		assertThat(provider.getToolCallbacks()[0].getToolDefinition().name()).isEqualTo("enhanceTool");
+		assertThat(provider.getToolCallbacks()[0].getToolDefinition().description()).isEqualTo("enhance tool");
+	}
+
+	@Test
+	void whenEnhanceToolObjectHasMixOfValidAndFunctionalTypeToolMethodsThenSucceed() {
+		MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
+			.toolObjects(new UseEnhanceToolMixedToolMethodsObject())
+			.build();
+
+		assertThat(provider.getToolCallbacks()).hasSize(1);
+		assertThat(provider.getToolCallbacks()[0].getToolDefinition().name()).isEqualTo("validTool");
+	}
+
 	static class ValidToolObject {
 
 		@Tool
@@ -155,4 +180,50 @@ public Object objectTool() {
 
 	}
 
+	@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+	@Retention(RetentionPolicy.RUNTIME)
+	@Documented
+	@Tool
+	@Inherited
+	@interface EnhanceTool {
+
+		@AliasFor(annotation = Tool.class)
+		String name() default "";
+
+		@AliasFor(annotation = Tool.class)
+		String description() default "";
+
+		@AliasFor(annotation = Tool.class)
+		boolean returnDirect() default false;
+
+		@AliasFor(annotation = Tool.class)
+		Class<? extends ToolCallResultConverter> resultConverter() default DefaultToolCallResultConverter.class;
+
+		String enhanceValue() default "";
+
+	}
+
+	static class ToolUseEnhanceToolObject {
+
+		@EnhanceTool(description = "enhance tool")
+		public String enhanceTool() {
+			return "enhance tool result";
+		}
+
+	}
+
+	static class UseEnhanceToolMixedToolMethodsObject {
+
+		@EnhanceTool
+		public String validTool() {
+			return "Valid tool result";
+		}
+
+		@EnhanceTool
+		public Function<String, String> functionTool() {
+			return input -> "Function result: " + input;
+		}
+
+	}
+
 }

From f3ae38dd9c8694c67bd0b361f5fb320d55438928 Mon Sep 17 00:00:00 2001
From: shown <yuluo08290126@gmail.com>
Date: Mon, 23 Jun 2025 18:40:46 +0800
Subject: [PATCH 112/165] chore: Remove useless proerties tags (#3634)

Fixes #3634

Signed-off-by: shown <yuluo08290126@gmail.com>
(cherry picked from commit ea55be2b28805f600e49d22b9265cf28e7562806)
---
 models/spring-ai-openai/pom.xml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/models/spring-ai-openai/pom.xml b/models/spring-ai-openai/pom.xml
index cb256062106..4279a0c59a3 100644
--- a/models/spring-ai-openai/pom.xml
+++ b/models/spring-ai-openai/pom.xml
@@ -36,9 +36,6 @@
 		<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
 	</scm>
 
-	<properties>
-	</properties>
-
 	<dependencies>
 		
 		<!-- production dependencies -->

From c13a8aad7b41d7b4e016e05c9a5a764bede72460 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Mon, 23 Jun 2025 19:57:36 +0900
Subject: [PATCH 113/165] fix: correct SimpleLoggerAdvisor documentation
 (#3638)

Fixes #3638

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit 873393886f6a4e799285f118382bc1e8b9a0d426)
---
 .../main/antora/modules/ROOT/pages/api/chatclient.adoc | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
index 92541ab7449..16765d7ef6d 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
@@ -593,8 +593,9 @@ You can customize what data from `AdvisedRequest` and `ChatResponse` is logged b
 [source,java]
 ----
 SimpleLoggerAdvisor(
-    Function<AdvisedRequest, String> requestToString,
-    Function<ChatResponse, String> responseToString
+    Function<ChatClientRequest, String> requestToString,
+    Function<ChatResponse, String> responseToString,
+    int order
 )
 ----
 
@@ -603,8 +604,9 @@ Example usage:
 [source,java]
 ----
 SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
-    request -> "Custom request: " + request.userText,
-    response -> "Custom response: " + response.getResult()
+    request -> "Custom request: " + request.prompt().getUserMessage(),
+    response -> "Custom response: " + response.getResult(),
+    0
 );
 ----
 

From d11d3e11b5c334fb481d11ad4dafaadaf61ea5ea Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Mon, 23 Jun 2025 20:20:55 +0900
Subject: [PATCH 114/165] migrate junit4 to junit5 in
 JdbcChatMemoryRepositoryHsqldbAutoConfigIT (#3633)

Fixes #3633

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit 28f86bf297a9217f37ea2b7f0f29f74a06299c20)
---
 ...hatMemoryRepositoryHsqldbAutoConfigurationIT.java | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java
index 90c32235f80..abfd6927a46 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java
@@ -18,9 +18,9 @@
 
 import java.util.List;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
 
 import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
 import org.springframework.ai.chat.messages.AssistantMessage;
@@ -34,12 +34,12 @@
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.ApplicationContext;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
 
-@RunWith(SpringRunner.class)
+@ExtendWith(SpringExtension.class)
 @SpringBootTest(classes = JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.TestConfig.class,
 		properties = { "spring.datasource.url=jdbc:hsqldb:mem:chat_memory_auto_configuration_test;DB_CLOSE_DELAY=-1",
 				"spring.datasource.username=sa", "spring.datasource.password=",
@@ -66,7 +66,7 @@ public class JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT {
 	/**
 	 * can't get the automatic loading of the schema with boot to work.
 	 */
-	@Before
+	@BeforeEach
 	public void setUp() {
 		// Explicitly initialize the schema
 		try {

From 3bcdb9d8127225d81d12d0523e94c40735d0e7fb Mon Sep 17 00:00:00 2001
From: Jemin Huh <hjm1980@gmail.com>
Date: Mon, 23 Jun 2025 23:28:26 +0900
Subject: [PATCH 115/165] Add FileDocumentWriterTest for FileDocumentWriter
 default constructor and various options

Fixes #3646

Signed-off-by: Jemin Huh <hjm1980@gmail.com>
(cherry picked from commit 089c3eb6afb08f00039c380d49fee0ae63310db0)
---
 .../ai/writer/FileDocumentWriterTest.java     | 145 ++++++++++++++++++
 1 file changed, 145 insertions(+)
 create mode 100644 spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java

diff --git a/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java b/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java
new file mode 100644
index 00000000000..e6a3c20f5f9
--- /dev/null
+++ b/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java
@@ -0,0 +1,145 @@
+package org.springframework.ai.writer;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.document.MetadataMode;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Jemin Huh
+ */
+public class FileDocumentWriterTest {
+
+	@TempDir
+	Path tempDir;
+
+	private String testFileName;
+
+	private List<Document> testDocuments;
+
+	@BeforeEach
+	void setUp() {
+		testFileName = tempDir.resolve("file-document-test-output.txt").toString();
+		testDocuments = List.of(
+				Document.builder()
+					.text("Document one introduces the core functionality of Spring AI.")
+					.metadata("page_number", "1")
+					.metadata("end_page_number", "2")
+					.metadata("source", "intro.pdf")
+					.metadata("title", "Spring AI Overview")
+					.metadata("author", "QA Team")
+					.build(),
+				Document.builder()
+					.text("Document two illustrates multi-line handling and line breaks.\nEnsure preservation of formatting.")
+					.metadata("page_number", "3")
+					.metadata("end_page_number", "4")
+					.metadata("source", "formatting.pdf")
+					.build(),
+				Document.builder()
+					.text("Document three checks metadata inclusion and output formatting behavior.")
+					.metadata("page_number", "5")
+					.metadata("end_page_number", "6")
+					.metadata("version", "v1.2")
+					.build());
+	}
+
+	@Test
+	void testBasicWrite() throws IOException {
+		var writer = new FileDocumentWriter(testFileName);
+		writer.accept(testDocuments);
+
+		List<String> lines = Files.readAllLines(Path.of(testFileName));
+		assertEquals("", lines.get(0));
+		assertEquals("", lines.get(1));
+		assertEquals("Document one introduces the core functionality of Spring AI.", lines.get(2));
+		assertEquals("", lines.get(3));
+		assertEquals("Document two illustrates multi-line handling and line breaks.", lines.get(4));
+		assertEquals("Ensure preservation of formatting.", lines.get(5));
+		assertEquals("", lines.get(6));
+		assertEquals("Document three checks metadata inclusion and output formatting behavior.", lines.get(7));
+	}
+
+	@Test
+	void testWriteWithDocumentMarkers() throws IOException {
+		var writer = new FileDocumentWriter(testFileName, true, MetadataMode.NONE, false);
+		writer.accept(testDocuments);
+
+		List<String> lines = Files.readAllLines(Path.of(testFileName));
+		assertEquals("", lines.get(0));
+		assertEquals("### Doc: 0, pages:[1,2]", lines.get(1));
+		assertEquals("", lines.get(2));
+		assertEquals("", lines.get(3));
+		assertEquals("Document one introduces the core functionality of Spring AI.", lines.get(4));
+		assertEquals("### Doc: 1, pages:[3,4]", lines.get(5));
+		assertEquals("", lines.get(6));
+		assertEquals("", lines.get(7));
+		assertEquals("Document two illustrates multi-line handling and line breaks.", lines.get(8));
+		assertEquals("Ensure preservation of formatting.", lines.get(9));
+		assertEquals("### Doc: 2, pages:[5,6]", lines.get(10));
+		assertEquals("", lines.get(11));
+		assertEquals("", lines.get(12));
+		assertEquals("Document three checks metadata inclusion and output formatting behavior.", lines.get(13));
+	}
+
+	@Test
+	void testMetadataModeAllWithDocumentMarkers() throws IOException {
+		var writer = new FileDocumentWriter(testFileName, true, MetadataMode.ALL, false);
+		writer.accept(testDocuments);
+
+		List<String> lines = Files.readAllLines(Path.of(testFileName));
+		assertEquals("", lines.get(0));
+		assertEquals("### Doc: 0, pages:[1,2]", lines.get(1));
+		String subListToString = lines.subList(2, 7).toString();
+		assertTrue(subListToString.contains("page_number: 1"));
+		assertTrue(subListToString.contains("end_page_number: 2"));
+		assertTrue(subListToString.contains("source: intro.pdf"));
+		assertTrue(subListToString.contains("title: Spring AI Overview"));
+		assertTrue(subListToString.contains("author: QA Team"));
+		assertEquals("", lines.get(7));
+		assertEquals("Document one introduces the core functionality of Spring AI.", lines.get(8));
+
+		assertEquals("### Doc: 1, pages:[3,4]", lines.get(9));
+		subListToString = lines.subList(10, 13).toString();
+		assertTrue(subListToString.contains("page_number: 3"));
+		assertTrue(subListToString.contains("source: formatting.pdf"));
+		assertTrue(subListToString.contains("end_page_number: 4"));
+		assertEquals("", lines.get(13));
+		assertEquals("Document two illustrates multi-line handling and line breaks.", lines.get(14));
+		assertEquals("Ensure preservation of formatting.", lines.get(15));
+
+		assertEquals("### Doc: 2, pages:[5,6]", lines.get(16));
+		subListToString = lines.subList(17, 20).toString();
+		assertTrue(subListToString.contains("page_number: 5"));
+		assertTrue(subListToString.contains("end_page_number: 6"));
+		assertTrue(subListToString.contains("version: v1.2"));
+		assertEquals("", lines.get(20));
+		assertEquals("Document three checks metadata inclusion and output formatting behavior.", lines.get(21));
+	}
+
+	@Test
+	void testAppendWrite() throws IOException {
+		Files.writeString(Path.of(testFileName), "Test String\n");
+
+		var writer = new FileDocumentWriter(testFileName, false, MetadataMode.NONE, true);
+		writer.accept(testDocuments.subList(0, 2));
+
+		List<String> lines = Files.readAllLines(Path.of(testFileName));
+		assertEquals("Test String", lines.get(0));
+		assertEquals("", lines.get(1));
+		assertEquals("", lines.get(2));
+		assertEquals("Document one introduces the core functionality of Spring AI.", lines.get(3));
+		assertEquals("", lines.get(4));
+		assertEquals("Document two illustrates multi-line handling and line breaks.", lines.get(5));
+		assertEquals("Ensure preservation of formatting.", lines.get(6));
+		assertEquals(7, lines.size());
+	}
+
+}

From 75d4e6f2d7d5c8ef3caaf855f861f35335adbc38 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Tue, 24 Jun 2025 22:35:15 +0800
Subject: [PATCH 116/165] fix: GH-3646 Added a license header for
 FileDocumentWriterTest (#3651)

Fixes #3651

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Co-authored-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit c81667dfb994b1d1a232b0e06715446e48131ba4)
---
 .../ai/writer/FileDocumentWriterTest.java        | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java b/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java
index e6a3c20f5f9..f7c8c6f146c 100644
--- a/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java
+++ b/spring-ai-commons/src/test/java/org/springframework/ai/writer/FileDocumentWriterTest.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2025 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.writer;
 
 import org.junit.jupiter.api.BeforeEach;

From d48467a6e2066bc1240db68b6217482970e5afb5 Mon Sep 17 00:00:00 2001
From: Nirsa <60082298+KoreaNirsa@users.noreply.github.com>
Date: Wed, 25 Jun 2025 17:16:47 +0900
Subject: [PATCH 117/165] Refactor: remove redundant null check in
 Builder#apiKey(String) (#3654)

Fixes #3654

Signed-off-by: nirsa <islandtim@naver.com>
(cherry picked from commit a1c341ad9ca3b6ab0a65cf379f6949197339d06c)
---
 .../main/java/org/springframework/ai/openai/api/OpenAiApi.java   | 1 -
 1 file changed, 1 deletion(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index 21a90a5d848..383065fc209 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -1909,7 +1909,6 @@ public Builder apiKey(ApiKey apiKey) {
 		}
 
 		public Builder apiKey(String simpleApiKey) {
-			Assert.notNull(simpleApiKey, "simpleApiKey cannot be null");
 			this.apiKey = new SimpleApiKey(simpleApiKey);
 			return this;
 		}

From caff575d465a147aad1154a480006cda948051c8 Mon Sep 17 00:00:00 2001
From: Alexandros Pappas <6052803+apappascs@users.noreply.github.com>
Date: Wed, 25 Jun 2025 10:18:14 +0200
Subject: [PATCH 118/165] fix: Correct naming inconsistencies in Mistral
 package (#3652)

Replaced incorrect references to 'OpenAI' with 'Mistral' in the Mistral package.

Fixes #3652

Signed-off-by: Alexandros Pappas <apappascs@gmail.com>
(cherry picked from commit 4cc4b106e65c131fd8f5a402a388970de73916f9)
---
 .../org/springframework/ai/mistralai/MistralAiChatModel.java  | 2 +-
 .../ai/mistralai/moderation/MistralAiModerationModel.java     | 4 ++--
 .../ai/mistralai/MistralAiChatModelObservationIT.java         | 2 +-
 .../ai/mistralai/MistralAiEmbeddingModelObservationIT.java    | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java
index b9838dcedf1..a2e61862851 100644
--- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java
+++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java
@@ -99,7 +99,7 @@ public class MistralAiChatModel implements ChatModel {
 	private final MistralAiChatOptions defaultOptions;
 
 	/**
-	 * Low-level access to the OpenAI API.
+	 * Low-level access to the Mistral API.
 	 */
 	private final MistralAiApi mistralAiApi;
 
diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java
index 076d9a3433e..6ec6d554b2b 100644
--- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java
+++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java
@@ -102,10 +102,10 @@ public ModerationResponse call(ModerationPrompt moderationPrompt) {
 	}
 
 	private ModerationResponse convertResponse(ResponseEntity<MistralAiModerationResponse> moderationResponseEntity,
-			MistralAiModerationRequest openAiModerationRequest) {
+			MistralAiModerationRequest mistralAiModerationRequest) {
 		var moderationApiResponse = moderationResponseEntity.getBody();
 		if (moderationApiResponse == null) {
-			logger.warn("No moderation response returned for request: {}", openAiModerationRequest);
+			logger.warn("No moderation response returned for request: {}", mistralAiModerationRequest);
 			return new ModerationResponse(new Generation());
 		}
 
diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java
index 7e2aeed21ea..1d5a2ac2f2a 100644
--- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java
+++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java
@@ -184,7 +184,7 @@ public MistralAiApi mistralAiApi() {
 		}
 
 		@Bean
-		public MistralAiChatModel openAiChatModel(MistralAiApi mistralAiApi,
+		public MistralAiChatModel mistralAiChatModel(MistralAiApi mistralAiApi,
 				TestObservationRegistry observationRegistry) {
 			return MistralAiChatModel.builder()
 				.mistralAiApi(mistralAiApi)
diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java
index 813c62bbcd1..7d5d6cd801e 100644
--- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java
+++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java
@@ -105,7 +105,7 @@ public MistralAiApi mistralAiApi() {
 		}
 
 		@Bean
-		public MistralAiEmbeddingModel openAiEmbeddingModel(MistralAiApi mistralAiApi,
+		public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiApi mistralAiApi,
 				TestObservationRegistry observationRegistry) {
 			return new MistralAiEmbeddingModel(mistralAiApi, MetadataMode.EMBED,
 					MistralAiEmbeddingOptions.builder().build(), RetryTemplate.defaultInstance(), observationRegistry);

From ea7773ccbc87003f012ec361793565686a7b282d Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Wed, 25 Jun 2025 17:21:29 +0900
Subject: [PATCH 119/165] fix incorrect ollama autoconfiguration changes
 (#3645)

Fixes #3645

Signed-off-by: jonghoonpark <dev@jonghoonpark.com>
(cherry picked from commit 288fdf96c01ce763670e8f9e6d040f07d9f0e9fd)
---
 .../pom.xml                                   |   1 -
 .../OllamaApiAutoConfiguration.java           |   7 +-
 .../OllamaChatAutoConfiguration.java          |   6 +-
 .../OllamaEmbeddingModelObservationIT.java    |   4 +-
 .../ollama/OllamaAutoConfiguration.java       | 153 ------------------
 5 files changed, 13 insertions(+), 158 deletions(-)
 delete mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java

diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
index d2751f5c7a0..dbebc20c2b7 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
@@ -39,7 +39,6 @@
 			<groupId>org.springframework.ai</groupId>
 			<artifactId>spring-ai-autoconfigure-retry</artifactId>
 			<version>${project.parent.version}</version>
-			<optional>true</optional>
 		</dependency>
 
 		<dependency>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java
index dcdd8c2fbf7..a3b0904811f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java
@@ -17,12 +17,15 @@
 package org.springframework.ai.model.ollama.autoconfigure;
 
 import org.springframework.ai.ollama.api.OllamaApi;
+import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.ResponseErrorHandler;
 import org.springframework.web.client.RestClient;
 import org.springframework.web.reactive.function.client.WebClient;
 
@@ -38,6 +41,7 @@
 @AutoConfiguration
 @ConditionalOnClass(OllamaApi.class)
 @EnableConfigurationProperties(OllamaConnectionProperties.class)
+@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class })
 public class OllamaApiAutoConfiguration {
 
 	@Bean
@@ -50,11 +54,12 @@ public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectio
 	@ConditionalOnMissingBean
 	public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails,
 			ObjectProvider<RestClient.Builder> restClientBuilderProvider,
-			ObjectProvider<WebClient.Builder> webClientBuilderProvider) {
+			ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
 		return OllamaApi.builder()
 			.baseUrl(connectionDetails.getBaseUrl())
 			.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
 			.webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder))
+			.responseErrorHandler(responseErrorHandler)
 			.build();
 	}
 
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java
index 98518ba4568..e8cbc25a31f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java
@@ -39,6 +39,7 @@
 import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
+import org.springframework.retry.support.RetryTemplate;
 
 /**
  * {@link AutoConfiguration Auto-configuration} for Ollama Chat model.
@@ -47,6 +48,7 @@
  * @author Eddú Meléndez
  * @author Thomas Vitale
  * @author Ilayaperumal Gopinathan
+ * @author Jonghoon Park
  * @since 0.8.0
  */
 @AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
@@ -64,7 +66,8 @@ public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties
 			OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager,
 			ObjectProvider<ObservationRegistry> observationRegistry,
 			ObjectProvider<ChatModelObservationConvention> observationConvention,
-			ObjectProvider<ToolExecutionEligibilityPredicate> ollamaToolExecutionEligibilityPredicate) {
+			ObjectProvider<ToolExecutionEligibilityPredicate> ollamaToolExecutionEligibilityPredicate,
+			RetryTemplate retryTemplate) {
 		var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy()
 				: PullModelStrategy.NEVER;
 
@@ -78,6 +81,7 @@ public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties
 			.modelManagementOptions(
 					new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(),
 							initProperties.getTimeout(), initProperties.getMaxRetries()))
+			.retryTemplate(retryTemplate)
 			.build();
 
 		observationConvention.ifAvailable(chatModel::setObservationConvention);
diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaEmbeddingModelObservationIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaEmbeddingModelObservationIT.java
index baaa9ab21d0..a94dbbe6312 100644
--- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaEmbeddingModelObservationIT.java
+++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaEmbeddingModelObservationIT.java
@@ -97,12 +97,12 @@ public TestObservationRegistry observationRegistry() {
 		}
 
 		@Bean
-		public OllamaApi openAiApi() {
+		public OllamaApi ollamaApi() {
 			return initializeOllama(MODEL);
 		}
 
 		@Bean
-		public OllamaEmbeddingModel openAiEmbeddingModel(OllamaApi ollamaApi,
+		public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi,
 				TestObservationRegistry observationRegistry) {
 			return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).observationRegistry(observationRegistry).build();
 		}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java
deleted file mode 100644
index 5675e4c4a1a..00000000000
--- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/ollama/OllamaAutoConfiguration.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2023-2025 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.autoconfigure.ollama;
-
-import io.micrometer.observation.ObservationRegistry;
-
-import org.springframework.ai.autoconfigure.chat.model.ToolCallingAutoConfiguration;
-import org.springframework.ai.chat.observation.ChatModelObservationConvention;
-import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
-import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
-import org.springframework.ai.model.function.FunctionCallbackResolver;
-import org.springframework.ai.model.tool.ToolCallingManager;
-import org.springframework.ai.ollama.OllamaChatModel;
-import org.springframework.ai.ollama.OllamaEmbeddingModel;
-import org.springframework.ai.ollama.api.OllamaApi;
-import org.springframework.ai.ollama.management.ModelManagementOptions;
-import org.springframework.ai.ollama.management.PullModelStrategy;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
-import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.retry.support.RetryTemplate;
-import org.springframework.web.client.RestClient;
-import org.springframework.web.reactive.function.client.WebClient;
-
-/**
- * {@link AutoConfiguration Auto-configuration} for Ollama Chat Client.
- *
- * @author Christian Tzolov
- * @author Eddú Meléndez
- * @author Thomas Vitale
- * @since 0.8.0
- */
-@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
-@ConditionalOnClass(OllamaApi.class)
-@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaEmbeddingProperties.class,
-		OllamaConnectionProperties.class, OllamaInitializationProperties.class })
-@ImportAutoConfiguration(classes = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class,
-		WebClientAutoConfiguration.class })
-public class OllamaAutoConfiguration {
-
-	@Bean
-	@ConditionalOnMissingBean(OllamaConnectionDetails.class)
-	public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) {
-		return new PropertiesOllamaConnectionDetails(properties);
-	}
-
-	@Bean
-	@ConditionalOnMissingBean
-	public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails,
-			ObjectProvider<RestClient.Builder> restClientBuilderProvider,
-			ObjectProvider<WebClient.Builder> webClientBuilderProvider) {
-		return new OllamaApi(connectionDetails.getBaseUrl(),
-				restClientBuilderProvider.getIfAvailable(RestClient::builder),
-				webClientBuilderProvider.getIfAvailable(WebClient::builder));
-	}
-
-	@Bean
-	@ConditionalOnMissingBean
-	@ConditionalOnProperty(prefix = OllamaChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
-			matchIfMissing = true)
-	public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties,
-			OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager,
-			ObjectProvider<ObservationRegistry> observationRegistry,
-			ObjectProvider<ChatModelObservationConvention> observationConvention, RetryTemplate retryTemplate) {
-		var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy()
-				: PullModelStrategy.NEVER;
-
-		var chatModel = OllamaChatModel.builder()
-			.ollamaApi(ollamaApi)
-			.defaultOptions(properties.getOptions())
-			.toolCallingManager(toolCallingManager)
-			.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
-			.modelManagementOptions(
-					new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(),
-							initProperties.getTimeout(), initProperties.getMaxRetries()))
-			.retryTemplate(retryTemplate)
-			.build();
-
-		observationConvention.ifAvailable(chatModel::setObservationConvention);
-
-		return chatModel;
-	}
-
-	@Bean
-	@ConditionalOnMissingBean
-	@ConditionalOnProperty(prefix = OllamaEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
-			matchIfMissing = true)
-	public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties,
-			OllamaInitializationProperties initProperties, ObjectProvider<ObservationRegistry> observationRegistry,
-			ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
-		var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude()
-				? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER;
-
-		var embeddingModel = OllamaEmbeddingModel.builder()
-			.ollamaApi(ollamaApi)
-			.defaultOptions(properties.getOptions())
-			.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
-			.modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy,
-					initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(),
-					initProperties.getMaxRetries()))
-			.build();
-
-		observationConvention.ifAvailable(embeddingModel::setObservationConvention);
-
-		return embeddingModel;
-	}
-
-	@Bean
-	@ConditionalOnMissingBean
-	public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
-		DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
-		manager.setApplicationContext(context);
-		return manager;
-	}
-
-	static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails {
-
-		private final OllamaConnectionProperties properties;
-
-		PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) {
-			this.properties = properties;
-		}
-
-		@Override
-		public String getBaseUrl() {
-			return this.properties.getBaseUrl();
-		}
-
-	}
-
-}

From 35894f3b9be30e28ca3bb91b8278bf9cf285ec72 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Wed, 25 Jun 2025 16:24:31 +0800
Subject: [PATCH 120/165] cleanup: Removed redundant `SOURCE_METADATA`
 assignments in `TextReader` and unwrapped some unnecessary conditional
 checks. (#3627)

Fixes #3627

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Co-authored-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit c1ee22dad30534ef15f0fe48f52c7a881c413192)
---
 .../org/springframework/ai/reader/TextReader.java     | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/spring-ai-commons/src/main/java/org/springframework/ai/reader/TextReader.java b/spring-ai-commons/src/main/java/org/springframework/ai/reader/TextReader.java
index 9e2a8471923..85733fb960c 100644
--- a/spring-ai-commons/src/main/java/org/springframework/ai/reader/TextReader.java
+++ b/spring-ai-commons/src/main/java/org/springframework/ai/reader/TextReader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -90,7 +90,6 @@ public List<Document> get() {
 
 			// Inject source information as a metadata.
 			this.customMetadata.put(CHARSET_METADATA, this.charset.name());
-			this.customMetadata.put(SOURCE_METADATA, this.resource.getFilename());
 			this.customMetadata.put(SOURCE_METADATA, getResourceIdentifier(this.resource));
 
 			return List.of(new Document(document, this.customMetadata));
@@ -111,9 +110,7 @@ protected String getResourceIdentifier(Resource resource) {
 		// Try to get the URI
 		try {
 			URI uri = resource.getURI();
-			if (uri != null) {
-				return uri.toString();
-			}
+			return uri.toString();
 		}
 		catch (IOException ignored) {
 			// If getURI() throws an exception, we'll try the next method
@@ -122,9 +119,7 @@ protected String getResourceIdentifier(Resource resource) {
 		// Try to get the URL
 		try {
 			URL url = resource.getURL();
-			if (url != null) {
-				return url.toString();
-			}
+			return url.toString();
 		}
 		catch (IOException ignored) {
 			// If getURL() throws an exception, we'll fall back to getDescription()

From 50e8615720d2fb344510637b1989f6ea449fa1c6 Mon Sep 17 00:00:00 2001
From: WOONBE <kepull2918@naver.com>
Date: Fri, 20 Jun 2025 14:23:12 +0900
Subject: [PATCH 121/165] fix getTextBetweenParagraphs to check for invalid
 page numbers in outline items

Fix ParagraphPdfDocumentReader to reliably extract text from PDFs with imperfect outlines and coordinate edge cases

Test : Add test to validate ParagraphPdfDocumentReader to skip Invalid Outline

Fixes #3421

Signed-off-by: WOONBE <kepull2918@naver.com>
(cherry picked from commit d92a2ea35152b56967f7c335a98f005ad2f982d5)
---
 .../pdf/ParagraphPdfDocumentReader.java       |  94 +++++++++---------
 .../pdf/ParagraphPdfDocumentReaderTests.java  |  56 ++++++++++-
 .../pdf-reader/src/test/resources/sample3.pdf | Bin 0 -> 1295 bytes
 3 files changed, 101 insertions(+), 49 deletions(-)
 create mode 100644 document-readers/pdf-reader/src/test/resources/sample3.pdf

diff --git a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java
index 95863fff649..d8be2179675 100644
--- a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java
+++ b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -46,6 +46,7 @@
  * The paragraphs are grouped into {@link Document} objects.
  *
  * @author Christian Tzolov
+ * @author Heonwoo Kim
  */
 public class ParagraphPdfDocumentReader implements DocumentReader {
 
@@ -127,29 +128,18 @@ public ParagraphPdfDocumentReader(Resource pdfResource, PdfDocumentReaderConfig
 	 */
 	@Override
 	public List<Document> get() {
-
 		var paragraphs = this.paragraphTextExtractor.flatten();
-
-		List<Document> documents = new ArrayList<>(paragraphs.size());
-
-		if (!CollectionUtils.isEmpty(paragraphs)) {
-			logger.info("Start processing paragraphs from PDF");
-			Iterator<Paragraph> itr = paragraphs.iterator();
-
-			var current = itr.next();
-
-			if (!itr.hasNext()) {
-				documents.add(toDocument(current, current));
-			}
-			else {
-				while (itr.hasNext()) {
-					var next = itr.next();
-					Document document = toDocument(current, next);
-					if (document != null && StringUtils.hasText(document.getText())) {
-						documents.add(toDocument(current, next));
-					}
-					current = next;
-				}
+		List<Document> documents = new ArrayList<>();
+		if (CollectionUtils.isEmpty(paragraphs)) {
+			return documents;
+		}
+		logger.info("Start processing paragraphs from PDF");
+		for (int i = 0; i < paragraphs.size(); i++) {
+			Paragraph from = paragraphs.get(i);
+			Paragraph to = (i + 1 < paragraphs.size()) ? paragraphs.get(i + 1) : from;
+			Document document = toDocument(from, to);
+			if (document != null && StringUtils.hasText(document.getText())) {
+				documents.add(document);
 			}
 		}
 		logger.info("End processing paragraphs from PDF");
@@ -173,17 +163,27 @@ protected Document toDocument(Paragraph from, Paragraph to) {
 	protected void addMetadata(Paragraph from, Paragraph to, Document document) {
 		document.getMetadata().put(METADATA_TITLE, from.title());
 		document.getMetadata().put(METADATA_START_PAGE, from.startPageNumber());
-		document.getMetadata().put(METADATA_END_PAGE, to.startPageNumber());
+		document.getMetadata().put(METADATA_END_PAGE, from.endPageNumber());
 		document.getMetadata().put(METADATA_LEVEL, from.level());
 		document.getMetadata().put(METADATA_FILE_NAME, this.resourceFileName);
 	}
 
 	public String getTextBetweenParagraphs(Paragraph fromParagraph, Paragraph toParagraph) {
 
+		if (fromParagraph.startPageNumber() < 1) {
+			logger.warn("Skipping paragraph titled '{}' because it has an invalid start page number: {}",
+					fromParagraph.title(), fromParagraph.startPageNumber());
+			return "";
+		}
+
 		// Page started from index 0, while PDFBOx getPage return them from index 1.
 		int startPage = fromParagraph.startPageNumber() - 1;
 		int endPage = toParagraph.startPageNumber() - 1;
 
+		if (fromParagraph == toParagraph || endPage < startPage) {
+			endPage = startPage;
+		}
+
 		try {
 
 			StringBuilder sb = new StringBuilder();
@@ -194,39 +194,37 @@ public String getTextBetweenParagraphs(Paragraph fromParagraph, Paragraph toPara
 			for (int pageNumber = startPage; pageNumber <= endPage; pageNumber++) {
 
 				var page = this.document.getPage(pageNumber);
+				float pageHeight = page.getMediaBox().getHeight();
 
-				int fromPosition = fromParagraph.position();
-				int toPosition = toParagraph.position();
-
-				if (this.config.reversedParagraphPosition) {
-					fromPosition = (int) (page.getMediaBox().getHeight() - fromPosition);
-					toPosition = (int) (page.getMediaBox().getHeight() - toPosition);
-				}
-
-				int x0 = (int) page.getMediaBox().getLowerLeftX();
-				int xW = (int) page.getMediaBox().getWidth();
+				int fromPos = fromParagraph.position();
+				int toPos = (fromParagraph != toParagraph) ? toParagraph.position() : 0;
 
-				int y0 = (int) page.getMediaBox().getLowerLeftY();
-				int yW = (int) page.getMediaBox().getHeight();
+				int x = (int) page.getMediaBox().getLowerLeftX();
+				int w = (int) page.getMediaBox().getWidth();
+				int y, h;
 
-				if (pageNumber == startPage) {
-					y0 = fromPosition;
-					yW = (int) page.getMediaBox().getHeight() - y0;
+				if (pageNumber == startPage && pageNumber == endPage) {
+					y = toPos;
+					h = fromPos - toPos;
 				}
-				if (pageNumber == endPage) {
-					yW = toPosition - y0;
+				else if (pageNumber == startPage) {
+					y = 0;
+					h = fromPos;
 				}
-
-				if ((y0 + yW) == (int) page.getMediaBox().getHeight()) {
-					yW = yW - this.config.pageBottomMargin;
+				else if (pageNumber == endPage) {
+					y = toPos;
+					h = (int) pageHeight - toPos;
+				}
+				else {
+					y = 0;
+					h = (int) pageHeight;
 				}
 
-				if (y0 == 0) {
-					y0 = y0 + this.config.pageTopMargin;
-					yW = yW - this.config.pageTopMargin;
+				if (h < 0) {
+					h = 0;
 				}
 
-				pdfTextStripper.addRegion("pdfPageRegion", new Rectangle(x0, y0, xW, yW));
+				pdfTextStripper.addRegion("pdfPageRegion", new Rectangle(x, y, w, h));
 				pdfTextStripper.extractRegions(page);
 				var text = pdfTextStripper.getTextForRegion("pdfPageRegion");
 				if (StringUtils.hasText(text)) {
diff --git a/document-readers/pdf-reader/src/test/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReaderTests.java b/document-readers/pdf-reader/src/test/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReaderTests.java
index b514f690e11..2e3351957cb 100644
--- a/document-readers/pdf-reader/src/test/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReaderTests.java
+++ b/document-readers/pdf-reader/src/test/java/org/springframework/ai/reader/pdf/ParagraphPdfDocumentReaderTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -16,15 +16,32 @@
 
 package org.springframework.ai.reader.pdf;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
+import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
 import org.junit.jupiter.api.Test;
 
+import org.springframework.ai.document.Document;
 import org.springframework.ai.reader.ExtractedTextFormatter;
 import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 
 /**
  * @author Christian Tzolov
+ * @author Heonwoo Kim
  */
 public class ParagraphPdfDocumentReaderTests {
 
@@ -50,4 +67,41 @@ public void testPdfWithoutToc() {
 
 	}
 
+	@Test
+	void shouldSkipInvalidOutline() throws IOException {
+
+		Resource basePdfResource = new ClassPathResource("sample3.pdf");
+
+		PDDocument documentToModify;
+		try (InputStream inputStream = basePdfResource.getInputStream()) {
+
+			byte[] pdfBytes = inputStream.readAllBytes();
+
+			documentToModify = Loader.loadPDF(pdfBytes);
+		}
+		PDDocumentOutline outline = documentToModify.getDocumentCatalog().getDocumentOutline();
+		if (outline != null && outline.getFirstChild() != null) {
+			PDOutlineItem chapter2OutlineItem = outline.getFirstChild().getNextSibling();
+			if (chapter2OutlineItem != null) {
+
+				chapter2OutlineItem.setDestination((PDDestination) null);
+			}
+		}
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		documentToModify.save(baos);
+		documentToModify.close();
+
+		Resource corruptedPdfResource = new ByteArrayResource(baos.toByteArray());
+
+		ParagraphPdfDocumentReader reader = new ParagraphPdfDocumentReader(corruptedPdfResource,
+				PdfDocumentReaderConfig.defaultConfig());
+
+		List<Document> documents = assertDoesNotThrow(() -> reader.get());
+
+		assertThat(documents).isNotNull();
+		assertThat(documents).hasSize(2);
+		assertThat(documents.get(0).getMetadata().get("title")).isEqualTo("Chapter 1");
+		assertThat(documents.get(1).getMetadata().get("title")).isEqualTo("Chapter 3");
+	}
+
 }
diff --git a/document-readers/pdf-reader/src/test/resources/sample3.pdf b/document-readers/pdf-reader/src/test/resources/sample3.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..8ed8b40633c853d6c288738f9f679b2fc12e7763
GIT binary patch

From db5c9a707aa91ebff8880bd0a0a1260e931ecd46 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <151690+ilayaperumalg@users.noreply.github.com>
Date: Wed, 25 Jun 2025 17:04:55 +0100
Subject: [PATCH 122/165] Use OkHttpClient for
 AzureOpenAiAudioTranscriptionModelIT (#3665)

- To fix the race condition with the default HTTP client used by the Azure OpenAIClient, switch to use OkHttpClient.
   More discussion on this here: https://github.com/Azure/azure-sdk-for-java/issues/43583

 - Thanks to @Allamss for his suggestion here: https://github.com/Azure/azure-sdk-for-java/issues/43583#issuecomment-2785088500

Fixes #3665

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
(cherry picked from commit a9fc130f9d8f1ae0b264798e53a07de2ba308251)
---
 models/spring-ai-azure-openai/pom.xml             |  7 +++++++
 .../AzureOpenAiAudioTranscriptionModelIT.java     | 15 ++++++++++++++-
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/models/spring-ai-azure-openai/pom.xml b/models/spring-ai-azure-openai/pom.xml
index ef46ac90713..c063ad02058 100644
--- a/models/spring-ai-azure-openai/pom.xml
+++ b/models/spring-ai-azure-openai/pom.xml
@@ -83,6 +83,13 @@
 			<artifactId>micrometer-observation-test</artifactId>
 			<scope>test</scope>
 		</dependency>
+
+		<dependency>
+			<groupId>com.azure</groupId>
+			<artifactId>azure-core-http-okhttp</artifactId>
+			<version>1.12.11</version>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 
 </project>
diff --git a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiAudioTranscriptionModelIT.java b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiAudioTranscriptionModelIT.java
index 85e0a54b360..abad2ee928d 100644
--- a/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiAudioTranscriptionModelIT.java
+++ b/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiAudioTranscriptionModelIT.java
@@ -1,3 +1,4 @@
+
 /*
  * Copyright 2023-2024 the original author or authors.
  *
@@ -16,9 +17,13 @@
 
 package org.springframework.ai.azure.openai;
 
+import java.util.concurrent.TimeUnit;
+
 import com.azure.ai.openai.OpenAIClient;
 import com.azure.ai.openai.OpenAIClientBuilder;
 import com.azure.core.credential.AzureKeyCredential;
+import com.azure.core.http.okhttp.OkHttpAsyncHttpClientBuilder;
+import okhttp3.OkHttpClient;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables;
@@ -91,8 +96,16 @@ public OpenAIClient openAIClient() {
 
 			// System.out.println("API Key: " + apiKey);
 			// System.out.println("Endpoint: " + endpoint);
+			int readTimeout = 120;
+			int writeTimeout = 120;
+
+			// OkHttp client with long timeouts
+			OkHttpClient okHttpClient = new OkHttpClient.Builder().readTimeout(readTimeout, TimeUnit.SECONDS)
+				.callTimeout(writeTimeout, TimeUnit.SECONDS)
+				.build();
 
-			return new OpenAIClientBuilder().credential(new AzureKeyCredential(apiKey))
+			return new OpenAIClientBuilder().httpClient(new OkHttpAsyncHttpClientBuilder(okHttpClient).build())
+				.credential(new AzureKeyCredential(apiKey))
 				.endpoint(endpoint)
 				// .serviceVersion(OpenAIServiceVersion.V2024_02_15_PREVIEW)
 				.buildClient();

From f5e202547140419bde4cd47eeb60c91c8d4bb222 Mon Sep 17 00:00:00 2001
From: Filip Hrisafov <filip.hrisafov@gmail.com>
Date: Wed, 25 Jun 2025 19:09:15 +0200
Subject: [PATCH 123/165] Remove `@Nullable` from VectorStore similaritySearch
 (#3613)

The VectorStore#similaritySearch methods never return null, but there's
a data flow issue in AbstractObservationVectorStore caused by Micrometer's
Observation#observe being marked as @Nullable. The returned value is
actually controlled by Spring AI's supplier, which never returns null.

Fixes #3613

Signed-off-by: Filip Hrisafov <filip.hrisafov@gmail.com>
(cherry picked from commit 64cf88a109db786f8f527c2893d0971220e74f5e)
---
 .../org/springframework/ai/vectorstore/VectorStore.java     | 3 ---
 .../observation/AbstractObservationVectorStore.java         | 4 +++-
 .../ai/vectorstore/cosmosdb/CosmosDBVectorStore.java        | 4 +---
 .../ai/chroma/vectorstore/ChromaVectorStore.java            | 6 ++----
 .../ai/vectorstore/gemfire/GemFireVectorStore.java          | 1 -
 5 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStore.java b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStore.java
index df1a11f614d..ec9a6601c47 100644
--- a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStore.java
+++ b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStore.java
@@ -27,7 +27,6 @@
 import org.springframework.ai.vectorstore.filter.Filter;
 import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
 import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
-import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
 
 /**
@@ -91,7 +90,6 @@ default void delete(String filterExpression) {
 	 * topK, similarity threshold and metadata filter expressions.
 	 * @return Returns documents th match the query request conditions.
 	 */
-	@Nullable
 	List<Document> similaritySearch(SearchRequest request);
 
 	/**
@@ -101,7 +99,6 @@ default void delete(String filterExpression) {
 	 * @return Returns a list of documents that have embeddings similar to the query text
 	 * embedding.
 	 */
-	@Nullable
 	default List<Document> similaritySearch(String query) {
 		return this.similaritySearch(SearchRequest.builder().query(query).build());
 	}
diff --git a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java
index ca3c3ae9185..52c8106a48e 100644
--- a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java
+++ b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java
@@ -111,7 +111,9 @@ public void delete(Filter.Expression filterExpression) {
 	}
 
 	@Override
-	@Nullable
+	// Micrometer Observation#observe returns the value of the Supplier, which is never
+	// null
+	@SuppressWarnings("DataFlowIssue")
 	public List<Document> similaritySearch(SearchRequest request) {
 
 		VectorStoreObservationContext searchObservationContext = this
diff --git a/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java b/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java
index ca2a817001a..ccf587b45ce 100644
--- a/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java
+++ b/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java
@@ -434,15 +434,13 @@ public List<Document> doSimilaritySearch(SearchRequest request) {
 			}
 
 			// Convert JsonNode to Document
-			List<Document> docs = documents.stream()
+			return documents.stream()
 				.map(doc -> Document.builder()
 					.id(doc.get("id").asText())
 					.text(doc.get("content").asText())
 					.metadata(docFields)
 					.build())
 				.collect(Collectors.toList());
-
-			return docs != null ? docs : List.of();
 		}
 		catch (Exception e) {
 			logger.error("Error during similarity search: {}", e.getMessage());
diff --git a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java
index a6f6628f6c6..8421058c24c 100644
--- a/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java
+++ b/vector-stores/spring-ai-chroma-store/src/main/java/org/springframework/ai/chroma/vectorstore/ChromaVectorStore.java
@@ -45,7 +45,6 @@
 import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
 import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
 import org.springframework.beans.factory.InitializingBean;
-import org.springframework.lang.NonNull;
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
@@ -148,7 +147,7 @@ public void afterPropertiesSet() throws Exception {
 	}
 
 	@Override
-	public void doAdd(@NonNull List<Document> documents) {
+	public void doAdd(List<Document> documents) {
 		Assert.notNull(documents, "Documents must not be null");
 		if (CollectionUtils.isEmpty(documents)) {
 			return;
@@ -202,8 +201,7 @@ protected void doDelete(Filter.Expression expression) {
 	}
 
 	@Override
-	@NonNull
-	public List<Document> doSimilaritySearch(@NonNull SearchRequest request) {
+	public List<Document> doSimilaritySearch(SearchRequest request) {
 
 		String query = request.getQuery();
 		Assert.notNull(query, "Query string must not be null");
diff --git a/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/gemfire/GemFireVectorStore.java b/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/gemfire/GemFireVectorStore.java
index 6d90043fde4..7cda95ce430 100644
--- a/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/gemfire/GemFireVectorStore.java
+++ b/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/gemfire/GemFireVectorStore.java
@@ -243,7 +243,6 @@ public void doDelete(List<String> idList) {
 	}
 
 	@Override
-	@Nullable
 	public List<Document> doSimilaritySearch(SearchRequest request) {
 		if (request.hasFilterExpression()) {
 			throw new UnsupportedOperationException("GemFire currently does not support metadata filter expressions.");

From 92daf74c133ab3d97e2b6092666277fbf9b10558 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Thu, 26 Jun 2025 14:58:12 +0800
Subject: [PATCH 124/165] fix: ZhiPuAiEmbedding supports custom dimensions
 (#3668)

Fixes #3668

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit 53f8f1771cb5feabb0f38c04f5d2ad651455d74b)
---
 .../ai/zhipuai/ZhiPuAiEmbeddingOptions.java   |  4 +--
 .../ai/zhipuai/embedding/EmbeddingIT.java     | 28 +++++++++++++++++++
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java
index 5dc26a3a71a..fc2c10161e4 100644
--- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java
+++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingOptions.java
@@ -16,7 +16,6 @@
 
 package org.springframework.ai.zhipuai;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -63,9 +62,8 @@ public void setDimensions(Integer dimensions) {
 	}
 
 	@Override
-	@JsonIgnore
 	public Integer getDimensions() {
-		return null;
+		return this.dimensions;
 	}
 
 	public static class Builder {
diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/EmbeddingIT.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/EmbeddingIT.java
index 447546d60db..15d2474bf6b 100644
--- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/EmbeddingIT.java
+++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/EmbeddingIT.java
@@ -21,9 +21,12 @@
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 
+import org.springframework.ai.embedding.EmbeddingRequest;
 import org.springframework.ai.embedding.EmbeddingResponse;
 import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel;
+import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingOptions;
 import org.springframework.ai.zhipuai.ZhiPuAiTestConfiguration;
+import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 
@@ -53,6 +56,31 @@ void defaultEmbedding() {
 		assertThat(this.embeddingModel.dimensions()).isEqualTo(1024);
 	}
 
+	@Test
+	void embeddingV3WithDefault() {
+		EmbeddingResponse embeddingResponse = this.embeddingModel.call(new EmbeddingRequest(List.of("Hello World"),
+				ZhiPuAiEmbeddingOptions.builder().model(ZhiPuAiApi.EmbeddingModel.Embedding_3.getValue()).build()));
+
+		assertThat(embeddingResponse.getResults()).hasSize(1);
+
+		assertThat(embeddingResponse.getResults().get(0)).isNotNull();
+		assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(2048);
+	}
+
+	@Test
+	void embeddingV3WithCustomDimension() {
+		EmbeddingResponse embeddingResponse = this.embeddingModel.call(new EmbeddingRequest(List.of("Hello World"),
+				ZhiPuAiEmbeddingOptions.builder()
+					.model(ZhiPuAiApi.EmbeddingModel.Embedding_3.getValue())
+					.dimensions(512)
+					.build()));
+
+		assertThat(embeddingResponse.getResults()).hasSize(1);
+
+		assertThat(embeddingResponse.getResults().get(0)).isNotNull();
+		assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(512);
+	}
+
 	@Test
 	void batchEmbedding() {
 		assertThat(this.embeddingModel).isNotNull();

From 9e902014c730d8e09712dd2b123285dfeec05827 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Thu, 26 Jun 2025 14:59:58 +0800
Subject: [PATCH 125/165] fix: remove unused options from ZhiPu model (#3669)

Fixes #3669

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
(cherry picked from commit 906299c18333d52aacc542f79841fc68deabd9d6)
---
 .../springframework/ai/zhipuai/ZhiPuAiChatModel.java | 10 ----------
 .../ai/zhipuai/ZhiPuAiChatOptions.java               |  3 ---
 .../springframework/ai/zhipuai/api/ZhiPuAiApi.java   | 12 ------------
 3 files changed, 25 deletions(-)

diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java
index 408666fdc34..565f11d9b41 100644
--- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java
+++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java
@@ -568,16 +568,6 @@ else if (mediaContentData instanceof String text) {
 		}
 	}
 
-	private ChatOptions buildRequestOptions(ZhiPuAiApi.ChatCompletionRequest request) {
-		return ChatOptions.builder()
-			.model(request.model())
-			.maxTokens(request.maxTokens())
-			.stopSequences(request.stop())
-			.temperature(request.temperature())
-			.topP(request.topP())
-			.build();
-	}
-
 	public void setObservationConvention(ChatModelObservationConvention observationConvention) {
 		this.observationConvention = observationConvention;
 	}
diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java
index 8b8d3974413..c31320defe1 100644
--- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java
+++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatOptions.java
@@ -78,9 +78,6 @@ public class ZhiPuAiChatOptions implements ToolCallingChatOptions {
 	 * provide a list of functions the model may generate JSON inputs for.
 	 */
 	private @JsonProperty("tools") List<ZhiPuAiApi.FunctionTool> tools;
-
-	private @JsonProperty("tools1")  List<ZhiPuAiApi.Foo> foos;
-
 	/**
 	 * Controls which (if any) function is called by the model. none means the model will not call a
 	 * function and instead generates a message. auto means the model can pick between generating a message or calling a
diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java
index 0786d0a5b96..cac51cbe232 100644
--- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java
+++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java
@@ -314,18 +314,6 @@ public String getValue() {
 		}
 	}
 
-	public class Foo {
-
-		String foo;
-
-		public Foo() {
-
-		}
-		public Foo(String foo) {
-			this.foo = foo;
-		}
-	}
-
 
 	/**
 	 * Represents a tool the model may call. Currently, only functions are supported as a tool.

From 8b7e42359297fc425a64463d46ffa5d2b63d16d5 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Thu, 26 Jun 2025 17:19:29 +0800
Subject: [PATCH 126/165] fix: Fixed some spelling errors in the documentation
 and comments. (#3678)

Fixes #3678

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Co-authored-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit 32ef3a0107f3bbd1102a041b86380fd6c20b2ca8)
---
 .../src/main/java/org/springframework/ai/mcp/McpToolUtils.java  | 2 +-
 .../src/main/antora/modules/ROOT/pages/api/chatclient.adoc      | 2 +-
 .../ROOT/pages/api/embeddings/azure-openai-embeddings.adoc      | 2 +-
 .../src/main/antora/modules/ROOT/pages/observability/index.adoc | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java
index 58400952518..2f8f366d076 100644
--- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java
+++ b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java
@@ -278,7 +278,7 @@ public static McpServerFeatures.AsyncToolSpecification toAsyncToolSpecification(
 	 * </ul>
 	 * @param toolCallback the Spring AI tool callback to convert
 	 * @param mimeType the MIME type of the output content
-	 * @return an MCP asynchronous tool specificaiotn that wraps the tool callback
+	 * @return an MCP asynchronous tool specification that wraps the tool callback
 	 * @see McpServerFeatures.AsyncToolSpecification
 	 * @see Schedulers#boundedElastic()
 	 */
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
index 16765d7ef6d..48f39dc70cc 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
@@ -296,7 +296,7 @@ Flux<String> output = chatClient.prompt()
 You can also stream the `ChatResponse` using the method `Flux<ChatResponse> chatResponse()`.
 
 In the future, we will offer a convenience method that will let you return a Java entity with the reactive `stream()` method.
-In the meantime, you should use the xref:api/structured-output-converter.adoc#StructuredOutputConverter[Structured Output Converter] to convert the aggregated response explicity as shown below.
+In the meantime, you should use the xref:api/structured-output-converter.adoc#StructuredOutputConverter[Structured Output Converter] to convert the aggregated response explicitly as shown below.
 This also demonstrates the use of parameters in the fluent API that will be discussed in more detail in a later section of the documentation.
 
 [source,java]
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/azure-openai-embeddings.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/azure-openai-embeddings.adoc
index 034282e29d5..a66d840db7d 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/azure-openai-embeddings.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/azure-openai-embeddings.adoc
@@ -145,7 +145,7 @@ The prefix `spring.ai.azure.openai` is the property prefix to configure the conn
 | spring.ai.azure.openai.endpoint | The endpoint from the Azure AI OpenAI `Keys and Endpoint` section under `Resource Management` | -
 | spring.ai.azure.openai.openai-api-key |  (non Azure) OpenAI API key. Used to authenticate with the OpenAI service, instead of Azure OpenAI. 
 This automatically sets the endpoint to https://api.openai.com/v1. Use either `api-key` or `openai-api-key` property. 
-With this configuration the `spring.ai.azure.openai.embedding.options.deployment-name` is threated as an https://platform.openai.com/docs/models[OpenAi Model] name.| -
+With this configuration the `spring.ai.azure.openai.embedding.options.deployment-name` is treated as an https://platform.openai.com/docs/models[OpenAi Model] name.| -
 |====
 
 
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/observability/index.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/observability/index.adoc
index e0341e46889..222ce3ea020 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/observability/index.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/observability/index.adoc
@@ -116,7 +116,7 @@ They measure the time spent in the advisor (including the time spend on the inne
 == Chat Model
 
 NOTE: Observability features are currently supported only for `ChatModel` implementations from the following AI model
-providers: Anthropic, Azure OpenAI, Mistral AI, Ollama, OpenAI, Vertex AI, MiniMax, Moonshot, QianFan, Zhiu AI.
+providers: Anthropic, Azure OpenAI, Mistral AI, Ollama, OpenAI, Vertex AI, MiniMax, Moonshot, QianFan, Zhipu AI.
 Additional AI model providers will be supported in a future release.
 
 The `gen_ai.client.operation` observations are recorded when calling the ChatModel `call` or `stream` methods. 

From 03bd867d84b812b9282dc7e47bbde8037438b14b Mon Sep 17 00:00:00 2001
From: NOUNI El bachir <enimiste@users.noreply.github.com>
Date: Thu, 26 Jun 2025 11:22:59 +0200
Subject: [PATCH 127/165] Fix link in javadoc to Message source (#3672)

Fixes #3672

Signed-off-by: NOUNI El bachir <enimiste@users.noreply.github.com>
(cherry picked from commit 5778b42e92641df7526fd2ae9ddf4f3d34099cfb)
---
 .../src/main/java/org/springframework/ai/content/Content.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-commons/src/main/java/org/springframework/ai/content/Content.java b/spring-ai-commons/src/main/java/org/springframework/ai/content/Content.java
index e5b0e6c12bd..30842eded01 100644
--- a/spring-ai-commons/src/main/java/org/springframework/ai/content/Content.java
+++ b/spring-ai-commons/src/main/java/org/springframework/ai/content/Content.java
@@ -21,7 +21,7 @@
 /**
  * Data structure that contains content and metadata. Common parent for the
  * {@link org.springframework.ai.document.Document} and the
- * org.springframework.ai.chat.messages.Message classes.
+ * {@link org.springframework.ai.chat.messages.Message} classes.
  *
  * @author Mark Pollack
  * @author Christian Tzolov

From 5d8a9303167df0b33b6a87e637ced1aea2c604bb Mon Sep 17 00:00:00 2001
From: Christian Tzolov <christian.tzolov@broadcom.com>
Date: Thu, 26 Jun 2025 11:11:59 +0200
Subject: [PATCH 128/165] fix(anthropic): prevent streaming tool calling
 responses when internal execution is enabled

- For streaming, block tool calling ChatResponse unless internal execution is disabled
- Add streaming validation test and debug logging

Fixes #3679

Related to #3650

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
(cherry picked from commit 879c5e20fda0a799fa840a29bd3d3d21c15bb8c8)
---
 .../ai/anthropic/AnthropicChatModel.java      | 45 +++++++++++--------
 .../ai/anthropic/api/AnthropicApi.java        | 26 +++++++----
 .../client/AnthropicChatClientIT.java         | 43 ++++++++++++++++--
 ...lientMethodInvokingFunctionCallbackIT.java |  2 +-
 .../application-logging-test.properties       |  2 +
 5 files changed, 86 insertions(+), 32 deletions(-)

diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java
index 270f3bef43d..02407e52c41 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java
@@ -260,26 +260,33 @@ public Flux<ChatResponse> internalStream(Prompt prompt, ChatResponse previousCha
 				Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
 				ChatResponse chatResponse = toChatResponse(chatCompletionResponse, accumulatedUsage);
 
-				if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse) && chatResponse.hasFinishReasons(Set.of("tool_use"))) {
-					// FIXME: bounded elastic needs to be used since tool calling
-					//  is currently only synchronous
-					return Flux.defer(() -> {
-						var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, chatResponse);
-						if (toolExecutionResult.returnDirect()) {
-							// Return tool execution result directly to the client.
-							return Flux.just(ChatResponse.builder().from(chatResponse)
-								.generations(ToolExecutionResult.buildGenerations(toolExecutionResult))
-								.build());
-						}
-						else {
-							// Send the tool execution result back to the model.
-							return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),
-									chatResponse);
-						}
-					}).subscribeOn(Schedulers.boundedElastic());
-				}
+				if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), chatResponse)) {
+
+					if (chatResponse.hasFinishReasons(Set.of("tool_use"))) {
+						// FIXME: bounded elastic needs to be used since tool calling
+						//  is currently only synchronous
+						return Flux.defer(() -> {
+							var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, chatResponse);
+							if (toolExecutionResult.returnDirect()) {
+								// Return tool execution result directly to the client.
+								return Flux.just(ChatResponse.builder().from(chatResponse)
+									.generations(ToolExecutionResult.buildGenerations(toolExecutionResult))
+									.build());
+							}
+							else {
+								// Send the tool execution result back to the model.
+								return this.internalStream(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),
+										chatResponse);
+							}
+						}).subscribeOn(Schedulers.boundedElastic());
+					} else {						
+						return Mono.empty();
+					}
 
-				return Mono.just(chatResponse);
+				} else {
+					// If internal tool execution is not required, just return the chat response.
+					return Mono.just(chatResponse);
+				}
 			})
 			.doOnError(observation::error)
 			.doFinally(s -> observation.stop())
diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
index 768690e8f0f..cf410690216 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java
@@ -24,15 +24,8 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonSubTypes;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.ai.anthropic.api.StreamHelper.ChatCompletionResponseBuilder;
 import org.springframework.ai.model.ApiKey;
 import org.springframework.ai.model.ChatModelDescription;
@@ -52,6 +45,16 @@
 import org.springframework.web.client.RestClient;
 import org.springframework.web.reactive.function.client.WebClient;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
 /**
  * The Anthropic API client.
  *
@@ -67,6 +70,8 @@
  */
 public final class AnthropicApi {
 
+	private static final Logger logger = LoggerFactory.getLogger(AnthropicApi.class);
+
 	public static Builder builder() {
 		return new Builder();
 	}
@@ -222,6 +227,9 @@ public Flux<ChatCompletionResponse> chatCompletionStream(ChatCompletionRequest c
 			.filter(event -> event.type() != EventType.PING)
 			// Detect if the chunk is part of a streaming function call.
 			.map(event -> {
+				
+				logger.debug("Received event: {}", event);
+
 				if (this.streamHelper.isToolUseStart(event)) {
 					isInsideTool.set(true);
 				}
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
index 13882775d3f..2baf73dd97e 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
@@ -16,6 +16,8 @@
 
 package org.springframework.ai.anthropic.client;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
 import java.io.IOException;
 import java.net.URL;
 import java.util.Arrays;
@@ -29,8 +31,6 @@
 import org.junit.jupiter.params.provider.ValueSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import reactor.core.publisher.Flux;
-
 import org.springframework.ai.anthropic.AnthropicChatOptions;
 import org.springframework.ai.anthropic.AnthropicTestConfiguration;
 import org.springframework.ai.anthropic.api.AnthropicApi;
@@ -41,7 +41,9 @@
 import org.springframework.ai.chat.model.ChatResponse;
 import org.springframework.ai.converter.BeanOutputConverter;
 import org.springframework.ai.converter.ListOutputConverter;
+import org.springframework.ai.model.tool.ToolCallingChatOptions;
 import org.springframework.ai.test.CurlyBracketEscaper;
+import org.springframework.ai.tool.annotation.Tool;
 import org.springframework.ai.tool.function.FunctionToolCallback;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -53,7 +55,7 @@
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.util.MimeTypeUtils;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import reactor.core.publisher.Flux;
 
 @SpringBootTest(classes = AnthropicTestConfiguration.class, properties = "spring.ai.retry.on-http-codes=429")
 @EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
@@ -343,4 +345,39 @@ record ActorsFilms(String actor, List<String> movies) {
 
 	}
 
+	@ParameterizedTest(name = "{0} : {displayName} ")
+	@ValueSource(strings = { "claude-3-7-sonnet-latest", "claude-sonnet-4-0" })
+	void streamToolCallingResponseShouldNotContainToolCallMessages(String modelName) {
+
+		ChatClient chatClient = ChatClient.builder(this.chatModel).build();
+
+		Flux<ChatResponse> responses = chatClient.prompt()
+			.options(ToolCallingChatOptions.builder().model(modelName).build())
+			.tools(new MyTools())
+			.user("Get current weather in Amsterdam and Paris")
+			// .user("Get current weather in Amsterdam. Please don't explain that you will
+			// call tools.")
+			.stream()
+			.chatResponse();
+
+		List<ChatResponse> chatResponses = responses.collectList().block();
+
+		assertThat(chatResponses).isNotEmpty();
+
+		// Verify that none of the ChatResponse objects have tool calls
+		chatResponses.forEach(chatResponse -> {
+			logger.info("ChatResponse Results: {}", chatResponse.getResults());
+			assertThat(chatResponse.hasToolCalls()).isFalse();
+		});
+	}
+
+	public static class MyTools {
+
+		@Tool(description = "Get the current weather forecast by city name")
+		String getCurrentDateTime(String cityName) {
+			return "For " + cityName + " Weather is hot and sunny with a temperature of 20 degrees";
+		}
+
+	}
+
 }
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java
index 2fa9bccf6bf..56d245a846f 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientMethodInvokingFunctionCallbackIT.java
@@ -290,7 +290,7 @@ void streamingParameterLessTool(String modelName) {
 			.map(cr -> cr.getResult().getOutput().getText())
 			.collect(Collectors.joining());
 
-		assertThat(content).contains("20 degrees");
+		assertThat(content).contains("20");
 	}
 
 	public static class ParameterLessTools {
diff --git a/models/spring-ai-anthropic/src/test/resources/application-logging-test.properties b/models/spring-ai-anthropic/src/test/resources/application-logging-test.properties
index 4466a718052..ee0d793d8b6 100644
--- a/models/spring-ai-anthropic/src/test/resources/application-logging-test.properties
+++ b/models/spring-ai-anthropic/src/test/resources/application-logging-test.properties
@@ -15,3 +15,5 @@
 #
 
 logging.level.org.springframework.ai.chat.client.advisor=DEBUG
+
+logging.level.org.springframework.ai.anthropic.api.AnthropicApi=DEBUG

From f7415a2cdb502bee5897a4bb219071bbcad368b0 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Fri, 27 Jun 2025 19:18:37 +0900
Subject: [PATCH 129/165] fix SimpleApiKey Assert Message (#3656)

Fixes #3656

Signed-off-by: jonghoon park <dev@jonghoonpark.com>
(cherry picked from commit 0ab4ff2b4c8b7ddd1a3fe5d3f56b07d42d6a728c)
---
 .../java/org/springframework/ai/model/SimpleApiKey.java     | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/SimpleApiKey.java b/spring-ai-model/src/main/java/org/springframework/ai/model/SimpleApiKey.java
index 12eec7ce1dc..05441cd44b0 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/SimpleApiKey.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/SimpleApiKey.java
@@ -31,11 +31,11 @@ public record SimpleApiKey(String value) implements ApiKey {
 
 	/**
 	 * Create a new SimpleApiKey.
-	 * @param value the API key value, must not be null or empty
-	 * @throws IllegalArgumentException if value is null or empty
+	 * @param value the API key value, must not be null
+	 * @throws IllegalArgumentException if value is null
 	 */
 	public SimpleApiKey(String value) {
-		Assert.notNull(value, "API key value must not be null or empty");
+		Assert.notNull(value, "API key value must not be null");
 		this.value = value;
 	}
 

From cd0f69dccab925f07c4d0aa2447da63d609b52b8 Mon Sep 17 00:00:00 2001
From: Philipp Krenn <xeraa@users.noreply.github.com>
Date: Fri, 27 Jun 2025 08:59:28 -0700
Subject: [PATCH 130/165] Add Elasticsearch to the README (#3688)

Elasticsearch is by far the most widely deployed vector database (https://db-engines.com/en/ranking/vector+dbms) and we were also part of the 1.0 launch.

Fixes: #3688

Signed-off-by: Philipp Krenn <xeraa@users.noreply.github.com>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 4227900016b..f7b0c6e4059 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ You can find more details in the [Reference Documentation](https://docs.spring.i
   - [Moderation](https://docs.spring.io/spring-ai/reference/api/index.html#api/moderation)
 * Portable API support across AI providers for both synchronous and streaming options. Access to [model-specific features](https://docs.spring.io/spring-ai/reference/api/chatmodel.html#_chat_options) is also available.
 * [Structured Outputs](https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html) - Mapping of AI Model output to POJOs.
-* Support for all major [Vector Database providers](https://docs.spring.io/spring-ai/reference/api/vectordbs.html) such as *Apache Cassandra, Azure Vector Search, Chroma, Milvus, MongoDB Atlas, MariaDB, Neo4j, Oracle, PostgreSQL/PGVector, PineCone, Qdrant, Redis, and Weaviate*.
+* Support for all major [Vector Database providers](https://docs.spring.io/spring-ai/reference/api/vectordbs.html) such as *Apache Cassandra, Azure Vector Search, Chroma, Elasticsearch, Milvus, MongoDB Atlas, MariaDB, Neo4j, Oracle, PostgreSQL/PGVector, PineCone, Qdrant, Redis, and Weaviate*.
 * Portable API across Vector Store providers, including a novel SQL-like [metadata filter API](https://docs.spring.io/spring-ai/reference/api/vectordbs.html#metadata-filters).
 * [Tools/Function Calling](https://docs.spring.io/spring-ai/reference/api/tools.html) - permits the model to request the execution of client-side tools and functions, thereby accessing necessary real-time information as required.
 * [Observability](https://docs.spring.io/spring-ai/reference/observability/index.html) - Provides insights into AI-related operations.

From 28e12a4b2a71510f0d0f6b2e598700fb1bf5e2a9 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <151690+ilayaperumalg@users.noreply.github.com>
Date: Fri, 27 Jun 2025 18:02:28 +0100
Subject: [PATCH 131/165] Convert Qdrant vector store IT use OpenAI embedding
 model (#3671)

- Move Qdrant vector store test dependency from OpenAI model module
  - Convert existing Qdrant vector store ITs to use OpenAI embedding model for better unification along with other vector stores

Fixes #3671

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
(cherry picked from commit 5316073ce429209f55d46a814b4d9345ca2e5132)
---
 models/spring-ai-openai/pom.xml               | 19 -------------------
 vector-stores/spring-ai-qdrant-store/pom.xml  |  2 +-
 .../qdrant/QdrantVectorStoreIT.java           | 11 +++++------
 .../QdrantVectorStoreObservationIT.java       | 14 +++++++-------
 4 files changed, 13 insertions(+), 33 deletions(-)

diff --git a/models/spring-ai-openai/pom.xml b/models/spring-ai-openai/pom.xml
index 4279a0c59a3..d4418bee614 100644
--- a/models/spring-ai-openai/pom.xml
+++ b/models/spring-ai-openai/pom.xml
@@ -87,31 +87,12 @@
 			<scope>test</scope>
 		</dependency>
 
-		<dependency>
-			<groupId>org.springframework.ai</groupId>
-			<artifactId>spring-ai-qdrant-store</artifactId>
-			<version>${project.version}</version>
-			<exclusions>
-				<exclusion>
-					<groupId>org.springframework.ai</groupId>
-					<artifactId>spring-ai-openai</artifactId>
-				</exclusion>
-			</exclusions>
-			<scope>test</scope>
-		</dependency>
-
 		<dependency>
 			<groupId>io.micrometer</groupId>
 			<artifactId>micrometer-observation-test</artifactId>
 			<scope>test</scope>
 		</dependency>
 
-		<dependency>
-			<groupId>org.testcontainers</groupId>
-			<artifactId>qdrant</artifactId>
-			<scope>test</scope>
-		</dependency>
-
 		<dependency>
 			<groupId>org.testcontainers</groupId>
 			<artifactId>testcontainers</artifactId>
diff --git a/vector-stores/spring-ai-qdrant-store/pom.xml b/vector-stores/spring-ai-qdrant-store/pom.xml
index c24303a5a5d..a83247b103a 100644
--- a/vector-stores/spring-ai-qdrant-store/pom.xml
+++ b/vector-stores/spring-ai-qdrant-store/pom.xml
@@ -68,7 +68,7 @@
         <!-- TESTING -->
 		<dependency>
 			<groupId>org.springframework.ai</groupId>
-			<artifactId>spring-ai-mistral-ai</artifactId>
+			<artifactId>spring-ai-openai</artifactId>
 			<version>${project.parent.version}</version>
 			<scope>test</scope>
 		</dependency>
diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
index 2a943e895a1..9c8fa580ca7 100644
--- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
+++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
@@ -32,7 +32,6 @@
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
-import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
 import org.testcontainers.qdrant.QdrantContainer;
@@ -40,8 +39,8 @@
 import org.springframework.ai.document.Document;
 import org.springframework.ai.document.DocumentMetadata;
 import org.springframework.ai.embedding.EmbeddingModel;
-import org.springframework.ai.mistralai.MistralAiEmbeddingModel;
-import org.springframework.ai.mistralai.api.MistralAiApi;
+import org.springframework.ai.openai.OpenAiEmbeddingModel;
+import org.springframework.ai.openai.api.OpenAiApi;
 import org.springframework.ai.test.vectorstore.BaseVectorStoreTests;
 import org.springframework.ai.vectorstore.SearchRequest;
 import org.springframework.ai.vectorstore.VectorStore;
@@ -62,12 +61,12 @@
  * @since 0.8.1
  */
 @Testcontainers
-@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
 public class QdrantVectorStoreIT extends BaseVectorStoreTests {
 
 	private static final String COLLECTION_NAME = "test_collection";
 
-	private static final int EMBEDDING_DIMENSION = 1024;
+	private static final int EMBEDDING_DIMENSION = 1536;
 
 	@Container
 	static QdrantContainer qdrantContainer = new QdrantContainer(QdrantImage.DEFAULT_IMAGE);
@@ -355,7 +354,7 @@ public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient
 
 		@Bean
 		public EmbeddingModel embeddingModel() {
-			return new MistralAiEmbeddingModel(new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY")));
+			return new OpenAiEmbeddingModel(OpenAiApi.builder().apiKey(System.getenv("OPENAI_API_KEY")).build());
 		}
 
 	}
diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java
index 901e732f321..5feac454874 100644
--- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java
+++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java
@@ -39,10 +39,10 @@
 import org.springframework.ai.document.Document;
 import org.springframework.ai.embedding.EmbeddingModel;
 import org.springframework.ai.embedding.TokenCountBatchingStrategy;
-import org.springframework.ai.mistralai.MistralAiEmbeddingModel;
-import org.springframework.ai.mistralai.api.MistralAiApi;
 import org.springframework.ai.observation.conventions.SpringAiKind;
 import org.springframework.ai.observation.conventions.VectorStoreProvider;
+import org.springframework.ai.openai.OpenAiEmbeddingModel;
+import org.springframework.ai.openai.api.OpenAiApi;
 import org.springframework.ai.vectorstore.SearchRequest;
 import org.springframework.ai.vectorstore.VectorStore;
 import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
@@ -61,12 +61,12 @@
  * @author Thomas Vitale
  */
 @Testcontainers
-@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".+")
+@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+")
 public class QdrantVectorStoreObservationIT {
 
 	private static final String COLLECTION_NAME = "test_collection";
 
-	private static final int EMBEDDING_DIMENSION = 1024;
+	private static final int EMBEDDING_DIMENSION = 1536;
 
 	@Container
 	static QdrantContainer qdrantContainer = new QdrantContainer(QdrantImage.DEFAULT_IMAGE);
@@ -126,7 +126,7 @@ void observationVectorStoreAddAndQueryOperations() {
 				.hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(),
 						SpringAiKind.VECTOR_STORE.value())
 				.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString())
-				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1024")
+				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536")
 				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), COLLECTION_NAME)
 				.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.DB_NAMESPACE.asString())
 				.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString())
@@ -159,7 +159,7 @@ void observationVectorStoreAddAndQueryOperations() {
 
 				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(),
 						"What is Great Depression")
-				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1024")
+				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536")
 				.hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), COLLECTION_NAME)
 				.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.DB_NAMESPACE.asString())
 				.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString())
@@ -206,7 +206,7 @@ public VectorStore qdrantVectorStore(EmbeddingModel embeddingModel, QdrantClient
 
 		@Bean
 		public EmbeddingModel embeddingModel() {
-			return new MistralAiEmbeddingModel(new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY")));
+			return new OpenAiEmbeddingModel(OpenAiApi.builder().apiKey(System.getenv("OPENAI_API_KEY")).build());
 		}
 
 	}

From 2632d938750b3ca4cb7434c2217fa2f57c443487 Mon Sep 17 00:00:00 2001
From: Jemin Huh <hjm1980@gmail.com>
Date: Sat, 28 Jun 2025 02:15:57 +0900
Subject: [PATCH 132/165] doc: fix incorrect default embedding model reference
 in ollama-embeddings.adoc (#3686)

Fixes #3686

Signed-off-by: Jemin Huh <hjm1980@gmail.com>
(cherry picked from commit 3f4b5d4a142a0da912f91c8304c33d176243fb44)
---
 .../modules/ROOT/pages/api/embeddings/ollama-embeddings.adoc    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/ollama-embeddings.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/ollama-embeddings.adoc
index e327abb6d33..e9a3f16401e 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/ollama-embeddings.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/embeddings/ollama-embeddings.adoc
@@ -113,7 +113,7 @@ Here are the advanced request parameter for the Ollama embedding model:
 |====
 | Property | Description | Default
 | spring.ai.ollama.embedding.enabled (Removed and no longer valid)     | Enables the Ollama embedding model auto-configuration. | true
-| spring.ai.model.embedding      | Enables the Ollama embedding model auto-configuration. | ollama
+| spring.ai.model.embedding      | Enables the Ollama embedding model auto-configuration. | mxbai-embed-large
 | spring.ai.ollama.embedding.options.model  | The name of the https://github.com/ollama/ollama?tab=readme-ov-file#model-library[supported model] to use.
 You can use dedicated https://ollama.com/search?c=embedding[Embedding Model] types | mistral
 | spring.ai.ollama.embedding.options.keep_alive  | Controls how long the model will stay loaded into memory following the request | 5m

From c0c7f3a7e8289e569644bae4d9ce8350637b6035 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Tue, 17 Jun 2025 10:06:03 +0800
Subject: [PATCH 133/165] fix: Fixed GH-3557, Fixed the issue where the audio
 filename was lost when requesting the OpenAI `/transcriptions` and
 `/translations` interfaces.

Fixes #3557
Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit af07517b579f0c693dab4f3c09d2cfcda290ba60)
---
 .../openai/OpenAiAudioTranscriptionModel.java |  4 ++-
 .../ai/openai/api/OpenAiAudioApi.java         | 29 +++++++++++++++----
 .../ai/openai/audio/api/OpenAiAudioApiIT.java | 19 +++++++-----
 3 files changed, 39 insertions(+), 13 deletions(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java
index 4c7bb105648..2773e163c0e 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java
@@ -167,8 +167,10 @@ OpenAiAudioApi.TranscriptionRequest createRequest(AudioTranscriptionPrompt trans
 			}
 		}
 
+		Resource instructions = transcriptionPrompt.getInstructions();
 		return OpenAiAudioApi.TranscriptionRequest.builder()
-			.file(toBytes(transcriptionPrompt.getInstructions()))
+			.file(toBytes(instructions))
+			.fileName(instructions.getFilename())
 			.responseFormat(options.getResponseFormat())
 			.prompt(options.getPrompt())
 			.temperature(options.getTemperature())
diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
index 1177a98f1d3..44e08cc114d 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java
@@ -160,7 +160,7 @@ public <T> ResponseEntity<T> createTranscription(TranscriptionRequest requestBod
 
 			@Override
 			public String getFilename() {
-				return "audio.webm";
+				return requestBody.fileName();
 			}
 		});
 		multipartBody.add("model", requestBody.model());
@@ -206,7 +206,7 @@ public <T> ResponseEntity<T> createTranslation(TranslationRequest requestBody, C
 
 			@Override
 			public String getFilename() {
-				return "audio.webm";
+				return requestBody.fileName();
 			}
 		});
 		multipartBody.add("model", requestBody.model());
@@ -496,6 +496,7 @@ public SpeechRequest build() {
 	 * Transcription</a>
 	 *
 	 * @param file The audio file to transcribe. Must be a valid audio file type.
+	 * @param fileName The audio file name.
 	 * @param model ID of the model to use. Only whisper-1 is currently available.
 	 * @param language The language of the input audio. Supplying the input language in
 	 * ISO-639-1 format will improve accuracy and latency.
@@ -517,6 +518,7 @@ public SpeechRequest build() {
 	public record TranscriptionRequest(
 	// @formatter:off
 		@JsonProperty("file") byte[] file,
+		@JsonProperty("fileName") String fileName,
 		@JsonProperty("model") String model,
 		@JsonProperty("language") String language,
 		@JsonProperty("prompt") String prompt,
@@ -554,6 +556,8 @@ public static class Builder {
 
 			private byte[] file;
 
+			private String fileName;
+
 			private String model = WhisperModel.WHISPER_1.getValue();
 
 			private String language;
@@ -571,6 +575,11 @@ public Builder file(byte[] file) {
 				return this;
 			}
 
+			public Builder fileName(String fileName) {
+				this.fileName = fileName;
+				return this;
+			}
+
 			public Builder model(String model) {
 				this.model = model;
 				return this;
@@ -603,11 +612,12 @@ public Builder granularityType(GranularityType granularityType) {
 
 			public TranscriptionRequest build() {
 				Assert.notNull(this.file, "file must not be null");
+				Assert.notNull(this.fileName, "fileName must not be null");
 				Assert.hasText(this.model, "model must not be empty");
 				Assert.notNull(this.responseFormat, "response_format must not be null");
 
-				return new TranscriptionRequest(this.file, this.model, this.language, this.prompt, this.responseFormat,
-						this.temperature, this.granularityType);
+				return new TranscriptionRequest(this.file, this.fileName, this.model, this.language, this.prompt,
+						this.responseFormat, this.temperature, this.granularityType);
 			}
 
 		}
@@ -619,6 +629,7 @@ public TranscriptionRequest build() {
 	 *
 	 * @param file The audio file object (not file name) to translate, in one of these
 	 * formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm.
+	 * @param fileName The audio file name.
 	 * @param model ID of the model to use. Only whisper-1 is currently available.
 	 * @param prompt An optional text to guide the model's style or continue a previous
 	 * audio segment. The prompt should be in English.
@@ -633,6 +644,7 @@ public TranscriptionRequest build() {
 	public record TranslationRequest(
 	// @formatter:off
 		@JsonProperty("file") byte[] file,
+		@JsonProperty("fileName") String fileName,
 		@JsonProperty("model") String model,
 		@JsonProperty("prompt") String prompt,
 		@JsonProperty("response_format") TranscriptResponseFormat responseFormat,
@@ -647,6 +659,8 @@ public static class Builder {
 
 			private byte[] file;
 
+			private String fileName;
+
 			private String model = WhisperModel.WHISPER_1.getValue();
 
 			private String prompt;
@@ -660,6 +674,11 @@ public Builder file(byte[] file) {
 				return this;
 			}
 
+			public Builder fileName(String fileName) {
+				this.fileName = fileName;
+				return this;
+			}
+
 			public Builder model(String model) {
 				this.model = model;
 				return this;
@@ -685,7 +704,7 @@ public TranslationRequest build() {
 				Assert.hasText(this.model, "model must not be empty");
 				Assert.notNull(this.responseFormat, "response_format must not be null");
 
-				return new TranslationRequest(this.file, this.model, this.prompt, this.responseFormat,
+				return new TranslationRequest(this.file, this.fileName, this.model, this.prompt, this.responseFormat,
 						this.temperature);
 			}
 
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiIT.java
index 6c933dec283..6533d15de56 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiIT.java
@@ -63,24 +63,29 @@ void speechTranscriptionAndTranslation() throws IOException {
 		FileCopyUtils.copy(speech, new File("target/speech.mp3"));
 
 		StructuredResponse translation = this.audioApi
-			.createTranslation(
-					TranslationRequest.builder().model(WhisperModel.WHISPER_1.getValue()).file(speech).build(),
-					StructuredResponse.class)
+			.createTranslation(TranslationRequest.builder()
+				.model(WhisperModel.WHISPER_1.getValue())
+				.file(speech)
+				.fileName("speech.mp3")
+				.build(), StructuredResponse.class)
 			.getBody();
 
 		assertThat(translation.text().replaceAll(",", "")).isEqualTo("Hello my name is Chris and I love Spring AI.");
 
 		StructuredResponse transcriptionEnglish = this.audioApi
-			.createTranscription(
-					TranscriptionRequest.builder().model(WhisperModel.WHISPER_1.getValue()).file(speech).build(),
-					StructuredResponse.class)
+			.createTranscription(TranscriptionRequest.builder()
+				.model(WhisperModel.WHISPER_1.getValue())
+				.file(speech)
+				.fileName("speech.mp3")
+				.build(), StructuredResponse.class)
 			.getBody();
 
 		assertThat(transcriptionEnglish.text().replaceAll(",", ""))
 			.isEqualTo("Hello my name is Chris and I love Spring AI.");
 
 		StructuredResponse transcriptionDutch = this.audioApi
-			.createTranscription(TranscriptionRequest.builder().file(speech).language("nl").build(),
+			.createTranscription(
+					TranscriptionRequest.builder().file(speech).fileName("speech.mp3").language("nl").build(),
 					StructuredResponse.class)
 			.getBody();
 

From 83e19c5476ab99a5443982afc121247fb08b63ce Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <151690+ilayaperumalg@users.noreply.github.com>
Date: Fri, 27 Jun 2025 18:40:52 +0100
Subject: [PATCH 134/165] Validate documents for text/media check when adding
 to vector store (#3687)

- Since the non-text documents aren't supported, we need a validation to throw exception if non text documents are encountered
 - Add tests
    - Add test to QdrantVectorStoreIT with the usecase provided by @devMtn30
    - Add simple vector store test for the validation
Fixes #3609

Auto-cherry-pick to 1.0.x

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 .../AbstractObservationVectorStore.java       | 13 +++++++++++-
 .../vectorstore/SimpleVectorStoreTests.java   | 16 +++++++++++++++
 .../qdrant/QdrantVectorStoreIT.java           | 20 +++++++++++++++++++
 3 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java
index 52c8106a48e..39e46d7d6f7 100644
--- a/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java
+++ b/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/observation/AbstractObservationVectorStore.java
@@ -74,7 +74,7 @@ public AbstractObservationVectorStore(AbstractVectorStoreBuilder<?> builder) {
 	 */
 	@Override
 	public void add(List<Document> documents) {
-
+		validateNonTextDocuments(documents);
 		VectorStoreObservationContext observationContext = this
 			.createObservationContextBuilder(VectorStoreObservationContext.Operation.ADD.value())
 			.build();
@@ -85,6 +85,17 @@ public void add(List<Document> documents) {
 			.observe(() -> this.doAdd(documents));
 	}
 
+	private void validateNonTextDocuments(List<Document> documents) {
+		if (documents == null)
+			return;
+		for (Document document : documents) {
+			if (document != null && !document.isText()) {
+				throw new IllegalArgumentException(
+						"Only text documents are supported for now. One of the documents contains non-text content.");
+			}
+		}
+	}
+
 	@Override
 	public void delete(List<String> deleteDocIds) {
 
diff --git a/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java b/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java
index 85fe0b384c6..3600d4e13c3 100644
--- a/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java
+++ b/spring-ai-vector-store/src/test/java/org/springframework/ai/vectorstore/SimpleVectorStoreTests.java
@@ -32,13 +32,18 @@
 import org.junit.jupiter.api.io.CleanupMode;
 import org.junit.jupiter.api.io.TempDir;
 
+import org.springframework.ai.content.Media;
 import org.springframework.ai.document.Document;
 import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.core.io.ByteArrayResource;
 import org.springframework.core.io.Resource;
+import org.springframework.util.MimeType;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -259,4 +264,15 @@ void shouldHandleNullVectors() {
 			.hasMessage("Vectors must not be null");
 	}
 
+	@Test
+	void shouldFailNonTextDocuments() {
+		Media media = new Media(MimeType.valueOf("image/png"), new ByteArrayResource(new byte[] { 0x00 }));
+
+		Document imgDoc = Document.builder().media(media).metadata(Map.of("fileName", "pixel.png")).build();
+
+		Exception exception = assertThrows(IllegalArgumentException.class, () -> this.vectorStore.add(List.of(imgDoc)));
+		assertEquals("Only text documents are supported for now. One of the documents contains non-text content.",
+				exception.getMessage());
+	}
+
 }
diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
index 9c8fa580ca7..6b540e2b483 100644
--- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
+++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java
@@ -36,6 +36,7 @@
 import org.testcontainers.junit.jupiter.Testcontainers;
 import org.testcontainers.qdrant.QdrantContainer;
 
+import org.springframework.ai.content.Media;
 import org.springframework.ai.document.Document;
 import org.springframework.ai.document.DocumentMetadata;
 import org.springframework.ai.embedding.EmbeddingModel;
@@ -48,8 +49,12 @@
 import org.springframework.boot.SpringBootConfiguration;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
 import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.util.MimeType;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /**
  * @author Anush Shetty
@@ -58,6 +63,7 @@
  * @author Thomas Vitale
  * @author Soby Chacko
  * @author Jonghoon Park
+ * @author Kim San
  * @since 0.8.1
  */
 @Testcontainers
@@ -333,6 +339,20 @@ void shouldConvertLongToString() {
 		});
 	}
 
+	@Test
+	void testNonTextDocuments() {
+		this.contextRunner.run(context -> {
+			QdrantVectorStore vectorStore = context.getBean(QdrantVectorStore.class);
+			Media media = new Media(MimeType.valueOf("image/png"), new ByteArrayResource(new byte[] { 0x00 }));
+
+			Document imgDoc = Document.builder().media(media).metadata(Map.of("fileName", "pixel.png")).build();
+
+			Exception exception = assertThrows(IllegalArgumentException.class, () -> vectorStore.add(List.of(imgDoc)));
+			assertEquals("Only text documents are supported for now. One of the documents contains non-text content.",
+					exception.getMessage());
+		});
+	}
+
 	@SpringBootConfiguration
 	public static class TestApplication {
 

From d0a25438e4563b31f331c6e78bada52017a1292d Mon Sep 17 00:00:00 2001
From: Jemin Huh <hjm1980@gmail.com>
Date: Wed, 2 Jul 2025 00:44:42 +0900
Subject: [PATCH 135/165] doc: Remove duplicated dependency instructions in
 opensearch.adoc (#3712)

Fixes #3712

Signed-off-by: Jemin Huh <hjm1980@gmail.com>
(cherry picked from commit 7d6ebb4deff01acc51b580e65ad7d68bfadf79d6)
---
 .../ROOT/pages/api/vectordbs/opensearch.adoc  | 22 ++-----------------
 1 file changed, 2 insertions(+), 20 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc
index ea597757473..5588945dcd9 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/opensearch.adoc
@@ -41,26 +41,8 @@ dependencies {
 }
 ----
 
-TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
-
-For Amazon OpenSearch Service, use these dependencies instead:
-
-[source,xml]
-----
-<dependency>
-    <groupId>org.springframework.ai</groupId>
-    <artifactId>spring-ai-starter-vector-store-opensearch</artifactId>
-</dependency>
-----
-
-or for Gradle:
-
-[source,groovy]
-----
-dependencies {
-    implementation 'org.springframework.ai:spring-ai-starter-vector-store-opensearch'
-}
-----
+TIP: For both self-hosted and Amazon OpenSearch Service, use the same dependency.
+Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file.
 
 Please have a look at the list of xref:#_configuration_properties[configuration parameters] for the vector store to learn about the default values and configuration options.
 

From 224538084aeea860d8f2a1855fd30bac0a56f0e2 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Wed, 2 Jul 2025 01:41:38 +0900
Subject: [PATCH 136/165] docs: correct property name from is-auto-id to
 auto-id in milvus.adoc (#3704)

Fixes #3702

Signed-off-by: jonghoon park <dev@jonghoonpark.com>
(cherry picked from commit 15e1eaac82f3079ce293bbb840b97207d3770369)
---
 .../main/antora/modules/ROOT/pages/api/vectordbs/milvus.adoc    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/milvus.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/milvus.adoc
index 59ebd0eea97..2f96ec7741b 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/milvus.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/vectordbs/milvus.adoc
@@ -236,7 +236,7 @@ You can use the following properties in your Spring Boot configuration to custom
 |spring.ai.vectorstore.milvus.metric-type | The metric type to be used for the Milvus collection.  | COSINE
 |spring.ai.vectorstore.milvus.index-parameters | The index parameters to be used for the Milvus collection.  | {"nlist":1024}
 |spring.ai.vectorstore.milvus.id-field-name | The ID field name for the collection | doc_id
-|spring.ai.vectorstore.milvus.is-auto-id | Boolean flag to indicate if the auto-id is used for the ID field | false
+|spring.ai.vectorstore.milvus.auto-id | Boolean flag to indicate if the auto-id is used for the ID field | false
 |spring.ai.vectorstore.milvus.content-field-name | The content field name for the collection | content
 |spring.ai.vectorstore.milvus.metadata-field-name | The metadata field name for the collection | metadata
 |spring.ai.vectorstore.milvus.embedding-field-name | The embedding field name for the collection | embedding

From 6fed42729d5fdfbd0a9e037e363247dd8bdd115b Mon Sep 17 00:00:00 2001
From: Sun Yuhan <20242118+sunyuhan1998@users.noreply.github.com>
Date: Wed, 2 Jul 2025 18:20:38 +0800
Subject: [PATCH 137/165] fix: GH-3713 Fixed outdated code examples in the
 documentation. (#3717)

Fixes #3713

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Co-authored-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit f3a132e0e8a94322d194216fc28fcfe575833b4d)
---
 .../api/structured-output-converter.adoc      | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc
index 153c5245a1b..4231e208a6a 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc
@@ -70,10 +70,11 @@ The format instructions are most often appended to the end of the user input usi
         {format}
         """; // user input with a "format" placeholder.
     Prompt prompt = new Prompt(
-       new PromptTemplate(
-			   this.userInputTemplate,
-          Map.of(..., "format", outputConverter.getFormat()) // replace the "format" placeholder with the converter's format.
-       ).createMessage());
+            PromptTemplate.builder()
+						.template(this.userInputTemplate)
+						.variables(Map.of(..., "format", this.outputConverter.getFormat())) // replace the "format" placeholder with the converter's format.
+						.build().createMessage()
+    );
 ----
 
 The Converter<String, T> is responsible to transform output text from the model into instances of the specified type `T`.
@@ -134,7 +135,7 @@ String template = """
         """;
 
 Generation generation = chatModel.call(
-    new PromptTemplate(this.template, Map.of("actor", this.actor, "format", this.format)).create()).getResult();
+    PromptTemplate.builder().template(this.template).variables(Map.of("actor", this.actor, "format", this.format)).build().create()).getResult();
 
 ActorsFilms actorsFilms = this.beanOutputConverter.convert(this.generation.getOutput().getText());
 ----
@@ -180,7 +181,7 @@ String template = """
         {format}
         """;
 
-Prompt prompt = new PromptTemplate(this.template, Map.of("format", this.format)).create();
+Prompt prompt = PromptTemplate.builder().template(this.template).variables(Map.of("format", this.format)).build().create();
 
 Generation generation = chatModel.call(this.prompt).getResult();
 
@@ -212,8 +213,8 @@ String template = """
         {format}
         """;
 
-Prompt prompt = new PromptTemplate(this.template,
-        Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", this.format)).create();
+Prompt prompt = PromptTemplate.builder().template(this.template)
+.variables(Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", this.format)).build().create();
 
 Generation generation = chatModel.call(this.prompt).getResult();
 
@@ -245,8 +246,7 @@ String template = """
         {format}
         """;
 
-Prompt prompt = new PromptTemplate(this.template,
-        Map.of("subject", "ice cream flavors", "format", this.format)).create();
+Prompt prompt = PromptTemplate.builder().template(this.template).variables(Map.of("subject", "ice cream flavors", "format", this.format)).build().create();
 
 Generation generation = this.chatModel.call(this.prompt).getResult();
 

From 08407bbfbcfd317a1d31786ece2d3f28e1bb10b2 Mon Sep 17 00:00:00 2001
From: Giorgos Gaganis <gaganis@yahoo.com>
Date: Wed, 2 Jul 2025 19:14:55 +0200
Subject: [PATCH 138/165] Fix typo token configuration documentation (#3677)

Fixes #3677

Signed-off-by: Giorgos Gaganis <gaganis@yahoo.com>
(cherry picked from commit 4ec73e220d8aaed9ae714c2593e3c82561692dc6)
---
 .../main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
index affb5d7dcfa..6523861c90b 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
@@ -40,7 +40,7 @@ spring:
 export ANTHROPIC_API_KEY=<your-anthropic-api-key>
 ----
 
-You can also set this configuration programmatically in your application code:
+You can also get this configuration programmatically in your application code:
 
 [source,java]
 ----

From fc0958f03f597a76617f4084c4089d08ef8a7a89 Mon Sep 17 00:00:00 2001
From: Nirsa <60082298+KoreaNirsa@users.noreply.github.com>
Date: Thu, 3 Jul 2025 02:34:20 +0900
Subject: [PATCH 139/165] Refactor: Add null check, optimize string joining,
 and add JavaDocs (#3663)

* Refactor: Add null check, optimize string joining, and add JavaDocs

Fixes #3663

Signed-off-by: nirsa <islandtim@naver.com>
(cherry picked from commit 8d45caf7eb47a4c1ebe8fef3716d78e7892b5a90)
---
 .../org/springframework/ai/openai/api/OpenAiApi.java | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
index 383065fc209..9f2e8a9ebdb 100644
--- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
+++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java
@@ -21,6 +21,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -144,11 +145,20 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> he
 			.build(); // @formatter:on
 	}
 
+	/**
+	 * Returns a string containing all text values from the given media content list. Only
+	 * elements of type "text" are processed and concatenated in order.
+	 * @param content The list of {@link ChatCompletionMessage.MediaContent}
+	 * @return a string containing all text values from "text" type elements
+	 * @throws IllegalArgumentException if content is null
+	 */
 	public static String getTextContent(List<ChatCompletionMessage.MediaContent> content) {
+		Assert.notNull(content, "content cannot be null");
+
 		return content.stream()
 			.filter(c -> "text".equals(c.type()))
 			.map(ChatCompletionMessage.MediaContent::text)
-			.reduce("", (a, b) -> a + b);
+			.collect(Collectors.joining());
 	}
 
 	/**

From f26ae22f62ed40ea8491bf6590da6f869949adb0 Mon Sep 17 00:00:00 2001
From: jonghoonpark <dev@jonghoonpark.com>
Date: Thu, 3 Jul 2025 02:46:45 +0900
Subject: [PATCH 140/165] improve schema initialization logic and update
 deprecated code for Milvus vectorstore (#3705)

Fixes #3705

Signed-off-by: jonghoon park <dev@jonghoonpark.com>
(cherry picked from commit 5c9891cb2a4f727c056cbb10ad283aef629803e9)
---
 .../vectorstore/milvus/MilvusVectorStore.java | 45 ++++++++++++-------
 .../milvus/MilvusVectorStoreIT.java           | 37 +++++++++++++++
 2 files changed, 65 insertions(+), 17 deletions(-)

diff --git a/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStore.java b/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStore.java
index 0b8a938f430..8ef20793167 100644
--- a/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStore.java
+++ b/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStore.java
@@ -38,6 +38,7 @@
 import io.milvus.param.R;
 import io.milvus.param.R.Status;
 import io.milvus.param.RpcStatus;
+import io.milvus.param.collection.CollectionSchemaParam;
 import io.milvus.param.collection.CreateCollectionParam;
 import io.milvus.param.collection.DropCollectionParam;
 import io.milvus.param.collection.FieldType;
@@ -443,6 +444,8 @@ void createCollection() {
 		if (!isDatabaseCollectionExists()) {
 			createCollection(this.databaseName, this.collectionName, this.idFieldName, this.isAutoId,
 					this.contentFieldName, this.metadataFieldName, this.embeddingFieldName);
+			createIndex(this.databaseName, this.collectionName, this.embeddingFieldName, this.indexType,
+					this.metricType, this.indexParameters);
 		}
 
 		R<DescribeIndexResponse> indexDescriptionResponse = this.milvusClient
@@ -452,19 +455,8 @@ void createCollection() {
 				.build());
 
 		if (indexDescriptionResponse.getData() == null) {
-			R<RpcStatus> indexStatus = this.milvusClient.createIndex(CreateIndexParam.newBuilder()
-				.withDatabaseName(this.databaseName)
-				.withCollectionName(this.collectionName)
-				.withFieldName(this.embeddingFieldName)
-				.withIndexType(this.indexType)
-				.withMetricType(this.metricType)
-				.withExtraParam(this.indexParameters)
-				.withSyncMode(Boolean.FALSE)
-				.build());
-
-			if (indexStatus.getException() != null) {
-				throw new RuntimeException("Failed to create Index", indexStatus.getException());
-			}
+			createIndex(this.databaseName, this.collectionName, this.embeddingFieldName, this.indexType,
+					this.metricType, this.indexParameters);
 		}
 
 		R<RpcStatus> loadCollectionStatus = this.milvusClient.loadCollection(LoadCollectionParam.newBuilder()
@@ -507,10 +499,12 @@ void createCollection(String databaseName, String collectionName, String idField
 			.withDescription("Spring AI Vector Store")
 			.withConsistencyLevel(ConsistencyLevelEnum.STRONG)
 			.withShardsNum(2)
-			.addFieldType(docIdFieldType)
-			.addFieldType(contentFieldType)
-			.addFieldType(metadataFieldType)
-			.addFieldType(embeddingFieldType)
+			.withSchema(CollectionSchemaParam.newBuilder()
+				.addFieldType(docIdFieldType)
+				.addFieldType(contentFieldType)
+				.addFieldType(metadataFieldType)
+				.addFieldType(embeddingFieldType)
+				.build())
 			.build();
 
 		R<RpcStatus> collectionStatus = this.milvusClient.createCollection(createCollectionReq);
@@ -520,6 +514,23 @@ void createCollection(String databaseName, String collectionName, String idField
 
 	}
 
+	void createIndex(String databaseName, String collectionName, String embeddingFieldName, IndexType indexType,
+			MetricType metricType, String indexParameters) {
+		R<RpcStatus> indexStatus = this.milvusClient.createIndex(CreateIndexParam.newBuilder()
+			.withDatabaseName(databaseName)
+			.withCollectionName(collectionName)
+			.withFieldName(embeddingFieldName)
+			.withIndexType(indexType)
+			.withMetricType(metricType)
+			.withExtraParam(indexParameters)
+			.withSyncMode(Boolean.FALSE)
+			.build());
+
+		if (indexStatus.getException() != null) {
+			throw new RuntimeException("Failed to create Index", indexStatus.getException());
+		}
+	}
+
 	int embeddingDimensions() {
 		if (this.embeddingDimension != INVALID_EMBEDDING_DIMENSION) {
 			return this.embeddingDimension;
diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java
index 50c5a64c4dc..360b35c7855 100644
--- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java
+++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java
@@ -18,6 +18,7 @@
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -26,6 +27,10 @@
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import io.milvus.client.AbstractMilvusGrpcClient;
 import io.milvus.client.MilvusServiceClient;
 import io.milvus.param.ConnectParam;
 import io.milvus.param.IndexType;
@@ -34,6 +39,7 @@
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
+import org.slf4j.LoggerFactory;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
 import org.testcontainers.milvus.MilvusContainer;
@@ -323,6 +329,37 @@ public void deleteWithComplexFilterExpression() {
 		});
 	}
 
+	@Test
+	void initializeSchema() {
+		this.contextRunner.withPropertyValues("test.spring.ai.vectorstore.milvus.metricType=COSINE").run(context -> {
+			VectorStore vectorStore = context.getBean(VectorStore.class);
+
+			Logger logger = (Logger) LoggerFactory.getLogger(AbstractMilvusGrpcClient.class);
+			LogAppender logAppender = new LogAppender();
+			logger.addAppender(logAppender);
+			logAppender.start();
+
+			resetCollection(vectorStore);
+
+			assertThat(logAppender.capturedLogs).isEmpty();
+		});
+	}
+
+	static class LogAppender extends AppenderBase<ILoggingEvent> {
+
+		private final List<String> capturedLogs = new ArrayList<>();
+
+		@Override
+		protected void append(ILoggingEvent eventObject) {
+			capturedLogs.add(eventObject.getFormattedMessage());
+		}
+
+		public List<String> getCapturedLogs() {
+			return capturedLogs;
+		}
+
+	}
+
 	@Test
 	void getNativeClientTest() {
 		this.contextRunner.withPropertyValues("test.spring.ai.vectorstore.milvus.metricType=COSINE").run(context -> {

From 3a18724318896ff042220214e593fa0a804e4b9c Mon Sep 17 00:00:00 2001
From: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
Date: Thu, 3 Jul 2025 00:50:31 +0700
Subject: [PATCH 141/165] Fix annotation package (#3716)

Fixes #3716

* Correct NonNull annotation package
* Correct Nullable annotation package
* Replace jetbrains NotNull
* Update copyright headers
* Add package-info

Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
(cherry picked from commit 2122c65d3128d08f3afdbb74d81a7e8a00384101)
---
 .../OpenAIAutoConfigurationUtil.java          |  5 ++---
 .../openai/autoconfigure/package-info.java    | 22 +++++++++++++++++++
 .../gemini/VertexAiGeminiChatModelIT.java     |  6 ++---
 .../ai/model/AbstractResponseMetadata.java    |  6 ++---
 .../ai/model/MutableResponseMetadata.java     |  6 ++---
 .../ai/model/ResponseMetadata.java            |  6 ++---
 .../ai/model/package-info.java                |  7 +++++-
 .../PgVectorStoreWithChatMemoryAdvisorIT.java | 14 ++++++------
 8 files changed, 49 insertions(+), 23 deletions(-)
 create mode 100644 auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/package-info.java

diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java
index 60fbb75cbcc..7eff1898fa4 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java
@@ -20,8 +20,7 @@
 import java.util.List;
 import java.util.Map;
 
-import org.jetbrains.annotations.NotNull;
-
+import org.springframework.lang.NonNull;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.MultiValueMap;
@@ -33,7 +32,7 @@ private OpenAIAutoConfigurationUtil() {
 		// Avoids instantiation
 	}
 
-	public static @NotNull ResolvedConnectionProperties resolveConnectionProperties(
+	public static @NonNull ResolvedConnectionProperties resolveConnectionProperties(
 			OpenAiParentProperties commonProperties, OpenAiParentProperties modelProperties, String modelType) {
 
 		String baseUrl = StringUtils.hasText(modelProperties.getBaseUrl()) ? modelProperties.getBaseUrl()
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/package-info.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/package-info.java
new file mode 100644
index 00000000000..23908ee5389
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2025-2025 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.
+ */
+
+@NonNullApi
+@NonNullFields
+package org.springframework.ai.model.openai.autoconfigure;
+
+import org.springframework.lang.NonNullApi;
+import org.springframework.lang.NonNullFields;
\ No newline at end of file
diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java
index 2c37f0608a6..9bacdde2066 100644
--- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java
+++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -26,7 +26,6 @@
 import com.google.cloud.vertexai.Transport;
 import com.google.cloud.vertexai.VertexAI;
 import io.micrometer.observation.ObservationRegistry;
-import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
@@ -56,6 +55,7 @@
 import org.springframework.core.convert.support.DefaultConversionService;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.core.io.Resource;
+import org.springframework.lang.NonNull;
 import org.springframework.util.MimeType;
 import org.springframework.util.MimeTypeUtils;
 
@@ -130,7 +130,7 @@ void testSafetySettings() {
 		assertThat(response.getResult().getMetadata().getFinishReason()).isEqualTo("SAFETY");
 	}
 
-	@NotNull
+	@NonNull
 	private Prompt createPrompt(VertexAiGeminiChatOptions chatOptions) {
 		String request = "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did.";
 		String name = "Bob";
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/AbstractResponseMetadata.java b/spring-ai-model/src/main/java/org/springframework/ai/model/AbstractResponseMetadata.java
index 627b74d29b4..33ece56b416 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/AbstractResponseMetadata.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/AbstractResponseMetadata.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -21,8 +21,8 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import io.micrometer.common.lang.NonNull;
-import io.micrometer.common.lang.Nullable;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
 
 public class AbstractResponseMetadata {
 
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/MutableResponseMetadata.java b/spring-ai-model/src/main/java/org/springframework/ai/model/MutableResponseMetadata.java
index 106a90e6867..e84d6c746e1 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/MutableResponseMetadata.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/MutableResponseMetadata.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -22,8 +22,8 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
-import io.micrometer.common.lang.NonNull;
-import io.micrometer.common.lang.Nullable;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
 
 public class MutableResponseMetadata implements ResponseMetadata {
 
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/ResponseMetadata.java b/spring-ai-model/src/main/java/org/springframework/ai/model/ResponseMetadata.java
index 7b63e91a481..cda2723d61e 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/ResponseMetadata.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/ResponseMetadata.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -20,8 +20,8 @@
 import java.util.Set;
 import java.util.function.Supplier;
 
-import io.micrometer.common.lang.NonNull;
-import io.micrometer.common.lang.Nullable;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
 
 /**
  * Interface representing metadata associated with an AI model's response.
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/package-info.java b/spring-ai-model/src/main/java/org/springframework/ai/model/package-info.java
index 207af410e2f..f1edcb4e8f6 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/package-info.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -25,4 +25,9 @@
  *
  */
 
+@NonNullApi
+@NonNullFields
 package org.springframework.ai.model;
+
+import org.springframework.lang.NonNullApi;
+import org.springframework.lang.NonNullFields;
\ No newline at end of file
diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java
index 9d180365805..089d722e3a7 100644
--- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java
+++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreWithChatMemoryAdvisorIT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023-2024 the original author or authors.
+ * Copyright 2023-2025 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.
@@ -20,7 +20,6 @@
 import java.util.Map;
 import java.util.UUID;
 
-import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
@@ -45,6 +44,7 @@
 import org.springframework.ai.embedding.EmbeddingModel;
 import org.springframework.ai.vectorstore.SearchRequest;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.lang.NonNull;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -68,7 +68,7 @@ class PgVectorStoreWithChatMemoryAdvisorIT {
 
 	float[] embed = { 0.003961659F, -0.0073295482F, 0.02663665F };
 
-	private static @NotNull ChatModel chatModelAlwaysReturnsTheSameReply() {
+	private static @NonNull ChatModel chatModelAlwaysReturnsTheSameReply() {
 		ChatModel chatModel = mock(ChatModel.class);
 		ArgumentCaptor<Prompt> argumentCaptor = ArgumentCaptor.forClass(Prompt.class);
 		ChatResponse chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("""
@@ -95,7 +95,7 @@ private static PgVectorStore createPgVectorStoreUsingTestcontainer(EmbeddingMode
 			.build();
 	}
 
-	private static @NotNull JdbcTemplate createJdbcTemplateWithConnectionToTestcontainer() {
+	private static @NonNull JdbcTemplate createJdbcTemplateWithConnectionToTestcontainer() {
 		PGSimpleDataSource ds = new PGSimpleDataSource();
 		ds.setUrl("jdbc:postgresql://localhost:" + postgresContainer.getMappedPort(5432) + "/postgres");
 		ds.setUser(postgresContainer.getUsername());
@@ -123,7 +123,7 @@ private static void verifyRequestHasBeenAdvisedWithMessagesFromVectorStore(ChatM
 	 * Create a mock ChatModel that supports streaming responses for testing.
 	 * @return A mock ChatModel that returns a predefined streaming response
 	 */
-	private static @NotNull ChatModel chatModelWithStreamingSupport() {
+	private static @NonNull ChatModel chatModelWithStreamingSupport() {
 		ChatModel chatModel = mock(ChatModel.class);
 
 		// Mock the regular call method
@@ -158,7 +158,7 @@ private static void verifyRequestHasBeenAdvisedWithMessagesFromVectorStore(ChatM
 	 * VectorStoreChatMemoryAdvisor.
 	 * @return A mock ChatModel that returns a problematic streaming response
 	 */
-	private static @NotNull ChatModel chatModelWithProblematicStreamingBehavior() {
+	private static @NonNull ChatModel chatModelWithProblematicStreamingBehavior() {
 		ChatModel chatModel = mock(ChatModel.class);
 
 		// Mock the regular call method
@@ -390,7 +390,7 @@ private Throwable getRootCause(Throwable throwable) {
 	}
 
 	@SuppressWarnings("unchecked")
-	private @NotNull EmbeddingModel embeddingNModelShouldAlwaysReturnFakedEmbed() {
+	private @NonNull EmbeddingModel embeddingNModelShouldAlwaysReturnFakedEmbed() {
 		EmbeddingModel embeddingModel = mock(EmbeddingModel.class);
 
 		Mockito.doAnswer(invocationOnMock -> List.of(this.embed, this.embed))

From d3aba22a23224360ef68aadaf4ee21e86ac30b29 Mon Sep 17 00:00:00 2001
From: Yanming Zhou <zhouyanming@gmail.com>
Date: Thu, 3 Jul 2025 02:45:45 +0800
Subject: [PATCH 142/165] Use diamond operator where possible (#3458)

Fixes #3458

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
(cherry picked from commit 911c45962b46f5a105c78eb55e645d5e1f9a2db8)
---
 .../properties/McpStdioClientProperties.java       |  7 +++----
 .../MongoDBAtlasVectorStoreAutoConfiguration.java  |  4 ++--
 .../pdf/layout/ForkPDFLayoutTextStripper.java      |  6 +++---
 .../pdf/layout/PDFLayoutTextStripperByArea.java    | 12 ++++++------
 .../ai/anthropic/EventParsingTests.java            |  2 +-
 .../ai/anthropic/client/AnthropicChatClientIT.java |  4 ++--
 .../converse/BedrockConverseChatClientIT.java      |  6 +++---
 .../ai/huggingface/HuggingfaceChatModel.java       |  2 +-
 .../ai/mistralai/MistralAiChatClientIT.java        |  4 ++--
 ...hatModelTypeReferenceBeanOutputConverterIT.java |  4 ++--
 .../ai/openai/chat/OpenAiPaymentTransactionIT.java |  2 +-
 .../ai/openai/chat/client/OpenAiChatClientIT.java  |  6 +++---
 .../ai/chat/client/DefaultChatClient.java          |  4 ++--
 .../chat/client/ChatClientResponseEntityTests.java |  2 +-
 .../ai/document/DefaultContentFormatter.java       |  6 +++---
 .../org/springframework/ai/reader/JsonReader.java  |  2 +-
 .../ai/template/NoOpTemplateRendererTests.java     |  2 +-
 .../ai/chat/model/MessageAggregator.java           |  6 +++---
 .../springframework/ai/model/EmbeddingUtils.java   |  2 +-
 .../ai/model/ModelOptionsUtils.java                |  4 ++--
 .../ai/template/st/StTemplateRendererTests.java    |  2 +-
 .../vectorstore/cosmosdb/CosmosDBVectorStore.java  |  2 +-
 .../ai/vectorstore/azure/AzureVectorStore.java     |  2 +-
 .../mongodb/atlas/MongoDBAtlasVectorStoreIT.java   |  4 ++--
 .../atlas/MongoDbVectorStoreObservationIT.java     |  4 ++--
 .../ai/vectorstore/pgvector/PgVectorStore.java     |  2 +-
 .../vectorstore/pinecone/PineconeVectorStore.java  |  2 +-
 .../qdrant/QdrantFilterExpressionConverter.java    | 14 +++++++-------
 .../ai/vectorstore/qdrant/QdrantValueFactory.java  |  4 ++--
 29 files changed, 61 insertions(+), 62 deletions(-)

diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/properties/McpStdioClientProperties.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/properties/McpStdioClientProperties.java
index f4d013b7a8f..2b18ce3cda0 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/properties/McpStdioClientProperties.java
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/src/main/java/org/springframework/ai/mcp/client/autoconfigure/properties/McpStdioClientProperties.java
@@ -75,10 +75,9 @@ public Map<String, Parameters> getConnections() {
 
 	private Map<String, ServerParameters> resourceToServerParameters() {
 		try {
-			Map<String, Map<String, Parameters>> stdioConnection = new ObjectMapper().readValue(
-					this.serversConfiguration.getInputStream(),
-					new TypeReference<Map<String, Map<String, Parameters>>>() {
-					});
+			Map<String, Map<String, Parameters>> stdioConnection = new ObjectMapper()
+				.readValue(this.serversConfiguration.getInputStream(), new TypeReference<>() {
+				});
 
 			Map<String, Parameters> mcpServerJsonConfig = stdioConnection.entrySet().iterator().next().getValue();
 
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/main/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/main/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfiguration.java
index 001e84e927e..2004e57918a 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/main/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfiguration.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/main/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfiguration.java
@@ -91,7 +91,7 @@ MongoDBAtlasVectorStore vectorStore(MongoTemplate mongoTemplate, EmbeddingModel
 
 	@Bean
 	public Converter<MimeType, String> mimeTypeToStringConverter() {
-		return new Converter<MimeType, String>() {
+		return new Converter<>() {
 
 			@Override
 			public String convert(MimeType source) {
@@ -102,7 +102,7 @@ public String convert(MimeType source) {
 
 	@Bean
 	public Converter<String, MimeType> stringToMimeTypeConverter() {
-		return new Converter<String, MimeType>() {
+		return new Converter<>() {
 
 			@Override
 			public MimeType convert(String source) {
diff --git a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java
index dcc10f09c84..c634b3e7a43 100644
--- a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java
+++ b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/ForkPDFLayoutTextStripper.java
@@ -58,7 +58,7 @@ public class ForkPDFLayoutTextStripper extends PDFTextStripper {
 	public ForkPDFLayoutTextStripper() throws IOException {
 		super();
 		this.previousTextPosition = null;
-		this.textLineList = new ArrayList<TextLine>();
+		this.textLineList = new ArrayList<>();
 	}
 
 	/**
@@ -71,7 +71,7 @@ public void processPage(PDPage page) throws IOException {
 			this.setCurrentPageWidth(pageRectangle.getWidth() * 1.4);
 			super.processPage(page);
 			this.previousTextPosition = null;
-			this.textLineList = new ArrayList<TextLine>();
+			this.textLineList = new ArrayList<>();
 		}
 	}
 
@@ -128,7 +128,7 @@ private void writeLine(final List<TextPosition> textPositionList) {
 	}
 
 	private void iterateThroughTextList(Iterator<TextPosition> textIterator) {
-		List<TextPosition> textPositionList = new ArrayList<TextPosition>();
+		List<TextPosition> textPositionList = new ArrayList<>();
 
 		while (textIterator.hasNext()) {
 			TextPosition textPosition = (TextPosition) textIterator.next();
diff --git a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/PDFLayoutTextStripperByArea.java b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/PDFLayoutTextStripperByArea.java
index a5d39db89a7..74b4b7a03c2 100644
--- a/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/PDFLayoutTextStripperByArea.java
+++ b/document-readers/pdf-reader/src/main/java/org/springframework/ai/reader/pdf/layout/PDFLayoutTextStripperByArea.java
@@ -39,13 +39,13 @@
  */
 public class PDFLayoutTextStripperByArea extends ForkPDFLayoutTextStripper {
 
-	private final List<String> regions = new ArrayList<String>();
+	private final List<String> regions = new ArrayList<>();
 
-	private final Map<String, Rectangle2D> regionArea = new HashMap<String, Rectangle2D>();
+	private final Map<String, Rectangle2D> regionArea = new HashMap<>();
 
-	private final Map<String, ArrayList<List<TextPosition>>> regionCharacterList = new HashMap<String, ArrayList<List<TextPosition>>>();
+	private final Map<String, ArrayList<List<TextPosition>>> regionCharacterList = new HashMap<>();
 
-	private final Map<String, StringWriter> regionText = new HashMap<String, StringWriter>();
+	private final Map<String, StringWriter> regionText = new HashMap<>();
 
 	/**
 	 * Constructor.
@@ -113,8 +113,8 @@ public void extractRegions(PDPage page) throws IOException {
 			setStartPage(getCurrentPageNo());
 			setEndPage(getCurrentPageNo());
 			// reset the stored text for the region so this class can be reused.
-			ArrayList<List<TextPosition>> regionCharactersByArticle = new ArrayList<List<TextPosition>>();
-			regionCharactersByArticle.add(new ArrayList<TextPosition>());
+			ArrayList<List<TextPosition>> regionCharactersByArticle = new ArrayList<>();
+			regionCharactersByArticle.add(new ArrayList<>());
 			this.regionCharacterList.put(regionName, regionCharactersByArticle);
 			this.regionText.put(regionName, new StringWriter());
 		}
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/EventParsingTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/EventParsingTests.java
index 9cd11068bfa..56af44b78a5 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/EventParsingTests.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/EventParsingTests.java
@@ -44,7 +44,7 @@ public void readEvents() throws IOException {
 		String json = new DefaultResourceLoader().getResource("classpath:/sample_events.json")
 			.getContentAsString(Charset.defaultCharset());
 
-		List<StreamEvent> events = new ObjectMapper().readerFor(new TypeReference<List<StreamEvent>>() {
+		List<StreamEvent> events = new ObjectMapper().readerFor(new TypeReference<>() {
 
 		}).readValue(json);
 
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
index 2baf73dd97e..3117d56fb74 100644
--- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/client/AnthropicChatClientIT.java
@@ -110,7 +110,7 @@ void listOutputConverterBean() {
 		List<ActorsFilms> actorsFilms = ChatClient.create(this.chatModel).prompt()
 				.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
 				.call()
-				.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
@@ -143,7 +143,7 @@ void mapOutputConverter() {
 				.user(u -> u.text("Provide me a List of {subject}")
 						.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
 				.call()
-				.entity(new ParameterizedTypeReference<Map<String, Object>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
diff --git a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java
index 2c90d25be0e..f6c7733a431 100644
--- a/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java
+++ b/models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockConverseChatClientIT.java
@@ -88,7 +88,7 @@ void listOutputConverterString() {
 				.user(u -> u.text("List five {subject}")
 						.param("subject", "ice cream flavors"))
 				.call()
-				.entity(new ParameterizedTypeReference<List<String>>() { });
+				.entity(new ParameterizedTypeReference<>() { });
 		// @formatter:on
 
 		logger.info(collection.toString());
@@ -102,7 +102,7 @@ void listOutputConverterBean() {
 		List<ActorsFilms> actorsFilms = ChatClient.create(this.chatModel).prompt()
 				.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
 				.call()
-				.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
@@ -135,7 +135,7 @@ void mapOutputConverter() {
 				.user(u -> u.text("Provide me a List of {subject}")
 						.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
 				.call()
-				.entity(new ParameterizedTypeReference<Map<String, Object>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
diff --git a/models/spring-ai-huggingface/src/main/java/org/springframework/ai/huggingface/HuggingfaceChatModel.java b/models/spring-ai-huggingface/src/main/java/org/springframework/ai/huggingface/HuggingfaceChatModel.java
index 5546b4c54d2..01762f50949 100644
--- a/models/spring-ai-huggingface/src/main/java/org/springframework/ai/huggingface/HuggingfaceChatModel.java
+++ b/models/spring-ai-huggingface/src/main/java/org/springframework/ai/huggingface/HuggingfaceChatModel.java
@@ -101,7 +101,7 @@ public ChatResponse call(Prompt prompt) {
 			String generatedText = generateResponse.getGeneratedText();
 			AllOfGenerateResponseDetails allOfGenerateResponseDetails = generateResponse.getDetails();
 			Map<String, Object> detailsMap = this.objectMapper.convertValue(allOfGenerateResponseDetails,
-					new TypeReference<Map<String, Object>>() {
+					new TypeReference<>() {
 
 					});
 			Generation generation = new Generation(new AssistantMessage(generatedText, detailsMap));
diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatClientIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatClientIT.java
index 47971707e64..a02bc33cd84 100644
--- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatClientIT.java
+++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatClientIT.java
@@ -123,7 +123,7 @@ void listOutputConverterBean() {
 		List<ActorsFilms> actorsFilms = ChatClient.create(this.chatModel).prompt()
 				.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
 				.call()
-				.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
@@ -156,7 +156,7 @@ void mapOutputConverter() {
 				.user(u -> u.text("Provide me a List of {subject}")
 						.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
 				.call()
-				.entity(new ParameterizedTypeReference<Map<String, Object>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelTypeReferenceBeanOutputConverterIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelTypeReferenceBeanOutputConverterIT.java
index 239b281f3ca..2e2a25374f2 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelTypeReferenceBeanOutputConverterIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelTypeReferenceBeanOutputConverterIT.java
@@ -49,7 +49,7 @@ class OpenAiChatModelTypeReferenceBeanOutputConverterIT extends AbstractIT {
 	void typeRefOutputConverterRecords() {
 
 		BeanOutputConverter<List<ActorsFilmsRecord>> outputConverter = new BeanOutputConverter<>(
-				new ParameterizedTypeReference<List<ActorsFilmsRecord>>() {
+				new ParameterizedTypeReference<>() {
 
 				});
 
@@ -78,7 +78,7 @@ void typeRefOutputConverterRecords() {
 	void typeRefStreamOutputConverterRecords() {
 
 		BeanOutputConverter<List<ActorsFilmsRecord>> outputConverter = new BeanOutputConverter<>(
-				new ParameterizedTypeReference<List<ActorsFilmsRecord>>() {
+				new ParameterizedTypeReference<>() {
 
 				});
 
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java
index d2cee2e2eae..d4435c105de 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiPaymentTransactionIT.java
@@ -85,7 +85,7 @@ public void transactionPaymentStatuses(String functionName) {
 					What is the status of my payment transactions 001, 002 and 003?
 					""")
 			.call()
-			.entity(new ParameterizedTypeReference<List<TransactionStatusResponse>>() {
+			.entity(new ParameterizedTypeReference<>() {
 
 			});
 
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java
index 66d6011e203..f98da332ba0 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/client/OpenAiChatClientIT.java
@@ -125,7 +125,7 @@ void listOutputConverterString() {
 				.user(u -> u.text("List five {subject}")
 						.param("subject", "ice cream flavors"))
 				.call()
-				.entity(new ParameterizedTypeReference<List<String>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
@@ -140,7 +140,7 @@ void listOutputConverterBean() {
 		List<ActorsFilms> actorsFilms = ChatClient.create(this.chatModel).prompt()
 				.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
 				.call()
-				.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
@@ -173,7 +173,7 @@ void mapOutputConverter() {
 				.user(u -> u.text("Provide me a List of {subject}")
 						.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
 				.call()
-				.entity(new ParameterizedTypeReference<Map<String, Object>>() {
+				.entity(new ParameterizedTypeReference<>() {
 				});
 		// @formatter:on
 
diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
index 28089504925..91fba4f864f 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
@@ -364,13 +364,13 @@ public DefaultCallResponseSpec(ChatClientRequest chatClientRequest, BaseAdvisorC
 		@Override
 		public <T> ResponseEntity<ChatResponse, T> responseEntity(Class<T> type) {
 			Assert.notNull(type, "type cannot be null");
-			return doResponseEntity(new BeanOutputConverter<T>(type));
+			return doResponseEntity(new BeanOutputConverter<>(type));
 		}
 
 		@Override
 		public <T> ResponseEntity<ChatResponse, T> responseEntity(ParameterizedTypeReference<T> type) {
 			Assert.notNull(type, "type cannot be null");
-			return doResponseEntity(new BeanOutputConverter<T>(type));
+			return doResponseEntity(new BeanOutputConverter<>(type));
 		}
 
 		@Override
diff --git a/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/ChatClientResponseEntityTests.java b/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/ChatClientResponseEntityTests.java
index 07fb07d34cd..e7f381267e8 100644
--- a/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/ChatClientResponseEntityTests.java
+++ b/spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/ChatClientResponseEntityTests.java
@@ -98,7 +98,7 @@ public void parametrizedResponseEntityTest() {
 			.prompt()
 			.user("Tell me about them")
 			.call()
-			.responseEntity(new ParameterizedTypeReference<List<MyBean>>() {
+			.responseEntity(new ParameterizedTypeReference<>() {
 
 			});
 
diff --git a/spring-ai-commons/src/main/java/org/springframework/ai/document/DefaultContentFormatter.java b/spring-ai-commons/src/main/java/org/springframework/ai/document/DefaultContentFormatter.java
index ff8076fc296..f62bc471035 100644
--- a/spring-ai-commons/src/main/java/org/springframework/ai/document/DefaultContentFormatter.java
+++ b/spring-ai-commons/src/main/java/org/springframework/ai/document/DefaultContentFormatter.java
@@ -124,10 +124,10 @@ public String format(Document document, MetadataMode metadataMode) {
 	protected Map<String, Object> metadataFilter(Map<String, Object> metadata, MetadataMode metadataMode) {
 
 		if (metadataMode == MetadataMode.ALL) {
-			return new HashMap<String, Object>(metadata);
+			return new HashMap<>(metadata);
 		}
 		if (metadataMode == MetadataMode.NONE) {
-			return new HashMap<String, Object>(Collections.emptyMap());
+			return new HashMap<>(Collections.emptyMap());
 		}
 
 		Set<String> usableMetadataKeys = new HashSet<>(metadata.keySet());
@@ -139,7 +139,7 @@ else if (metadataMode == MetadataMode.EMBED) {
 			usableMetadataKeys.removeAll(this.excludedEmbedMetadataKeys);
 		}
 
-		return new HashMap<String, Object>(metadata.entrySet()
+		return new HashMap<>(metadata.entrySet()
 			.stream()
 			.filter(e -> usableMetadataKeys.contains(e.getKey()))
 			.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));
diff --git a/spring-ai-commons/src/main/java/org/springframework/ai/reader/JsonReader.java b/spring-ai-commons/src/main/java/org/springframework/ai/reader/JsonReader.java
index 2ea446400b8..680798209e5 100644
--- a/spring-ai-commons/src/main/java/org/springframework/ai/reader/JsonReader.java
+++ b/spring-ai-commons/src/main/java/org/springframework/ai/reader/JsonReader.java
@@ -90,7 +90,7 @@ public List<Document> get() {
 	}
 
 	private Document parseJsonNode(JsonNode jsonNode, ObjectMapper objectMapper) {
-		Map<String, Object> item = objectMapper.convertValue(jsonNode, new TypeReference<Map<String, Object>>() {
+		Map<String, Object> item = objectMapper.convertValue(jsonNode, new TypeReference<>() {
 
 		});
 		var sb = new StringBuilder();
diff --git a/spring-ai-commons/src/test/java/org/springframework/ai/template/NoOpTemplateRendererTests.java b/spring-ai-commons/src/test/java/org/springframework/ai/template/NoOpTemplateRendererTests.java
index caea33f01a8..94dd4010a30 100644
--- a/spring-ai-commons/src/test/java/org/springframework/ai/template/NoOpTemplateRendererTests.java
+++ b/spring-ai-commons/src/test/java/org/springframework/ai/template/NoOpTemplateRendererTests.java
@@ -86,7 +86,7 @@ void shouldNotAcceptNullVariables() {
 	void shouldNotAcceptVariablesWithNullKeySet() {
 		NoOpTemplateRenderer renderer = new NoOpTemplateRenderer();
 		String template = "Hello!";
-		Map<String, Object> variables = new HashMap<String, Object>();
+		Map<String, Object> variables = new HashMap<>();
 		variables.put(null, "Spring AI");
 
 		assertThatThrownBy(() -> renderer.apply(template, variables)).isInstanceOf(IllegalArgumentException.class)
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java
index 222d53d426f..480a200fee1 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/MessageAggregator.java
@@ -66,9 +66,9 @@ public Flux<ChatResponse> aggregate(Flux<ChatResponse> fluxChatResponse,
 				ChatGenerationMetadata.NULL);
 
 		// Usage
-		AtomicReference<Integer> metadataUsagePromptTokensRef = new AtomicReference<Integer>(0);
-		AtomicReference<Integer> metadataUsageGenerationTokensRef = new AtomicReference<Integer>(0);
-		AtomicReference<Integer> metadataUsageTotalTokensRef = new AtomicReference<Integer>(0);
+		AtomicReference<Integer> metadataUsagePromptTokensRef = new AtomicReference<>(0);
+		AtomicReference<Integer> metadataUsageGenerationTokensRef = new AtomicReference<>(0);
+		AtomicReference<Integer> metadataUsageTotalTokensRef = new AtomicReference<>(0);
 
 		AtomicReference<PromptMetadata> metadataPromptMetadataRef = new AtomicReference<>(PromptMetadata.empty());
 		AtomicReference<RateLimit> metadataRateLimitRef = new AtomicReference<>(new EmptyRateLimit());
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/EmbeddingUtils.java b/spring-ai-model/src/main/java/org/springframework/ai/model/EmbeddingUtils.java
index 2ff3339e459..79683df1e64 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/EmbeddingUtils.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/EmbeddingUtils.java
@@ -72,7 +72,7 @@ public static Float[] toFloatArray(final float[] array) {
 
 	public static List<Float> toList(float[] floats) {
 
-		List<Float> output = new ArrayList<Float>();
+		List<Float> output = new ArrayList<>();
 		for (float value : floats) {
 			output.add(value);
 		}
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java b/spring-ai-model/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java
index 0ace8cc5800..cbb944c0de8 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/ModelOptionsUtils.java
@@ -74,11 +74,11 @@ public abstract class ModelOptionsUtils {
 
 	private static final List<String> BEAN_MERGE_FIELD_EXCISIONS = List.of("class");
 
-	private static final ConcurrentHashMap<Class<?>, List<String>> REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap<Class<?>, List<String>>();
+	private static final ConcurrentHashMap<Class<?>, List<String>> REQUEST_FIELD_NAMES_PER_CLASS = new ConcurrentHashMap<>();
 
 	private static final AtomicReference<SchemaGenerator> SCHEMA_GENERATOR_CACHE = new AtomicReference<>();
 
-	private static TypeReference<HashMap<String, Object>> MAP_TYPE_REF = new TypeReference<HashMap<String, Object>>() {
+	private static TypeReference<HashMap<String, Object>> MAP_TYPE_REF = new TypeReference<>() {
 
 	};
 
diff --git a/spring-ai-template-st/src/test/java/org/springframework/ai/template/st/StTemplateRendererTests.java b/spring-ai-template-st/src/test/java/org/springframework/ai/template/st/StTemplateRendererTests.java
index 4d4e979e869..1dd548c5c0e 100644
--- a/spring-ai-template-st/src/test/java/org/springframework/ai/template/st/StTemplateRendererTests.java
+++ b/spring-ai-template-st/src/test/java/org/springframework/ai/template/st/StTemplateRendererTests.java
@@ -94,7 +94,7 @@ void shouldNotAcceptNullVariables() {
 	void shouldNotAcceptVariablesWithNullKeySet() {
 		StTemplateRenderer renderer = StTemplateRenderer.builder().build();
 		String template = "Hello!";
-		Map<String, Object> variables = new HashMap<String, Object>();
+		Map<String, Object> variables = new HashMap<>();
 		variables.put(null, "Spring AI");
 
 		assertThatThrownBy(() -> renderer.apply(template, variables)).isInstanceOf(IllegalArgumentException.class)
diff --git a/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java b/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java
index ccf587b45ce..9ed554f8bc7 100644
--- a/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java
+++ b/vector-stores/spring-ai-azure-cosmos-db-store/src/main/java/org/springframework/ai/vectorstore/cosmosdb/CosmosDBVectorStore.java
@@ -151,7 +151,7 @@ private void initializeContainer(String containerName, String databaseName, int
 
 		// handle hierarchical partition key
 		PartitionKeyDefinition subPartitionKeyDefinition = new PartitionKeyDefinition();
-		List<String> pathsFromCommaSeparatedList = new ArrayList<String>();
+		List<String> pathsFromCommaSeparatedList = new ArrayList<>();
 		String[] subPartitionKeyPaths = partitionKeyPath.split(",");
 		Collections.addAll(pathsFromCommaSeparatedList, subPartitionKeyPaths);
 		if (subPartitionKeyPaths.length > 1) {
diff --git a/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java b/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java
index 0f86bd10c9f..d1059711985 100644
--- a/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java
+++ b/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java
@@ -240,7 +240,7 @@ public List<Document> doSimilaritySearch(SearchRequest request) {
 				final AzureSearchDocument entry = result.getDocument(AzureSearchDocument.class);
 
 				Map<String, Object> metadata = (StringUtils.hasText(entry.metadata()))
-						? JSONObject.parseObject(entry.metadata(), new TypeReference<Map<String, Object>>() {
+						? JSONObject.parseObject(entry.metadata(), new TypeReference<>() {
 
 						}) : Map.of();
 
diff --git a/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDBAtlasVectorStoreIT.java b/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDBAtlasVectorStoreIT.java
index 1f61bdf09ad..7d9cd22fec0 100644
--- a/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDBAtlasVectorStoreIT.java
+++ b/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDBAtlasVectorStoreIT.java
@@ -350,7 +350,7 @@ public EmbeddingModel embeddingModel() {
 
 		@Bean
 		public Converter<MimeType, String> mimeTypeToStringConverter() {
-			return new Converter<MimeType, String>() {
+			return new Converter<>() {
 
 				@Override
 				public String convert(MimeType source) {
@@ -361,7 +361,7 @@ public String convert(MimeType source) {
 
 		@Bean
 		public Converter<String, MimeType> stringToMimeTypeConverter() {
-			return new Converter<String, MimeType>() {
+			return new Converter<>() {
 
 				@Override
 				public MimeType convert(String source) {
diff --git a/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDbVectorStoreObservationIT.java b/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDbVectorStoreObservationIT.java
index 20bf3db36b7..d116081314d 100644
--- a/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDbVectorStoreObservationIT.java
+++ b/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/mongodb/atlas/MongoDbVectorStoreObservationIT.java
@@ -214,7 +214,7 @@ public EmbeddingModel embeddingModel() {
 
 		@Bean
 		public Converter<MimeType, String> mimeTypeToStringConverter() {
-			return new Converter<MimeType, String>() {
+			return new Converter<>() {
 
 				@Override
 				public String convert(MimeType source) {
@@ -225,7 +225,7 @@ public String convert(MimeType source) {
 
 		@Bean
 		public Converter<String, MimeType> stringToMimeTypeConverter() {
-			return new Converter<String, MimeType>() {
+			return new Converter<>() {
 
 				@Override
 				public MimeType convert(String source) {
diff --git a/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java b/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java
index ded27db7c73..177d14da801 100644
--- a/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java
+++ b/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/pgvector/PgVectorStore.java
@@ -382,7 +382,7 @@ public List<Document> doSimilaritySearch(SearchRequest request) {
 	public List<Double> embeddingDistance(String query) {
 		return this.jdbcTemplate.query(
 				"SELECT embedding " + this.comparisonOperator() + " ? AS distance FROM " + getFullyQualifiedTableName(),
-				new RowMapper<Double>() {
+				new RowMapper<>() {
 
 					@Override
 					public Double mapRow(ResultSet rs, int rowNum) throws SQLException {
diff --git a/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStore.java b/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStore.java
index 0f21192686e..b77d9b9b11e 100644
--- a/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStore.java
+++ b/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/pinecone/PineconeVectorStore.java
@@ -301,7 +301,7 @@ private Struct metadataFiltersToStruct(String metadataFilters) {
 	private Map<String, Object> extractMetadata(Struct metadataStruct) {
 		try {
 			String json = JsonFormat.printer().print(metadataStruct);
-			Map<String, Object> metadata = this.objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {
+			Map<String, Object> metadata = this.objectMapper.readValue(json, new TypeReference<>() {
 
 			});
 			metadata.remove(this.pineconeContentFieldName);
diff --git a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java
index 944b67ede7d..ab668a40276 100644
--- a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java
+++ b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java
@@ -42,9 +42,9 @@ public Filter convertExpression(Expression expression) {
 
 	protected Filter convertOperand(Operand operand) {
 		var context = Filter.newBuilder();
-		List<Condition> mustClauses = new ArrayList<Condition>();
-		List<Condition> shouldClauses = new ArrayList<Condition>();
-		List<Condition> mustNotClauses = new ArrayList<Condition>();
+		List<Condition> mustClauses = new ArrayList<>();
+		List<Condition> shouldClauses = new ArrayList<>();
+		List<Condition> mustNotClauses = new ArrayList<>();
 
 		if (operand instanceof Expression expression) {
 			if (expression.type() == ExpressionType.NOT && expression.left() instanceof Group group) {
@@ -173,7 +173,7 @@ protected Condition buildInCondition(Key key, Value value) {
 
 			if (firstValue instanceof String) {
 				// If the first value is a string, then all values should be strings
-				List<String> stringValues = new ArrayList<String>();
+				List<String> stringValues = new ArrayList<>();
 				for (Object valueObj : valueList) {
 					stringValues.add(valueObj.toString());
 				}
@@ -181,7 +181,7 @@ protected Condition buildInCondition(Key key, Value value) {
 			}
 			else if (firstValue instanceof Number) {
 				// If the first value is a number, then all values should be numbers
-				List<Long> longValues = new ArrayList<Long>();
+				List<Long> longValues = new ArrayList<>();
 				for (Object valueObj : valueList) {
 					Long longValue = Long.parseLong(valueObj.toString());
 					longValues.add(longValue);
@@ -204,7 +204,7 @@ protected Condition buildNInCondition(Key key, Value value) {
 
 			if (firstValue instanceof String) {
 				// If the first value is a string, then all values should be strings
-				List<String> stringValues = new ArrayList<String>();
+				List<String> stringValues = new ArrayList<>();
 				for (Object valueObj : valueList) {
 					stringValues.add(valueObj.toString());
 				}
@@ -212,7 +212,7 @@ protected Condition buildNInCondition(Key key, Value value) {
 			}
 			else if (firstValue instanceof Number) {
 				// If the first value is a number, then all values should be numbers
-				List<Long> longValues = new ArrayList<Long>();
+				List<Long> longValues = new ArrayList<>();
 				for (Object valueObj : valueList) {
 					Long longValue = Long.parseLong(valueObj.toString());
 					longValues.add(longValue);
diff --git a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java
index c062e1f6cae..59e88d9e76b 100644
--- a/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java
+++ b/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantValueFactory.java
@@ -90,7 +90,7 @@ private static Value value(Object value) {
 	}
 
 	private static Value value(List<Object> elements) {
-		List<Value> values = new ArrayList<Value>(elements.size());
+		List<Value> values = new ArrayList<>(elements.size());
 
 		for (Object element : elements) {
 			values.add(value(element));
@@ -100,7 +100,7 @@ private static Value value(List<Object> elements) {
 	}
 
 	private static Value value(Object[] elements) {
-		List<Value> values = new ArrayList<Value>(elements.length);
+		List<Value> values = new ArrayList<>(elements.length);
 
 		for (Object element : elements) {
 			values.add(value(element));

From 1d31c138f2a1004af3cd604d1e9c625488a3f62f Mon Sep 17 00:00:00 2001
From: spud <92900806+jamespud@users.noreply.github.com>
Date: Thu, 3 Jul 2025 02:54:14 +0800
Subject: [PATCH 143/165] Fix typos in chat client module (#3013)

Fixes #3013

Signed-off-by: Wenli Tian <jamespud9@gmail.com>
(cherry picked from commit a77509573d018aa26978f43dced49fab245cfab5)
---
 .../org/springframework/ai/chat/client/ChatClient.java    | 6 +++---
 .../springframework/ai/chat/client/DefaultChatClient.java | 8 ++++----
 .../ai/chat/client/DefaultChatClientBuilder.java          | 2 +-
 .../java/org/springframework/ai/chat/package-info.java    | 4 ++--
 4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java
index d6fe27574d1..bf74ab6335c 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java
@@ -45,7 +45,7 @@
 
 /**
  * Client to perform stateless requests to an AI Model, using a fluent API.
- *
+ * <p>
  * Use {@link ChatClient#builder(ChatModel)} to prepare an instance.
  *
  * @author Mark Pollack
@@ -203,8 +203,8 @@ interface StreamPromptResponseSpec {
 	interface ChatClientRequestSpec {
 
 		/**
-		 * Return a {@code ChatClient.Builder} to create a new {@code ChatClient} whose
-		 * settings are replicated from this {@code ChatClientRequest}.
+		 * Return a {@link ChatClient.Builder} to create a new {@link ChatClient} whose
+		 * settings are replicated from this {@link ChatClientRequest}.
 		 */
 		Builder mutate();
 
diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
index 91fba4f864f..5cfe18ac9b4 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java
@@ -122,8 +122,8 @@ public ChatClientRequestSpec prompt(Prompt prompt) {
 	}
 
 	/**
-	 * Return a {@code ChatClient2Builder} to create a new {@code ChatClient} whose
-	 * settings are replicated from this {@code ChatClientRequest}.
+	 * Return a {@link ChatClient.Builder} to create a new {@link ChatClient} whose
+	 * settings are replicated from this {@link ChatClientRequest}.
 	 */
 	@Override
 	public Builder mutate() {
@@ -703,8 +703,8 @@ public TemplateRenderer getTemplateRenderer() {
 		}
 
 		/**
-		 * Return a {@code ChatClient2Builder} to create a new {@code ChatClient2} whose
-		 * settings are replicated from this {@code ChatClientRequest}.
+		 * Return a {@link ChatClient.Builder} to create a new {@link ChatClient} whose
+		 * settings are replicated from this {@link ChatClientRequest}.
 		 */
 		@Override
 		public Builder mutate() {
diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java
index 8d314b0ef59..7d42596ef1e 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClientBuilder.java
@@ -42,7 +42,7 @@
 
 /**
  * DefaultChatClientBuilder is a builder class for creating a ChatClient.
- *
+ * <p>
  * It provides methods to set default values for various properties of the ChatClient.
  *
  * @author Mark Pollack
diff --git a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/package-info.java b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/package-info.java
index 5c60477e19e..3acb1609cbd 100644
--- a/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/package-info.java
+++ b/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/package-info.java
@@ -19,11 +19,11 @@
  * AI generative model domain. This package extends the core domain defined in
  * org.sf.ai.generative, providing implementations specific to chat-based generative AI
  * interactions.
- *
+ * <p>
  * In line with Domain-Driven Design principles, this package includes implementations of
  * entities and value objects specific to the chat context, such as ChatPrompt and
  * ChatResponse, adhering to the ubiquitous language of chat interactions in AI models.
- *
+ * <p>
  * This bounded context is designed to encapsulate all aspects of chat-based AI
  * functionalities, maintaining a clear boundary from other contexts within the AI domain.
  */

From e30fd66307b7e34cce9f6e0909d1eede86d2178d Mon Sep 17 00:00:00 2001
From: Lpepsi <846179345@qq.com>
Date: Thu, 3 Jul 2025 02:58:30 +0800
Subject: [PATCH 144/165] Update anthropic-chat.adoc (#2723)

AnthropicChatModel does not provide a constructor with two parameters.
If there are two parameters, use the Builder to construct an AnthropicChatModel.
The current document misleads users and needs to be updated.

Fixes #2723

Signed-off-by: Lpepsi <846179345@qq.com>
(cherry picked from commit 61c32ae4c9e998db6450d41a5aca0d861b8b924c)
---
 .../modules/ROOT/pages/api/chat/anthropic-chat.adoc    | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
index 6523861c90b..efe8b0e6998 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/anthropic-chat.adoc
@@ -484,13 +484,13 @@ Next, create a `AnthropicChatModel` and use it for text generations:
 [source,java]
 ----
 var anthropicApi = new AnthropicApi(System.getenv("ANTHROPIC_API_KEY"));
-
-var chatModel = new AnthropicChatModel(this.anthropicApi,
-        AnthropicChatOptions.builder()
-            .model("claude-3-opus-20240229")
+var anthropicChatOptions = AnthropicChatOptions.builder()
+            .model("claude-3-7-sonnet-20250219")
             .temperature(0.4)
             .maxTokens(200)
-        .build());
+        .build()
+var chatModel = AnthropicChatModel.builder().anthropicApi(anthropicApi)
+                .defaultOptions(anthropicChatOptions).build();
 
 ChatResponse response = this.chatModel.call(
     new Prompt("Generate the names of 5 famous pirates."));

From f20eeeaea00bc99740aec9522129bf14b45c2754 Mon Sep 17 00:00:00 2001
From: han <125226601+TheEterna@users.noreply.github.com>
Date: Fri, 4 Jul 2025 00:30:01 +0800
Subject: [PATCH 145/165] Fix parameter ordering in JsonSchemaGenerator

Incorrect usage of ClassUtils.isAssignable.

Incorrectly reversing the parameters causes the method to always return true when an Object is passed.

Fixes: #3729

Signed-off-by: TheEterna <125226601+TheEterna@users.noreply.github.com>
(cherry picked from commit cd0cc4693c127492b57da79eede1bc9cfff1608a)
---
 .../util/json/schema/JsonSchemaGenerator.java |  2 +-
 .../util/json/JsonSchemaGeneratorTests.java   | 26 +++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/util/json/schema/JsonSchemaGenerator.java b/spring-ai-model/src/main/java/org/springframework/ai/util/json/schema/JsonSchemaGenerator.java
index 27fae0fcc55..c66b9af2733 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/util/json/schema/JsonSchemaGenerator.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/util/json/schema/JsonSchemaGenerator.java
@@ -129,7 +129,7 @@ public static String generateForMethodInput(Method method, SchemaOption... schem
 			String parameterName = method.getParameters()[i].getName();
 			Type parameterType = method.getGenericParameterTypes()[i];
 			if (parameterType instanceof Class<?> parameterClass
-					&& ClassUtils.isAssignable(parameterClass, ToolContext.class)) {
+					&& ClassUtils.isAssignable(ToolContext.class, parameterClass)) {
 				// A ToolContext method parameter is not included in the JSON Schema
 				// generation.
 				// It's a special type used by Spring AI to pass contextual data to tools
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonSchemaGeneratorTests.java b/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonSchemaGeneratorTests.java
index c0c61f7eab6..243ec73bbfb 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonSchemaGeneratorTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/util/json/JsonSchemaGeneratorTests.java
@@ -161,6 +161,29 @@ void generateSchemaForMethodWithOpenApiSchemaAnnotations() throws Exception {
 		assertThat(schema).isEqualToIgnoringWhitespace(expectedJsonSchema);
 	}
 
+	@Test
+	void generateSchemaForMethodWithObjectParam() throws Exception {
+		Method method = TestMethods.class.getDeclaredMethod("objectParamMethod", Object.class);
+
+		String schema = JsonSchemaGenerator.generateForMethodInput(method);
+		String expectedJsonSchema = """
+				{
+				    "$schema": "https://json-schema.org/draft/2020-12/schema",
+				    "type": "object",
+				    "properties": {
+				        "object": {
+				        }
+				    },
+				    "required": [
+				        "object"
+				    ],
+				    "additionalProperties": false
+				}
+				""";
+
+		assertThat(schema).isEqualToIgnoringWhitespace(expectedJsonSchema);
+	}
+
 	@Test
 	void generateSchemaForMethodWithJacksonAnnotations() throws Exception {
 		Method method = TestMethods.class.getDeclaredMethod("jacksonMethod", String.class, String.class);
@@ -662,6 +685,9 @@ static class TestMethods {
 		public void simpleMethod(String name, int age) {
 		}
 
+		public void objectParamMethod(Object object) {
+		}
+
 		public void annotatedMethod(
 				@ToolParam(required = false, description = "The username of the customer") String username,
 				@ToolParam(required = true) String password) {

From 2f91adb595513ee2b0781d118296a43cf4152491 Mon Sep 17 00:00:00 2001
From: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
Date: Fri, 13 Jun 2025 11:36:58 +0800
Subject: [PATCH 146/165] fix: Fixed GH-3526, Fixed the issue where using the
 `builder()` in `SystemPromptTemplate` returned a Builder for `PromptTemplate`
 instead.

Fixes #3526

Signed-off-by: Sun Yuhan <sunyuhan1998@users.noreply.github.com>
(cherry picked from commit dbaa608be3b8b09b2ebaa1ec2784ce1ecf2b92f2)
---
 .../ai/chat/prompt/PromptTemplate.java        |  12 +-
 .../ai/chat/prompt/SystemPromptTemplate.java  |  56 +++
 .../prompt/SystemPromptTemplateTests.java     | 329 ++++++++++++++++++
 3 files changed, 391 insertions(+), 6 deletions(-)
 create mode 100644 spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/SystemPromptTemplateTests.java

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java
index 9e33506399d..930f5d81b86 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/PromptTemplate.java
@@ -209,17 +209,17 @@ public static Builder builder() {
 		return new Builder();
 	}
 
-	public static final class Builder {
+	public static class Builder {
 
-		private String template;
+		protected String template;
 
-		private Resource resource;
+		protected Resource resource;
 
-		private Map<String, Object> variables = new HashMap<>();
+		protected Map<String, Object> variables = new HashMap<>();
 
-		private TemplateRenderer renderer = DEFAULT_TEMPLATE_RENDERER;
+		protected TemplateRenderer renderer = DEFAULT_TEMPLATE_RENDERER;
 
-		private Builder() {
+		protected Builder() {
 		}
 
 		public Builder template(String template) {
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java
index 18b1629dbec..f1567a9788c 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/prompt/SystemPromptTemplate.java
@@ -20,7 +20,9 @@
 
 import org.springframework.ai.chat.messages.Message;
 import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.template.TemplateRenderer;
 import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
 
 public class SystemPromptTemplate extends PromptTemplate {
 
@@ -32,6 +34,14 @@ public SystemPromptTemplate(Resource resource) {
 		super(resource);
 	}
 
+	private SystemPromptTemplate(String template, Map<String, Object> variables, TemplateRenderer renderer) {
+		super(template, variables, renderer);
+	}
+
+	private SystemPromptTemplate(Resource resource, Map<String, Object> variables, TemplateRenderer renderer) {
+		super(resource, variables, renderer);
+	}
+
 	@Override
 	public Message createMessage() {
 		return new SystemMessage(render());
@@ -52,4 +62,50 @@ public Prompt create(Map<String, Object> model) {
 		return new Prompt(new SystemMessage(render(model)));
 	}
 
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	public static class Builder extends PromptTemplate.Builder {
+
+		public Builder template(String template) {
+			Assert.hasText(template, "template cannot be null or empty");
+			this.template = template;
+			return this;
+		}
+
+		public Builder resource(Resource resource) {
+			Assert.notNull(resource, "resource cannot be null");
+			this.resource = resource;
+			return this;
+		}
+
+		public Builder variables(Map<String, Object> variables) {
+			Assert.notNull(variables, "variables cannot be null");
+			Assert.noNullElements(variables.keySet(), "variables keys cannot be null");
+			this.variables = variables;
+			return this;
+		}
+
+		public Builder renderer(TemplateRenderer renderer) {
+			Assert.notNull(renderer, "renderer cannot be null");
+			this.renderer = renderer;
+			return this;
+		}
+
+		@Override
+		public SystemPromptTemplate build() {
+			if (this.template != null && this.resource != null) {
+				throw new IllegalArgumentException("Only one of template or resource can be set");
+			}
+			else if (this.resource != null) {
+				return new SystemPromptTemplate(this.resource, this.variables, this.renderer);
+			}
+			else {
+				return new SystemPromptTemplate(this.template, this.variables, this.renderer);
+			}
+		}
+
+	}
+
 }
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/SystemPromptTemplateTests.java b/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/SystemPromptTemplateTests.java
new file mode 100644
index 00000000000..d5b08064d12
--- /dev/null
+++ b/spring-ai-model/src/test/java/org/springframework/ai/chat/prompt/SystemPromptTemplateTests.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2023-2025 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.chat.prompt;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.template.NoOpTemplateRenderer;
+import org.springframework.ai.template.TemplateRenderer;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Unit tests for {@link SystemPromptTemplate}.
+ *
+ * @author Sun Yuhan
+ */
+class SystemPromptTemplateTests {
+
+	@Test
+	void createWithValidTemplate() {
+		String template = "Hello {name}!";
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(template);
+		assertThat(systemPromptTemplate.getTemplate()).isEqualTo(template);
+	}
+
+	@Test
+	void createWithEmptyTemplate() {
+		assertThatThrownBy(() -> new SystemPromptTemplate("")).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("template cannot be null or empty");
+	}
+
+	@Test
+	void createWithNullTemplate() {
+		String template = null;
+		assertThatThrownBy(() -> new SystemPromptTemplate(template)).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("template cannot be null or empty");
+	}
+
+	@Test
+	void createWithValidResource() {
+		String content = "Hello {name}!";
+		Resource resource = new ByteArrayResource(content.getBytes());
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(resource);
+		assertThat(systemPromptTemplate.getTemplate()).isEqualTo(content);
+	}
+
+	@Test
+	void createWithNullResource() {
+		Resource resource = null;
+		assertThatThrownBy(() -> new SystemPromptTemplate(resource)).isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("resource cannot be null");
+	}
+
+	@Test
+	void createWithNullVariables() {
+		String template = "Hello!";
+		Map<String, Object> variables = null;
+		assertThatThrownBy(() -> SystemPromptTemplate.builder().template(template).variables(variables).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("variables cannot be null");
+	}
+
+	@Test
+	void createWithNullVariableKeys() {
+		String template = "Hello!";
+		Map<String, Object> variables = new HashMap<>();
+		variables.put(null, "value");
+		assertThatThrownBy(() -> SystemPromptTemplate.builder().template(template).variables(variables).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("variables keys cannot be null");
+	}
+
+	@Test
+	void addVariable() {
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("Hello {name}!");
+		systemPromptTemplate.add("name", "Spring AI");
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void renderWithoutVariables() {
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("Hello!");
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello!");
+	}
+
+	@Test
+	void renderWithVariables() {
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("name", "Spring AI");
+		PromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {name}!")
+			.variables(variables)
+			.build();
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void renderWithAdditionalVariables() {
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("greeting", "Hello");
+		PromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("{greeting} {name}!")
+			.variables(variables)
+			.build();
+
+		Map<String, Object> additionalVariables = new HashMap<>();
+		additionalVariables.put("name", "Spring AI");
+		assertThat(systemPromptTemplate.render(additionalVariables)).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void renderWithResourceVariable() {
+		String resourceContent = "Spring AI";
+		Resource resource = new ByteArrayResource(resourceContent.getBytes());
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("content", resource);
+
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("Hello {content}!");
+		assertThat(systemPromptTemplate.render(variables)).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void createMessageWithoutVariables() {
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("Hello!");
+		Message message = systemPromptTemplate.createMessage();
+		assertThat(message).isInstanceOf(SystemMessage.class);
+		assertThat(message.getText()).isEqualTo("Hello!");
+	}
+
+	@Test
+	void createMessageWithVariables() {
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("name", "Spring AI");
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("Hello {name}!");
+		Message message = systemPromptTemplate.createMessage(variables);
+		assertThat(message).isInstanceOf(SystemMessage.class);
+		assertThat(message.getText()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void createPromptWithoutVariables() {
+		SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate("Hello!");
+		Prompt prompt = systemPromptTemplate.create();
+		assertThat(prompt.getContents()).isEqualTo("Hello!");
+	}
+
+	@Test
+	void createPromptWithVariables() {
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("name", "Spring AI");
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {name}!")
+			.variables(variables)
+			.build();
+		Prompt prompt = systemPromptTemplate.create(variables);
+		assertThat(prompt.getContents()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void createWithCustomRenderer() {
+		TemplateRenderer customRenderer = new NoOpTemplateRenderer();
+		PromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {name}!")
+			.renderer(customRenderer)
+			.build();
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello {name}!");
+	}
+
+	@Test
+	void builderShouldNotAllowBothTemplateAndResource() {
+		String template = "Hello!";
+		Resource resource = new ByteArrayResource(template.getBytes());
+
+		assertThatThrownBy(() -> SystemPromptTemplate.builder().template(template).resource(resource).build())
+			.isInstanceOf(IllegalArgumentException.class)
+			.hasMessageContaining("Only one of template or resource can be set");
+	}
+
+	// --- Builder Pattern Tests ---
+
+	@Test
+	void createWithValidTemplate_Builder() {
+		String template = "Hello {name}!";
+		PromptTemplate systemPromptTemplate = SystemPromptTemplate.builder().template(template).build();
+		// Render with the required variable to check the template string was set
+		// correctly
+		assertThat(systemPromptTemplate.render(Map.of("name", "Test"))).isEqualTo("Hello Test!");
+	}
+
+	@Test
+	void renderWithVariables_Builder() {
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("name", "Spring AI");
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {name}!")
+			.variables(variables) // Use builder's variable method
+			.build();
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void createWithValidResource_Builder() {
+		String content = "Hello {name}!";
+		Resource resource = new ByteArrayResource(content.getBytes());
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder().resource(resource).build();
+		// Render with the required variable to check the resource was read correctly
+		assertThat(systemPromptTemplate.render(Map.of("name", "Resource"))).isEqualTo("Hello Resource!");
+	}
+
+	@Test
+	void addVariable_Builder() {
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {name}!")
+			.variables(Map.of("name", "Spring AI")) // Use variables() method
+			.build();
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void renderWithoutVariables_Builder() {
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder().template("Hello!").build();
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello!");
+	}
+
+	@Test
+	void renderWithAdditionalVariables_Builder() {
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("greeting", "Hello");
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("{greeting} {name}!")
+			.variables(variables) // Set default variables via builder
+			.build();
+
+		Map<String, Object> additionalVariables = new HashMap<>();
+		additionalVariables.put("name", "Spring AI");
+		// Pass additional variables during render - should merge with defaults
+		assertThat(systemPromptTemplate.render(additionalVariables)).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void renderWithResourceVariable_Builder() {
+		String resourceContent = "Spring AI";
+		Resource resource = new ByteArrayResource(resourceContent.getBytes());
+		Map<String, Object> variables = new HashMap<>();
+		variables.put("content", resource);
+
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {content}!")
+			.variables(variables) // Set resource variable via builder
+			.build();
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Spring AI!");
+	}
+
+	@Test
+	void variablesOverwriting_Builder() {
+		Map<String, Object> initialVars = Map.of("name", "Initial", "adj", "Good");
+		Map<String, Object> overwriteVars = Map.of("name", "Overwritten", "noun", "Day");
+
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template("Hello {name} {noun}!")
+			.variables(initialVars) // Set initial variables
+			.variables(overwriteVars) // Overwrite with new variables
+			.build();
+
+		// Expect only variables from the last call to be present
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Overwritten Day!");
+	}
+
+	@Test
+	void customRenderer_Builder() {
+		String template = "This is a test.";
+		TemplateRenderer customRenderer = new CustomTestRenderer();
+
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.template(template)
+			.renderer(customRenderer) // Set custom renderer
+			.build();
+
+		assertThat(systemPromptTemplate.render()).isEqualTo(template + " (Rendered by Custom)");
+	}
+
+	@Test
+	void resource_Builder() {
+		String templateContent = "Hello {name} from Resource!";
+		Resource templateResource = new ByteArrayResource(templateContent.getBytes());
+		Map<String, Object> vars = Map.of("name", "Builder");
+
+		SystemPromptTemplate systemPromptTemplate = SystemPromptTemplate.builder()
+			.resource(templateResource)
+			.variables(vars)
+			.build();
+
+		assertThat(systemPromptTemplate.render()).isEqualTo("Hello Builder from Resource!");
+	}
+
+	// Helper Custom Renderer for testing
+	private static class CustomTestRenderer implements TemplateRenderer {
+
+		@Override
+		public String apply(String template, Map<String, Object> model) {
+			// Simple renderer that just appends a marker
+			// Note: This simple renderer ignores the model map for test purposes.
+			return template + " (Rendered by Custom)";
+		}
+
+	}
+
+}

From a8c4c31681a8968f18b5a886a73b2e1753cae752 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <151690+ilayaperumalg@users.noreply.github.com>
Date: Mon, 7 Jul 2025 16:05:29 +0100
Subject: [PATCH 147/165] Fix NPE in AnthropicApi StreamHelper (#3755)

- When the error event occurs, the ChatCompletionResponseBuilder is empty and hence the contentBlockReference.get() throws NPE.
   - Add null check to unhandled event in addition to the logging the event type
   - Add test to verify the usecase

Fixes #3740

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
(cherry picked from commit 283d0c68ab1031f6705b16de7c043abc8ebc83a4)
---
 .../ai/anthropic/api/StreamHelper.java        |  9 +++++++
 .../ai/anthropic/api/StreamHelperTests.java   | 27 +++++++++++++++++++
 2 files changed, 36 insertions(+)
 create mode 100644 models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java

diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java
index 980c3342afe..f636f29a158 100644
--- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java
+++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/StreamHelper.java
@@ -20,6 +20,9 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionResponse;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock;
 import org.springframework.ai.anthropic.api.AnthropicApi.ContentBlock.Type;
@@ -56,6 +59,8 @@
  */
 public class StreamHelper {
 
+	private static final Logger logger = LoggerFactory.getLogger(StreamHelper.class);
+
 	public boolean isToolUseStart(StreamEvent event) {
 		if (event == null || event.type() == null || event.type() != EventType.CONTENT_BLOCK_START) {
 			return false;
@@ -216,7 +221,11 @@ else if (event.type().equals(EventType.MESSAGE_STOP)) {
 		}
 		else {
 			// Any other event types that should propagate upwards without content
+			if (contentBlockReference.get() == null) {
+				contentBlockReference.set(new ChatCompletionResponseBuilder());
+			}
 			contentBlockReference.get().withType(event.type().name()).withContent(List.of());
+			logger.warn("Unhandled event type: {}", event.type().name());
 		}
 
 		return contentBlockReference.get().build();
diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java
new file mode 100644
index 00000000000..ae112ad7daa
--- /dev/null
+++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/StreamHelperTests.java
@@ -0,0 +1,27 @@
+package org.springframework.ai.anthropic.api;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.ai.anthropic.api.StreamHelper.ChatCompletionResponseBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Ilayaperumal Gopinathan
+ */
+class StreamHelperTest {
+
+	@Test
+	void testErrorEventTypeWithEmptyContentBlock() {
+		AnthropicApi.ErrorEvent errorEvent = new AnthropicApi.ErrorEvent(AnthropicApi.EventType.ERROR,
+				new AnthropicApi.ErrorEvent.Error("error", "error message"));
+		AtomicReference<ChatCompletionResponseBuilder> contentBlockReference = new AtomicReference<>();
+		StreamHelper streamHelper = new StreamHelper();
+		AnthropicApi.ChatCompletionResponse response = streamHelper.eventToChatCompletionResponse(errorEvent,
+				contentBlockReference);
+		assertThat(response).isNotNull();
+	}
+
+}

From 73c0d4d747e1390003a38884be656060fe5fbef0 Mon Sep 17 00:00:00 2001
From: Yanming Zhou <zhouyanming@gmail.com>
Date: Tue, 8 Jul 2025 18:46:43 +0800
Subject: [PATCH 148/165] Improve the MCP document to stop referring to
 outdated DefaultMcpSession (#3760)

Fixes #3760

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
(cherry picked from commit dca1f98c2c4ec4d00b2e44d9bbfdc1eb05d67727)
---
 .../main/antora/modules/ROOT/pages/api/mcp/mcp-overview.adoc    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-overview.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-overview.adoc
index 1f5a19ad906..430ac219565 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-overview.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-overview.adoc
@@ -26,7 +26,7 @@ The Java MCP implementation follows a three-layer architecture:
 |  |
 ^a| image::mcp/mcp-stack.svg[MCP Stack Architecture]
 a| * *Client/Server Layer*: The McpClient handles client-side operations while the McpServer manages server-side protocol operations. Both utilize McpSession for communication management.
-* *Session Layer (McpSession)*: Manages communication patterns and state through the DefaultMcpSession implementation.
+* *Session Layer (McpSession)*: Manages communication patterns and state through the McpClientSession and McpServerSession implementations.
 * *Transport Layer (McpTransport)*: Handles JSON-RPC message serialization and deserialization with support for multiple transport implementations.
 |===
 

From 8c88a6430b4ee075f289924a3dbdd416e4f2ed3a Mon Sep 17 00:00:00 2001
From: Christian Tzolov <christian.tzolov@broadcom.com>
Date: Wed, 16 Jul 2025 15:10:47 +0200
Subject: [PATCH 149/165] Update the ChatClient Advisor reference documentation

Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
---
 .../ROOT/images/advisors-api-classes.jpg      | Bin 775835 -> 404406 bytes
 .../modules/ROOT/images/advisors-flow.jpg     | Bin 364704 -> 372428 bytes
 .../images/advisors-non-stream-vs-stream.jpg  | Bin 435499 -> 411756 bytes
 .../modules/ROOT/pages/api/advisors.adoc      | 218 ++++++++++--------
 4 files changed, 121 insertions(+), 97 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/advisors-api-classes.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/advisors-api-classes.jpg
index 2bec61341e614c24084fda823faa95197a13914f..16ec6ffdd470346de859c131c87021871e63bf64 100644
GIT binary patch
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/advisors-flow.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/advisors-flow.jpg
index a277b190e5ac22783af8e5cd6007b3233f0adc66..0ccf272a7af55e1a251cea0650921810631d8ad4 100644
GIT binary patch
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/advisors-non-stream-vs-stream.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/advisors-non-stream-vs-stream.jpg
index 1ff2af7b399556f82ccb2ed9e37335ff8bb4e3f6..86748b157f0732e2a9af99a4c7730aacfbf4ae36 100644
GIT binary patch
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc
index 4a70fbc8e5e..0004a1e7dd1 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/advisors.adoc
@@ -11,6 +11,10 @@ You can configure existing advisors using the xref:api/chatclient.adoc#_advisor_
 
 [source,java]
 ----
+
+ChatMemory chatMemory = ... // Initialize your chat memory store
+VectorStore vectorStore = ... // Initialize your vector store
+
 var chatClient = ChatClient.builder(chatModel)
     .defaultAdvisors(
         MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
@@ -37,12 +41,12 @@ Advisors also participate in the Observability stack, so you can view metrics an
 
 == Core Components
 
-The API consists of `CallAroundAdvisor` and `CallAroundAdvisorChain` for non-streaming scenarios, and `StreamAroundAdvisor` and `StreamAroundAdvisorChain` for streaming scenarios. 
-It also includes `AdvisedRequest` to represent the unsealed Prompt request, `AdvisedResponse` for the Chat Completion response. Both hold an `advise-context` to share state across the advisor chain.
+The API consists of `CallAdvisor` and `CallAdvisorChain` for non-streaming scenarios, and `StreamAdvisor` and `StreamAdvisorChain` for streaming scenarios. 
+It also includes `ChatClientRequest` to represent the unsealed Prompt request, `ChatClientResponse` for the Chat Completion response. Both hold an `advise-context` to share state across the advisor chain.
 
 image::advisors-api-classes.jpg[Advisors API Classes, width=600, align="center"]
 
-The `nextAroundCall()` and the `nextAroundStream()` are the key advisor methods, typically performing actions such as examining the unsealed Prompt data, customizing and augmenting the Prompt data, invoking the next entity in the advisor chain, optionally blocking the request, examining the chat completion response, and throwing exceptions to indicate processing errors.
+The `adviseCall()` and the `adviseStream()` are the key advisor methods, typically performing actions such as examining the unsealed Prompt data, customizing and augmenting the Prompt data, invoking the next entity in the advisor chain, optionally blocking the request, examining the chat completion response, and throwing exceptions to indicate processing errors.
 
 In addition the `getOrder()` method determines advisor order in the chain, while `getName()` provides a unique advisor name.
 
@@ -52,14 +56,14 @@ The last advisor, added automatically, sends the request to the LLM.
 
 Following flow diagram illustrates the interaction between the advisor chain and the Chat Model:
 
-image::advisors-flow.jpg[Advisors API Flow, width=400, align="left"]
+image::advisors-flow.jpg[Advisors API Flow, width=400, align="center"]
 
-. The Spring AI framework creates an `AdvisedRequest` from user's `Prompt` along with an empty `AdvisorContext` object.
+. The Spring AI framework creates an `ChatClientRequest` from user's `Prompt` along with an empty advisor `context` object.
 . Each advisor in the chain processes the request, potentially modifying it. Alternatively, it can choose to block the request by not making the call to invoke the next entity. In the latter case, the advisor is responsible for filling out the response.
 . The final advisor, provided by the framework, sends the request to the `Chat Model`.
-. The Chat Model's response is then passed back through the advisor chain and converted into `AdvisedResponse`. Later includes the shared `AdvisorContext` instance.
+. The Chat Model's response is then passed back through the advisor chain and converted into `ChatClientResponse`. Later includes the shared advisor `context` instance.
 . Each advisor can process or modify the response.
-. The final `AdvisedResponse` is returned to the client by extracting the `ChatCompletion`.
+. The final `ChatClientResponse` is returned to the client by extracting the `ChatCompletion`.
 
 === Advisor Order
 The execution order of advisors in the chain is determined by the `getOrder()` method. Key points to understand:
@@ -142,43 +146,44 @@ public interface Advisor extends Ordered {
 The two sub-interfaces for synchronous and reactive Advisors are
 
 ```java
-public interface CallAroundAdvisor extends Advisor {
+public interface CallAdvisor extends Advisor {
 
-	/**
-	 * Around advice that wraps the ChatModel#call(Prompt) method.
-	 * @param advisedRequest the advised request
-	 * @param chain the advisor chain
-	 * @return the response
-	 */
-	AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
+	ChatClientResponse adviseCall(
+		ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
 
 }
+
 ```
 
 and
 
 ```java
-public interface StreamAroundAdvisor extends Advisor {
+public interface StreamAdvisor extends Advisor {
 
-	/**
-	 * Around advice that wraps the invocation of the advised request.
-	 * @param advisedRequest the advised request
-	 * @param chain the chain of advisors to execute
-	 * @return the result of the advised request
-	 */
-	Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
+	Flux<ChatClientResponse> adviseStream(
+		ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
 
 }
 ```
 
-To continue the chain of Advice, use `CallAroundAdvisorChain` and `StreamAroundAdvisorChain` in your Advice implementation:
+To continue the chain of Advice, use `CallAdvisorChain` and `StreamAdvisorChain` in your Advice implementation:
 
 The interfaces are
 
 ```java
-public interface CallAroundAdvisorChain {
+public interface CallAdvisorChain extends AdvisorChain {
 
-	AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
+	/**
+	 * Invokes the next {@link CallAdvisor} in the {@link CallAdvisorChain} with the given
+	 * request.
+	 */
+	ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
+
+	/**
+	 * Returns the list of all the {@link CallAdvisor} instances included in this chain at
+	 * the time of its creation.
+	 */
+	List<CallAdvisor> getCallAdvisors();
 
 }
 ```
@@ -186,18 +191,27 @@ public interface CallAroundAdvisorChain {
 and
 
 ```java
-public interface StreamAroundAdvisorChain {
+public interface StreamAdvisorChain extends AdvisorChain {
 
-	Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
+	/**
+	 * Invokes the next {@link StreamAdvisor} in the {@link StreamAdvisorChain} with the
+	 * given request.
+	 */
+	Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
+
+	/**
+	 * Returns the list of all the {@link StreamAdvisor} instances included in this chain
+	 * at the time of its creation.
+	 */
+	List<StreamAdvisor> getStreamAdvisors();
 
 }
 ```
 
 
-
 == Implementing an Advisor
 
-To create an advisor, implement either `CallAroundAdvisor` or `StreamAroundAdvisor` (or both). The key method to implement is `nextAroundCall()` for non-streaming or `nextAroundStream()` for streaming advisors.
+To create an advisor, implement either `CallAdvisor` or `StreamAdvisor` (or both). The key method to implement is `nextCall()` for non-streaming or `nextStream()` for streaming advisors.
 
 === Examples
 
@@ -205,13 +219,13 @@ We will provide few hands-on examples to illustrate how to implement advisors fo
 
 ==== Logging Advisor
 
-We can implement a simple logging advisor that logs the `AdvisedRequest` before and the `AdvisedResponse` after the call to the next advisor in the chain.
+We can implement a simple logging advisor that logs the `ChatClientRequest` before and the `ChatClientResponse` after the call to the next advisor in the chain.
 Note that the advisor only observes the request and response and does not modify them.
 This implementation support both non-streaming and streaming scenarios.
 
 [source,java]
 ----
-public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
+public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
 
 	private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
 
@@ -225,33 +239,41 @@ public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvis
 		return 0; 
 	}
 
-	@Override
-	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
 
-		logger.debug("BEFORE: {}", advisedRequest);
+	@Override
+	public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
+		logRequest(chatClientRequest);
 
-		AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
+		ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
 
-		logger.debug("AFTER: {}", advisedResponse);
+		logResponse(chatClientResponse);
 
-		return advisedResponse;
+		return chatClientResponse;
 	}
 
 	@Override
-	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
+	public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
+			StreamAdvisorChain streamAdvisorChain) {
+		logRequest(chatClientRequest);
 
-		logger.debug("BEFORE: {}", advisedRequest);
+		Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
 
-		Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
-		
-        return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, 
-                    advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); // <3>
+		return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse); // <3>
 	}
+
+	private void logRequest(ChatClientRequest request) {
+		logger.debug("request: {}", request);
+	}
+
+	private void logResponse(ChatClientResponse chatClientResponse) {
+		logger.debug("response: {}", chatClientResponse);
+	}
+
 }
 ----
 <1> Provides a unique name for the advisor.
 <2> You can control the order of execution by setting the order value. Lower values execute first.
-<3> The `MessageAggregator` is a utility class that aggregates the Flux responses into a single AdvisedResponse.
+<3> The `MessageAggregator` is a utility class that aggregates the Flux responses into a single ChatClientResponse.
 This can be useful for logging or other processing that observe the entire response rather than individual items in the stream.
 Note that you can not alter the response in the `MessageAggregator` as it is a read-only operation.
 
@@ -269,49 +291,59 @@ Implementing an advisor that applies the Re2 technique to the user's input query
 
 [source,java]
 ----
-public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
 
+public class ReReadingAdvisor implements BaseAdvisor {
 
-	private AdvisedRequest before(AdvisedRequest advisedRequest) { // <1>
+	private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
+			{re2_input_query}
+			Read the question again: {re2_input_query}
+			""";
 
-		Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
-		advisedUserParams.put("re2_input_query", advisedRequest.userText());
+	private final String re2AdviseTemplate;
 
-		return AdvisedRequest.from(advisedRequest)
-			.userText("""
-			    {re2_input_query}
-			    Read the question again: {re2_input_query}
-			    """)
-			.userParams(advisedUserParams)
-			.build();
+	private int order = 0;
+
+	public ReReadingAdvisor() {
+		this(DEFAULT_RE2_ADVISE_TEMPLATE);
+	}
+
+	public ReReadingAdvisor(String re2AdviseTemplate) {
+		this.re2AdviseTemplate = re2AdviseTemplate;
 	}
 
 	@Override
-	public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { // <2>
-		return chain.nextAroundCall(this.before(advisedRequest));
+	public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { // <1>
+		String augmentedUserText = PromptTemplate.builder()
+			.template(this.re2AdviseTemplate)
+			.variables(Map.of("re2_input_query", chatClientRequest.prompt().getUserMessage().getText()))
+			.build()
+			.render();
+
+		return chatClientRequest.mutate()
+			.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
+			.build();
 	}
 
 	@Override
-	public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { // <3>
-		return chain.nextAroundStream(this.before(advisedRequest));
+	public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
+		return chatClientResponse;
 	}
 
 	@Override
-	public int getOrder() { // <4>
-		return 0; 
+	public int getOrder() { // <2>
+		return this.order;
 	}
 
-    @Override
-    public String getName() { // <5>
-		return this.getClass().getSimpleName();
+	public ReReadingAdvisor withOrder(int order) {
+		this.order = order;
+		return this;
 	}
+
 }
 ----
 <1> The `before` method augments the user's input query applying the Re-Reading technique.
-<2> The `aroundCall` method intercepts the non-streaming request and applies the Re-Reading technique.
-<3> The `aroundStream` method intercepts the streaming request and applies the Re-Reading technique.
-<4> You can control the order of execution by setting the order value. Lower values execute first.
-<5> Provides a unique name for the advisor.
+<2> You can control the order of execution by setting the order value. Lower values execute first.
+
 
 ==== Spring AI Built-in Advisors
 
@@ -335,7 +367,19 @@ Retrieves memory from a VectorStore and adds it into the prompt's system text. T
 ===== Question Answering Advisor
 * `QuestionAnswerAdvisor`
 +
-This advisor uses a vector store to provide question-answering capabilities, implementing the RAG (Retrieval-Augmented Generation) pattern.
+This advisor uses a vector store to provide question-answering capabilities, implementing the Naive RAG (Retrieval-Augmented Generation) pattern.
+
+* `RetrievalAugmentationAdvisor`
++
+ Advisor that implements common Retrieval Augmented Generation (RAG) flows using the building blocks defined in the `org.springframework.ai.rag` package and following the Modular RAG Architecture.
+
+
+===== Reasoning Advisor
+* `ReReadingAdvisor`
++
+Implements a re-reading strategy for LLM reasoning, dubbed RE2, to enhance understanding in the input phase. 
+Based on the article: [Re-Reading Improves Reasoning in LLMs](https://arxiv.org/pdf/2309.06275).
+
 
 ===== Content Safety Advisor
 * `SafeGuardAdvisor`
@@ -345,7 +389,7 @@ A simple advisor designed to prevent the model from generating harmful or inappr
 
 === Streaming vs Non-Streaming
 
-image::advisors-non-stream-vs-stream.jpg[Advisors Streaming vs Non-Streaming Flow, width=800, align="left"]
+image::advisors-non-stream-vs-stream.jpg[Advisors Streaming vs Non-Streaming Flow, width=800, align="center"]
 
 * Non-streaming advisors work with complete requests and responses.
 * Streaming advisors handle requests and responses as continuous streams, using reactive programming concepts (e.g., Flux for responses).
@@ -356,15 +400,15 @@ image::advisors-non-stream-vs-stream.jpg[Advisors Streaming vs Non-Streaming Flo
 [source,java]
 ----
 @Override
-public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
+public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain chain) {
     
-    return  Mono.just(advisedRequest)
+    return  Mono.just(chatClientRequest)
             .publishOn(Schedulers.boundedElastic())
             .map(request -> {
                 // This can be executed by blocking and non-blocking Threads.
                 // Advisor before next section
             })
-            .flatMapMany(request -> chain.nextAroundStream(request))
+            .flatMapMany(request -> chain.nextStream(request))
             .map(response -> {
                 // Advisor after next section
             });
@@ -378,13 +422,7 @@ public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamA
 . Implement both streaming and non-streaming versions of your advisor for maximum flexibility.
 . Carefully consider the order of advisors in your chain to ensure proper data flow.
 
-
-== Backward Compatibility
-
-IMPORTANT: The `AdvisedRequest` class is moved to a new package.
-
 == Breaking API Changes
-The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to 1.0 M3. Here are the key modifications:
 
 === Advisor Interfaces
 
@@ -395,6 +433,9 @@ The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to
 ** `CallAroundAdvisor`
 ** `StreamAroundAdvisor`
 * The `StreamResponseMode`, previously part of `ResponseAdvisor`, has been removed.
+* In 1.0.0 these interfaces have been replaced:
+** `CallAroundAdvisor` -> `CallAdvisor`, `StreamAroundAdvisor` -> `StreamAdvisor`, `CallAroundAdvisorChain` -> `CallAdvisorChain` and `StreamAroundAdvisorChain` -> `StreamAdvisorChain`. 
+** `AdvisedRequest` -> `ChatClientRequest` are `AdivsedResponse` -> `ChatClientResponse`.
 
 === Context Map Handling
 
@@ -405,20 +446,3 @@ The Spring AI Advisor Chain underwent significant changes from version 1.0 M2 to
 ** The context map is now part of the `AdvisedRequest` and `AdvisedResponse` records.
 ** The map is immutable.
 ** To update the context, use the `updateContext` method, which creates a new unmodifiable map with the updated contents.
-
-Example of updating the context in 1.0 M3:
-
-[source,java]
-----
-@Override
-public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
-
-    this.advisedRequest = advisedRequest.updateContext(context -> {
-        context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName());  // Add multiple key-value pairs
-        context.put("lastBefore", getName());  // Add a single key-value pair
-        return context;
-    });
-
-    // Method implementation continues...
-}
-----

From 6a19c2e9e0a8de7ed87b7d8a31cb28b1c92d328d Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Wed, 16 Jul 2025 11:42:03 -0400
Subject: [PATCH 150/165] Update openai audio test due to changes in openai
 safety settings

Signed-off-by: Mark Pollack <mark.pollack@broadcom.com>
---
 .../springframework/ai/openai/api/OpenAiApiIT.java |  5 ++---
 .../ai/openai/chat/OpenAiChatModelIT.java          | 14 +++++++++++++-
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java
index bf56a9fc2e8..e45b0537eed 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiIT.java
@@ -122,8 +122,7 @@ void inputAudio() throws IOException {
 
 	@Test
 	void outputAudio() {
-		ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage(
-				"What is the magic spell to make objects fly?", Role.USER);
+		ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage("Say 'I am a robot'", Role.USER);
 		ChatCompletionRequest.AudioParameters audioParameters = new ChatCompletionRequest.AudioParameters(
 				ChatCompletionRequest.AudioParameters.Voice.NOVA,
 				ChatCompletionRequest.AudioParameters.AudioResponseFormat.MP3);
@@ -139,7 +138,7 @@ void outputAudio() {
 
 		assertThat(response.getBody().choices().get(0).message().audioOutput().data()).isNotNull();
 		assertThat(response.getBody().choices().get(0).message().audioOutput().transcript())
-			.containsIgnoringCase("leviosa");
+			.containsIgnoringCase("robot");
 	}
 
 	@Test
diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
index d36d6b89a4c..65eb486b6f7 100644
--- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
+++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelIT.java
@@ -589,7 +589,19 @@ void multiModalityInputAudio(String modelName) {
 			.call(new Prompt(List.of(userMessage), ChatOptions.builder().model(modelName).build()));
 
 		logger.info(response.getResult().getOutput().getText());
-		assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("hobbits");
+		String responseText = response.getResult().getOutput().getText();
+		assertThat(responseText).satisfiesAnyOf(text -> assertThat(text).containsIgnoringCase("hobbit"),
+				text -> assertThat(text).containsIgnoringCase("lord of the rings"),
+				text -> assertThat(text).containsIgnoringCase("lotr"),
+				text -> assertThat(text).containsIgnoringCase("tolkien"),
+				text -> assertThat(text).containsIgnoringCase("fantasy"),
+				text -> assertThat(text).containsIgnoringCase("ring"),
+				text -> assertThat(text).containsIgnoringCase("shire"),
+				text -> assertThat(text).containsIgnoringCase("baggins"),
+				text -> assertThat(text).containsIgnoringCase("gandalf"),
+				text -> assertThat(text).containsIgnoringCase("frodo"),
+				text -> assertThat(text).containsIgnoringCase("meme"),
+				text -> assertThat(text).containsIgnoringCase("remix"));
 		assertThat(response.getMetadata().getModel()).containsIgnoringCase(modelName);
 	}
 

From 69a5d8eed5d12141bdcff2a9db560f36a1bf8671 Mon Sep 17 00:00:00 2001
From: ingbyr <zwk@bupt.cn>
Date: Thu, 17 Jul 2025 00:56:24 +0800
Subject: [PATCH 151/165] Fix example code for "Streaming Responses" (#3802)

* Fix example code for "Streaming Responses"

Fixes #3802

Signed-off-by: ingbyr <zwk@bupt.cn>
(cherry picked from commit 42210d3a110b937520ff4fd27de8a5e7842ac20e)
---
 .../src/main/antora/modules/ROOT/pages/api/chatclient.adoc      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
index 48f39dc70cc..e1ea623fb6c 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatclient.adoc
@@ -314,7 +314,7 @@ Flux<String> flux = this.chatClient.prompt()
 
 String content = this.flux.collectList().block().stream().collect(Collectors.joining());
 
-List<ActorFilms> actorFilms = this.converter.convert(this.content);
+List<ActorsFilms> actorFilms = this.converter.convert(this.content);
 ----
 
 == Prompt Templates

From fba87cab218f17e53a0c2f85d196766ffc805f67 Mon Sep 17 00:00:00 2001
From: Seokjae Lee <seok9211@naver.com>
Date: Thu, 17 Jul 2025 01:59:39 +0900
Subject: [PATCH 152/165] docs: update mcp specification document link (#3810)

Fixes #3810

Signed-off-by: Seokjae Lee <seok9211@naver.com>
(cherry picked from commit 788d25e10963ea8d1f0c2a5b87bec85e82084334)
---
 .../ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
index dae75fbc143..6d19626961c 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-server-boot-starter-docs.adoc
@@ -383,5 +383,5 @@ You can have multiple beans producing ToolCallbacks. The auto-configuration will
 == Additional Resources
 
 * link:https://docs.spring.io/spring-ai/reference/[Spring AI Documentation]
-* link:https://modelcontextprotocol.github.io/specification/[Model Context Protocol Specification]
+* link:https://modelcontextprotocol.io/specification[Model Context Protocol Specification]
 * link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration[Spring Boot Auto-configuration]

From 4da1a23f801970e6ed0a3bd90866e3aff47b2c79 Mon Sep 17 00:00:00 2001
From: Sizhe Fan <132420031+paoxiaomooo@users.noreply.github.com>
Date: Thu, 17 Jul 2025 01:02:22 +0800
Subject: [PATCH 153/165] docs(mcp): add dynamic MCP client selection example
 (#3751)

Fixes #3751

demonstrate how to filter injected MCP clients by server name
  using getServerInfo().name()

Signed-off-by: Sizhe Fan <132420031+paoxiaomooo@users.noreply.github.com>
(cherry picked from commit ffad572d51fb4fd450fdf8097aa0335002d30002)
---
 .../modules/ROOT/pages/api/mcp/mcp-helpers.adoc | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-helpers.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-helpers.adoc
index 43faf5d145d..9f24513c9de 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-helpers.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-helpers.adoc
@@ -65,7 +65,24 @@ For multiple clients:
 List<McpSyncClient> clients = // obtain list of clients
 List<ToolCallback> callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);
 ----
++
+For dynamic selection of a subset of clients 
++
+[source,java]
+----
+@Autowired
+private List<McpSyncClient> mcpSyncClients;
 
+public ToolCallbackProvider buildProvider(Set<String> allowedServerNames) {
+    // Filter by server.name().
+    List<McpSyncClient> selected = mcpSyncClients.stream()
+        .filter(c -> allowedServerNames.contains(c.getServerInfo().name()))
+        .toList();
+
+    return new SyncMcpToolCallbackProvider(selected);
+}
+
+----
 Async::
 +
 [source,java]

From fa3583316808e7ff53542742724b548666c8f231 Mon Sep 17 00:00:00 2001
From: finyuq <148207754+finyuq@users.noreply.github.com>
Date: Thu, 17 Jul 2025 21:37:14 +0800
Subject: [PATCH 154/165] fix method tool call throws same method name (#2774)

Fixes #2771
Signed-off-by: finyu <18664388610@msn.cn>
(cherry picked from commit a4351c7fe79909a63f31c5e78f962b1408fd86ca)
---
 .../method/MethodToolCallbackProvider.java    |  1 +
 .../MethodToolCallbackProviderTest.java       | 32 +++++++++++++++++++
 2 files changed, 33 insertions(+)
 create mode 100644 spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java

diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
index 264c29c53fd..400f77eedb7 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/method/MethodToolCallbackProvider.java
@@ -88,6 +88,7 @@ public ToolCallback[] getToolCallbacks() {
 						AopUtils.isAopProxy(toolObject) ? AopUtils.getTargetClass(toolObject) : toolObject.getClass()))
 				.filter(this::isToolAnnotatedMethod)
 				.filter(toolMethod -> !isFunctionalType(toolMethod))
+				.filter(ReflectionUtils.USER_DECLARED_METHODS::matches)
 				.map(toolMethod -> MethodToolCallback.builder()
 					.toolDefinition(ToolDefinitions.from(toolMethod))
 					.toolMetadata(ToolMetadata.from(toolMethod))
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java
new file mode 100644
index 00000000000..24430354cb6
--- /dev/null
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java
@@ -0,0 +1,32 @@
+package org.springframework.ai.tool.method;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.ai.tool.annotation.Tool;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class MethodToolCallbackProviderTest {
+
+	abstract class TestObjectClass<T> {
+
+		public abstract String test(T input);
+	}
+
+	class TestObjectSuperClass extends TestObjectClass<String> {
+
+		@Tool
+		public String test(String input) {
+			return input;
+		}
+	}
+
+	@Test
+	public void buildToolsWithBridgeMethodReturnOnlyUserDeclaredMethods() {
+		MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder().toolObjects(new TestObjectSuperClass()).build();
+		ToolCallback[] toolCallbacks = provider.getToolCallbacks();
+		assertEquals(1, toolCallbacks.length);
+		assertInstanceOf(MethodToolCallback.class, toolCallbacks[0]);
+	}
+
+}

From 926b4dc1616ee82cd37459cbb5fec358f7a9e127 Mon Sep 17 00:00:00 2001
From: Sebastian Espei <seblsebastian@aol.de>
Date: Thu, 17 Jul 2025 17:37:52 +0200
Subject: [PATCH 155/165] Fix PropmptTemplate typo in structured-output-api.jpg
 (#3304)

Fixes #3304

Signed-off-by: Skillkiller <seblsebastian@aol.de>
(cherry picked from commit 9d1e1b5b152ee330e1f7a09e59391ea007871442)
---
 .../ROOT/images/structured-output-api.jpg     | Bin 250046 -> 542489 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/structured-output-api.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/structured-output-api.jpg
index de792b4b3d44e132d6141e176d17a718b6938c5c..620bfaf8f567094b42c9e4fbd5cb8ea403c3407a 100644
GIT binary patch

From 1eb50556742c6a0fd77d679fd406a1983d2c1e4c Mon Sep 17 00:00:00 2001
From: jay <rlaakswo0687@gmail.com>
Date: Mon, 26 May 2025 16:37:04 +0900
Subject: [PATCH 156/165] Refactor builder pattern in
 VertexAiGeminiSafetySetting

Replace builder-based ctor with full-arg private constructor

Signed-off-by: jay <rlaakswo0687@gmail.com>
---
 .../common/VertexAiGeminiSafetySetting.java   | 33 +++++--------------
 .../gemini/VertexAiGeminiChatModelIT.java     |  2 +-
 2 files changed, 9 insertions(+), 26 deletions(-)

diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/common/VertexAiGeminiSafetySetting.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/common/VertexAiGeminiSafetySetting.java
index 9a513cfa5f0..6d46b9d9e5b 100644
--- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/common/VertexAiGeminiSafetySetting.java
+++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/common/VertexAiGeminiSafetySetting.java
@@ -18,6 +18,10 @@
 
 public class VertexAiGeminiSafetySetting {
 
+	public static Builder builder() {
+		return new Builder();
+	}
+
 	/**
 	 * Enum representing different threshold levels for blocking harmful content.
 	 */
@@ -77,51 +81,30 @@ public int getValue() {
 
 	}
 
-	private HarmCategory category;
+	private final HarmCategory category;
 
-	private HarmBlockThreshold threshold;
+	private final HarmBlockThreshold threshold;
 
-	private HarmBlockMethod method;
-
-	// Default constructor
-	public VertexAiGeminiSafetySetting() {
-		this.category = HarmCategory.HARM_CATEGORY_UNSPECIFIED;
-		this.threshold = HarmBlockThreshold.HARM_BLOCK_THRESHOLD_UNSPECIFIED;
-		this.method = HarmBlockMethod.HARM_BLOCK_METHOD_UNSPECIFIED;
-	}
+	private final HarmBlockMethod method;
 
-	// Constructor with all fields
-	public VertexAiGeminiSafetySetting(HarmCategory category, HarmBlockThreshold threshold, HarmBlockMethod method) {
+	private VertexAiGeminiSafetySetting(HarmCategory category, HarmBlockThreshold threshold, HarmBlockMethod method) {
 		this.category = category;
 		this.threshold = threshold;
 		this.method = method;
 	}
 
-	// Getters and setters
 	public HarmCategory getCategory() {
 		return this.category;
 	}
 
-	public void setCategory(HarmCategory category) {
-		this.category = category;
-	}
-
 	public HarmBlockThreshold getThreshold() {
 		return this.threshold;
 	}
 
-	public void setThreshold(HarmBlockThreshold threshold) {
-		this.threshold = threshold;
-	}
-
 	public HarmBlockMethod getMethod() {
 		return this.method;
 	}
 
-	public void setMethod(HarmBlockMethod method) {
-		this.method = method;
-	}
-
 	@Override
 	public String toString() {
 		return "SafetySetting{" + "category=" + this.category + ", threshold=" + this.threshold + ", method="
diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java
index 9bacdde2066..dc73d55ef09 100644
--- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java
+++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModelIT.java
@@ -117,7 +117,7 @@ void googleSearchToolFlash() {
 	@Test
 	@Disabled
 	void testSafetySettings() {
-		List<VertexAiGeminiSafetySetting> safetySettings = List.of(new VertexAiGeminiSafetySetting.Builder()
+		List<VertexAiGeminiSafetySetting> safetySettings = List.of(VertexAiGeminiSafetySetting.builder()
 			.withCategory(VertexAiGeminiSafetySetting.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT)
 			.withThreshold(VertexAiGeminiSafetySetting.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE)
 			.build());

From 9068789a565594911f9d9b5b27836d38f23d9826 Mon Sep 17 00:00:00 2001
From: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
Date: Thu, 17 Jul 2025 15:20:35 +0100
Subject: [PATCH 157/165] Refactor MethodToolCallbackProviderTests

 - Move the existing buildToolsWithBridgeMethodReturnOnlyUserDeclaredMethods into MethodToolCallbackProviderTests
 - Delete MethodToolCallbackProviderTest

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
---
 .../MethodToolCallbackProviderTest.java       | 32 -------------------
 .../MethodToolCallbackProviderTests.java      | 28 ++++++++++++++++
 2 files changed, 28 insertions(+), 32 deletions(-)
 delete mode 100644 spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java

diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java
deleted file mode 100644
index 24430354cb6..00000000000
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.springframework.ai.tool.method;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.ai.tool.ToolCallback;
-import org.springframework.ai.tool.annotation.Tool;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-class MethodToolCallbackProviderTest {
-
-	abstract class TestObjectClass<T> {
-
-		public abstract String test(T input);
-	}
-
-	class TestObjectSuperClass extends TestObjectClass<String> {
-
-		@Tool
-		public String test(String input) {
-			return input;
-		}
-	}
-
-	@Test
-	public void buildToolsWithBridgeMethodReturnOnlyUserDeclaredMethods() {
-		MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder().toolObjects(new TestObjectSuperClass()).build();
-		ToolCallback[] toolCallbacks = provider.getToolCallbacks();
-		assertEquals(1, toolCallbacks.length);
-		assertInstanceOf(MethodToolCallback.class, toolCallbacks[0]);
-	}
-
-}
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
index a5659a9cc9d..f0785188920 100644
--- a/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
+++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/method/MethodToolCallbackProviderTests.java
@@ -23,6 +23,7 @@
 
 import org.junit.jupiter.api.Test;
 
+import org.springframework.ai.tool.ToolCallback;
 import org.springframework.ai.tool.annotation.Tool;
 import org.springframework.ai.tool.execution.DefaultToolCallResultConverter;
 import org.springframework.ai.tool.execution.ToolCallResultConverter;
@@ -30,6 +31,8 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 
 /**
  * Unit tests for {@link MethodToolCallbackProvider}.
@@ -112,6 +115,31 @@ void whenEnhanceToolObjectHasMixOfValidAndFunctionalTypeToolMethodsThenSucceed()
 		assertThat(provider.getToolCallbacks()[0].getToolDefinition().name()).isEqualTo("validTool");
 	}
 
+	@Test
+	public void buildToolsWithBridgeMethodReturnOnlyUserDeclaredMethods() {
+		MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
+			.toolObjects(new TestObjectSuperClass())
+			.build();
+		ToolCallback[] toolCallbacks = provider.getToolCallbacks();
+		assertEquals(1, toolCallbacks.length);
+		assertInstanceOf(MethodToolCallback.class, toolCallbacks[0]);
+	}
+
+	abstract class TestObjectClass<T> {
+
+		public abstract String test(T input);
+
+	}
+
+	class TestObjectSuperClass extends TestObjectClass<String> {
+
+		@Tool
+		public String test(String input) {
+			return input;
+		}
+
+	}
+
 	static class ValidToolObject {
 
 		@Tool

From 2e579b1e38080d38a786e7878b2e1d14805f7aa7 Mon Sep 17 00:00:00 2001
From: Gareth Evans <gareth@bryncynfelin.co.uk>
Date: Wed, 23 Jul 2025 13:47:53 +0100
Subject: [PATCH 158/165] chore: correct spelling of frequencyPenalty on google
 builders

Signed-off-by: Gareth Evans <gareth@bryncynfelin.co.uk>
---
 .gitignore                                                      | 2 +-
 .../ai/vertexai/gemini/VertexAiGeminiChatOptions.java           | 2 +-
 .../ai/vertexai/gemini/CreateGeminiRequestTests.java            | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 949a99baa4f..f201559a60c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,7 +29,7 @@ out
 
 vscode
 settings.json
-
+CLAUDE.md
 node
 node_modules
 package-lock.json
diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java
index 68ae24a92e2..07b4df4be31 100644
--- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java
+++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatOptions.java
@@ -403,7 +403,7 @@ public Builder topK(Integer topK) {
 			return this;
 		}
 
-		public Builder frequencePenalty(Double frequencyPenalty) {
+		public Builder frequencyPenalty(Double frequencyPenalty) {
 			this.options.setFrequencyPenalty(frequencyPenalty);
 			return this;
 		}
diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/CreateGeminiRequestTests.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/CreateGeminiRequestTests.java
index bcb32a748fa..5fa5cf588ac 100644
--- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/CreateGeminiRequestTests.java
+++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/CreateGeminiRequestTests.java
@@ -86,7 +86,7 @@ public void createRequestWithFrequencyAndPresencePenalty() {
 			.vertexAI(this.vertexAI)
 			.defaultOptions(VertexAiGeminiChatOptions.builder()
 				.model("DEFAULT_MODEL")
-				.frequencePenalty(.25)
+				.frequencyPenalty(.25)
 				.presencePenalty(.75)
 				.build())
 			.build();

From 2082a594a40b6552b57cdbc51cae7c2112efd0f9 Mon Sep 17 00:00:00 2001
From: YunKui Lu <luyunkui95@gmail.com>
Date: Mon, 19 May 2025 20:47:41 +0800
Subject: [PATCH 159/165] feat: Add custom template support to
 KeywordMetadataEnricher

- Support the use of custom templates
- Adding builder pattern
- Adding unit tests
- Adjusted the relevant documents

Signed-off-by: YunKui Lu <luyunkui95@gmail.com>
---
 .../modules/ROOT/pages/api/etl-pipeline.adoc  |  31 +++-
 .../transformer/KeywordMetadataEnricher.java  |  91 ++++++++--
 .../KeywordMetadataEnricherTest.java          | 168 ++++++++++++++++++
 3 files changed, 273 insertions(+), 17 deletions(-)
 create mode 100644 spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java

diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc
index 58f461d457e..a4f808837ef 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/etl-pipeline.adoc
@@ -709,18 +709,26 @@ class MyKeywordEnricher {
     }
 
     List<Document> enrichDocuments(List<Document> documents) {
-        KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(this.chatModel, 5);
+        KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
+                .keywordCount(5)
+                .build();
+
+        // Or use custom templates
+        KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
+               .keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
+               .build();
+
         return enricher.apply(documents);
     }
 }
 ----
 
-==== Constructor
+==== Constructor Options
 
-The `KeywordMetadataEnricher` constructor takes two parameters:
+The `KeywordMetadataEnricher` provides two constructor options:
 
-1. `ChatModel chatModel`: The AI model used for generating keywords.
-2. `int keywordCount`: The number of keywords to extract for each document.
+1. `KeywordMetadataEnricher(ChatModel chatModel, int keywordCount)`: To use the default template and extract a specified number of keywords.
+2. `KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate)`: To use a custom template for keyword extraction.
 
 ==== Behavior
 
@@ -734,7 +742,8 @@ The `KeywordMetadataEnricher` processes documents as follows:
 
 ==== Customization
 
-The keyword extraction prompt can be customized by modifying the `KEYWORDS_TEMPLATE` constant in the class. The default template is:
+You can use the default template or customize the template through the keywordsTemplate parameter.
+The default template is:
 
 [source,java]
 ----
@@ -748,7 +757,14 @@ Where `+{context_str}+` is replaced with the document content, and `%s` is repla
 [source,java]
 ----
 ChatModel chatModel = // initialize your chat model
-KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 5);
+KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
+                .keywordCount(5)
+                .build();
+
+// Or use custom templates
+KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
+                .keywordsTemplate(new PromptTemplate("Extract 5 important keywords from the following text and separate them with commas:\n{context_str}"))
+                .build();
 
 Document doc = new Document("This is a document about artificial intelligence and its applications in modern technology.");
 
@@ -766,6 +782,7 @@ System.out.println("Extracted keywords: " + keywords);
 * The enricher adds the "excerpt_keywords" metadata field to each processed document.
 * The generated keywords are returned as a comma-separated string.
 * This enricher is particularly useful for improving document searchability and for generating tags or categories for documents.
+* In the Builder pattern, if the `keywordsTemplate` parameter is set, the `keywordCount` parameter will be ignored.
 
 === SummaryMetadataEnricher
 The `SummaryMetadataEnricher` is a `DocumentTransformer` that uses a generative AI model to create summaries for documents and add them as metadata. It can generate summaries for the current document, as well as adjacent documents (previous and next).
diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/transformer/KeywordMetadataEnricher.java b/spring-ai-model/src/main/java/org/springframework/ai/model/transformer/KeywordMetadataEnricher.java
index 838fc9fe5b8..bf9b38fca70 100644
--- a/spring-ai-model/src/main/java/org/springframework/ai/model/transformer/KeywordMetadataEnricher.java
+++ b/spring-ai-model/src/main/java/org/springframework/ai/model/transformer/KeywordMetadataEnricher.java
@@ -19,6 +19,9 @@
 import java.util.List;
 import java.util.Map;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.springframework.ai.chat.model.ChatModel;
 import org.springframework.ai.chat.prompt.Prompt;
 import org.springframework.ai.chat.prompt.PromptTemplate;
@@ -30,16 +33,19 @@
  * Keyword extractor that uses generative to extract 'excerpt_keywords' metadata field.
  *
  * @author Christian Tzolov
+ * @author YunKui Lu
  */
 public class KeywordMetadataEnricher implements DocumentTransformer {
 
+	private static final Logger logger = LoggerFactory.getLogger(KeywordMetadataEnricher.class);
+
 	public static final String CONTEXT_STR_PLACEHOLDER = "context_str";
 
 	public static final String KEYWORDS_TEMPLATE = """
 			{context_str}. Give %s unique keywords for this
 			document. Format as comma separated. Keywords: """;
 
-	private static final String EXCERPT_KEYWORDS_METADATA_KEY = "excerpt_keywords";
+	public static final String EXCERPT_KEYWORDS_METADATA_KEY = "excerpt_keywords";
 
 	/**
 	 * Model predictor
@@ -47,28 +53,93 @@ public class KeywordMetadataEnricher implements DocumentTransformer {
 	private final ChatModel chatModel;
 
 	/**
-	 * The number of keywords to extract.
+	 * The prompt template to use for keyword extraction.
 	 */
-	private final int keywordCount;
+	private final PromptTemplate keywordsTemplate;
 
+	/**
+	 * Create a new {@link KeywordMetadataEnricher} instance.
+	 * @param chatModel the model predictor to use for keyword extraction.
+	 * @param keywordCount the number of keywords to extract.
+	 */
 	public KeywordMetadataEnricher(ChatModel chatModel, int keywordCount) {
-		Assert.notNull(chatModel, "ChatModel must not be null");
-		Assert.isTrue(keywordCount >= 1, "Document count must be >= 1");
+		Assert.notNull(chatModel, "chatModel must not be null");
+		Assert.isTrue(keywordCount >= 1, "keywordCount must be >= 1");
+
+		this.chatModel = chatModel;
+		this.keywordsTemplate = new PromptTemplate(String.format(KEYWORDS_TEMPLATE, keywordCount));
+	}
+
+	/**
+	 * Create a new {@link KeywordMetadataEnricher} instance.
+	 * @param chatModel the model predictor to use for keyword extraction.
+	 * @param keywordsTemplate the prompt template to use for keyword extraction.
+	 */
+	public KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate) {
+		Assert.notNull(chatModel, "chatModel must not be null");
+		Assert.notNull(keywordsTemplate, "keywordsTemplate must not be null");
 
 		this.chatModel = chatModel;
-		this.keywordCount = keywordCount;
+		this.keywordsTemplate = keywordsTemplate;
 	}
 
 	@Override
 	public List<Document> apply(List<Document> documents) {
 		for (Document document : documents) {
-
-			var template = new PromptTemplate(String.format(KEYWORDS_TEMPLATE, this.keywordCount));
-			Prompt prompt = template.create(Map.of(CONTEXT_STR_PLACEHOLDER, document.getText()));
+			Prompt prompt = this.keywordsTemplate.create(Map.of(CONTEXT_STR_PLACEHOLDER, document.getText()));
 			String keywords = this.chatModel.call(prompt).getResult().getOutput().getText();
-			document.getMetadata().putAll(Map.of(EXCERPT_KEYWORDS_METADATA_KEY, keywords));
+			document.getMetadata().put(EXCERPT_KEYWORDS_METADATA_KEY, keywords);
 		}
 		return documents;
 	}
 
+	// Exposed for testing purposes
+	PromptTemplate getKeywordsTemplate() {
+		return this.keywordsTemplate;
+	}
+
+	public static Builder builder(ChatModel chatModel) {
+		return new Builder(chatModel);
+	}
+
+	public static class Builder {
+
+		private final ChatModel chatModel;
+
+		private int keywordCount;
+
+		private PromptTemplate keywordsTemplate;
+
+		public Builder(ChatModel chatModel) {
+			Assert.notNull(chatModel, "The chatModel must not be null");
+			this.chatModel = chatModel;
+		}
+
+		public Builder keywordCount(int keywordCount) {
+			Assert.isTrue(keywordCount >= 1, "The keywordCount must be >= 1");
+			this.keywordCount = keywordCount;
+			return this;
+		}
+
+		public Builder keywordsTemplate(PromptTemplate keywordsTemplate) {
+			Assert.notNull(keywordsTemplate, "The keywordsTemplate must not be null");
+			this.keywordsTemplate = keywordsTemplate;
+			return this;
+		}
+
+		public KeywordMetadataEnricher build() {
+			if (this.keywordsTemplate != null) {
+
+				if (this.keywordCount != 0) {
+					logger.warn("keywordCount will be ignored as keywordsTemplate is set.");
+				}
+
+				return new KeywordMetadataEnricher(this.chatModel, this.keywordsTemplate);
+			}
+
+			return new KeywordMetadataEnricher(this.chatModel, this.keywordCount);
+		}
+
+	}
+
 }
diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java b/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java
new file mode 100644
index 00000000000..a708671d405
--- /dev/null
+++ b/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java
@@ -0,0 +1,168 @@
+package org.springframework.ai.model.transformer;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.model.Generation;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.chat.prompt.PromptTemplate;
+import org.springframework.ai.document.Document;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.*;
+import static org.springframework.ai.model.transformer.KeywordMetadataEnricher.*;
+
+/**
+ * @author YunKui Lu
+ */
+@ExtendWith(MockitoExtension.class)
+class KeywordMetadataEnricherTest {
+
+	@Mock
+	private ChatModel chatModel;
+
+	@Captor
+	private ArgumentCaptor<Prompt> promptCaptor;
+
+	private final String CUSTOM_TEMPLATE = "Custom template: {context_str}";
+
+	@Test
+	void testUseWithDefaultTemplate() {
+		// 1. Prepare test data
+		// @formatter:off
+		List<Document> documents = List.of(
+				new Document("content1"),
+				new Document("content2"),
+				new Document("content3"));// @formatter:on
+		int keywordCount = 3;
+
+		// 2. Mock
+		given(chatModel.call(any(Prompt.class))).willReturn(
+				new ChatResponse(List.of(new Generation(new AssistantMessage("keyword1-1, keyword1-2, keyword1-3")))),
+				new ChatResponse(List.of(new Generation(new AssistantMessage("keyword2-1, keyword2-2, keyword2-3")))),
+				new ChatResponse(List.of(new Generation(new AssistantMessage("keyword3-1, keyword3-2, keyword3-3")))));
+
+		// 3. Create instance
+		KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, keywordCount);
+
+		// 4. Apply
+		keywordMetadataEnricher.apply(documents);
+
+		// 5. Assert
+		verify(chatModel, times(3)).call(promptCaptor.capture());
+
+		assertThat(promptCaptor.getAllValues().get(0).getUserMessage().getText())
+			.isEqualTo(getDefaultTemplatePromptText(keywordCount, "content1"));
+		assertThat(promptCaptor.getAllValues().get(1).getUserMessage().getText())
+			.isEqualTo(getDefaultTemplatePromptText(keywordCount, "content2"));
+		assertThat(promptCaptor.getAllValues().get(2).getUserMessage().getText())
+			.isEqualTo(getDefaultTemplatePromptText(keywordCount, "content3"));
+
+		assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY,
+				"keyword1-1, keyword1-2, keyword1-3");
+		assertThat(documents.get(1).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY,
+				"keyword2-1, keyword2-2, keyword2-3");
+		assertThat(documents.get(2).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY,
+				"keyword3-1, keyword3-2, keyword3-3");
+	}
+
+	@Test
+	void testUseCustomTemplate() {
+		// 1. Prepare test data
+		// @formatter:off
+		List<Document> documents = List.of(
+				new Document("content1"),
+				new Document("content2"),
+				new Document("content3"));// @formatter:on
+		PromptTemplate promptTemplate = new PromptTemplate(CUSTOM_TEMPLATE);
+
+		// 2. Mock
+		given(chatModel.call(any(Prompt.class))).willReturn(
+				new ChatResponse(List.of(new Generation(new AssistantMessage("keyword1-1, keyword1-2, keyword1-3")))),
+				new ChatResponse(List.of(new Generation(new AssistantMessage("keyword2-1, keyword2-2, keyword2-3")))),
+				new ChatResponse(List.of(new Generation(new AssistantMessage("keyword3-1, keyword3-2, keyword3-3")))));
+
+		// 3. Create instance
+		KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(this.chatModel, promptTemplate);
+
+		// 4. Apply
+		keywordMetadataEnricher.apply(documents);
+
+		// 5. Assert
+		verify(chatModel, times(documents.size())).call(promptCaptor.capture());
+
+		assertThat(promptCaptor.getAllValues().get(0).getUserMessage().getText())
+			.isEqualTo("Custom template: content1");
+		assertThat(promptCaptor.getAllValues().get(1).getUserMessage().getText())
+			.isEqualTo("Custom template: content2");
+		assertThat(promptCaptor.getAllValues().get(2).getUserMessage().getText())
+			.isEqualTo("Custom template: content3");
+
+		assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY,
+				"keyword1-1, keyword1-2, keyword1-3");
+		assertThat(documents.get(1).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY,
+				"keyword2-1, keyword2-2, keyword2-3");
+		assertThat(documents.get(2).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY,
+				"keyword3-1, keyword3-2, keyword3-3");
+	}
+
+	@Test
+	void testConstructorThrowsException() {
+		assertThrows(IllegalArgumentException.class, () -> new KeywordMetadataEnricher(null, 3),
+				"chatModel must not be null");
+
+		assertThrows(IllegalArgumentException.class, () -> new KeywordMetadataEnricher(chatModel, 0),
+				"keywordCount must be >= 1");
+
+		assertThrows(IllegalArgumentException.class, () -> new KeywordMetadataEnricher(chatModel, null),
+				"keywordsTemplate must not be null");
+	}
+
+	@Test
+	void testBuilderThrowsException() {
+		assertThrows(IllegalArgumentException.class, () -> KeywordMetadataEnricher.builder(null),
+				"The chatModel must not be null");
+
+		Builder builder = builder(chatModel);
+		assertThrows(IllegalArgumentException.class, () -> builder.keywordCount(0), "The keywordCount must be >= 1");
+
+		assertThrows(IllegalArgumentException.class, () -> builder.keywordsTemplate(null),
+				"The keywordsTemplate must not be null");
+	}
+
+	@Test
+	void testBuilderWithKeywordCount() {
+		int keywordCount = 3;
+		KeywordMetadataEnricher enricher = builder(chatModel).keywordCount(keywordCount).build();
+
+		assertThat(enricher.getKeywordsTemplate().getTemplate())
+			.isEqualTo(String.format(KEYWORDS_TEMPLATE, keywordCount));
+	}
+
+	@Test
+	void testBuilderWithKeywordsTemplate() {
+		PromptTemplate template = new PromptTemplate(CUSTOM_TEMPLATE);
+		KeywordMetadataEnricher enricher = builder(chatModel).keywordsTemplate(template).build();
+
+		assertThat(enricher).extracting("chatModel", "keywordsTemplate").containsExactly(chatModel, template);
+	}
+
+	private String getDefaultTemplatePromptText(int keywordCount, String documentContent) {
+		PromptTemplate promptTemplate = new PromptTemplate(String.format(KEYWORDS_TEMPLATE, keywordCount));
+		Prompt prompt = promptTemplate.create(Map.of(CONTEXT_STR_PLACEHOLDER, documentContent));
+		return prompt.getContents();
+	}
+
+}

From 53ec00733032b09ddc4dcc34a572140429f85e93 Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Mon, 28 Jul 2025 12:57:26 -0400
Subject: [PATCH 160/165] Release version 1.0.1

---
 advisors/spring-ai-advisors-vector-store/pom.xml                | 2 +-
 .../common/spring-ai-autoconfigure-retry/pom.xml                | 2 +-
 .../mcp/spring-ai-autoconfigure-mcp-client/pom.xml              | 2 +-
 .../mcp/spring-ai-autoconfigure-mcp-server/pom.xml              | 2 +-
 .../client/spring-ai-autoconfigure-model-chat-client/pom.xml    | 2 +-
 .../pom.xml                                                     | 2 +-
 .../pom.xml                                                     | 2 +-
 .../pom.xml                                                     | 2 +-
 .../memory/spring-ai-autoconfigure-model-chat-memory/pom.xml    | 2 +-
 .../spring-ai-autoconfigure-model-chat-observation/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-model-embedding-observation/pom.xml | 2 +-
 .../spring-ai-autoconfigure-model-image-observation/pom.xml     | 2 +-
 .../models/spring-ai-autoconfigure-model-anthropic/pom.xml      | 2 +-
 .../models/spring-ai-autoconfigure-model-azure-openai/pom.xml   | 2 +-
 .../models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml     | 2 +-
 .../models/spring-ai-autoconfigure-model-deepseek/pom.xml       | 2 +-
 .../models/spring-ai-autoconfigure-model-huggingface/pom.xml    | 2 +-
 .../models/spring-ai-autoconfigure-model-minimax/pom.xml        | 2 +-
 .../models/spring-ai-autoconfigure-model-mistral-ai/pom.xml     | 2 +-
 .../models/spring-ai-autoconfigure-model-oci-genai/pom.xml      | 2 +-
 .../models/spring-ai-autoconfigure-model-ollama/pom.xml         | 2 +-
 .../models/spring-ai-autoconfigure-model-openai/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-model-postgresml-embedding/pom.xml  | 2 +-
 .../models/spring-ai-autoconfigure-model-stability-ai/pom.xml   | 2 +-
 .../models/spring-ai-autoconfigure-model-transformers/pom.xml   | 2 +-
 .../models/spring-ai-autoconfigure-model-vertex-ai/pom.xml      | 2 +-
 .../models/spring-ai-autoconfigure-model-zhipuai/pom.xml        | 2 +-
 .../models/tool/spring-ai-autoconfigure-model-tool/pom.xml      | 2 +-
 .../pom.xml                                                     | 2 +-
 .../spring-ai-autoconfigure-vector-store-azure/pom.xml          | 2 +-
 .../spring-ai-autoconfigure-vector-store-cassandra/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-vector-store-chroma/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-couchbase/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml  | 2 +-
 .../spring-ai-autoconfigure-vector-store-gemfire/pom.xml        | 2 +-
 .../spring-ai-autoconfigure-vector-store-mariadb/pom.xml        | 2 +-
 .../spring-ai-autoconfigure-vector-store-milvus/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml  | 2 +-
 .../spring-ai-autoconfigure-vector-store-neo4j/pom.xml          | 2 +-
 .../spring-ai-autoconfigure-vector-store-observation/pom.xml    | 2 +-
 .../spring-ai-autoconfigure-vector-store-opensearch/pom.xml     | 2 +-
 .../spring-ai-autoconfigure-vector-store-oracle/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-pgvector/pom.xml       | 2 +-
 .../spring-ai-autoconfigure-vector-store-pinecone/pom.xml       | 2 +-
 .../spring-ai-autoconfigure-vector-store-qdrant/pom.xml         | 2 +-
 .../spring-ai-autoconfigure-vector-store-redis/pom.xml          | 2 +-
 .../spring-ai-autoconfigure-vector-store-typesense/pom.xml      | 2 +-
 .../spring-ai-autoconfigure-vector-store-weaviate/pom.xml       | 2 +-
 document-readers/jsoup-reader/pom.xml                           | 2 +-
 document-readers/markdown-reader/pom.xml                        | 2 +-
 document-readers/pdf-reader/pom.xml                             | 2 +-
 document-readers/tika-reader/pom.xml                            | 2 +-
 mcp/common/pom.xml                                              | 2 +-
 .../spring-ai-model-chat-memory-repository-cassandra/pom.xml    | 2 +-
 .../spring-ai-model-chat-memory-repository-jdbc/pom.xml         | 2 +-
 .../spring-ai-model-chat-memory-repository-neo4j/pom.xml        | 2 +-
 models/spring-ai-anthropic/pom.xml                              | 2 +-
 models/spring-ai-azure-openai/pom.xml                           | 2 +-
 models/spring-ai-bedrock-converse/pom.xml                       | 2 +-
 models/spring-ai-bedrock/pom.xml                                | 2 +-
 models/spring-ai-deepseek/pom.xml                               | 2 +-
 models/spring-ai-huggingface/pom.xml                            | 2 +-
 models/spring-ai-minimax/pom.xml                                | 2 +-
 models/spring-ai-mistral-ai/pom.xml                             | 2 +-
 models/spring-ai-oci-genai/pom.xml                              | 2 +-
 models/spring-ai-ollama/pom.xml                                 | 2 +-
 models/spring-ai-openai/pom.xml                                 | 2 +-
 models/spring-ai-postgresml/pom.xml                             | 2 +-
 models/spring-ai-stability-ai/pom.xml                           | 2 +-
 models/spring-ai-transformers/pom.xml                           | 2 +-
 models/spring-ai-vertex-ai-embedding/pom.xml                    | 2 +-
 models/spring-ai-vertex-ai-gemini/pom.xml                       | 2 +-
 models/spring-ai-zhipuai/pom.xml                                | 2 +-
 pom.xml                                                         | 2 +-
 spring-ai-bom/pom.xml                                           | 2 +-
 spring-ai-client-chat/pom.xml                                   | 2 +-
 spring-ai-commons/pom.xml                                       | 2 +-
 spring-ai-docs/pom.xml                                          | 2 +-
 spring-ai-integration-tests/pom.xml                             | 2 +-
 spring-ai-model/pom.xml                                         | 2 +-
 spring-ai-rag/pom.xml                                           | 2 +-
 spring-ai-retry/pom.xml                                         | 2 +-
 spring-ai-spring-boot-docker-compose/pom.xml                    | 2 +-
 .../spring-ai-starter-mcp-client-webflux/pom.xml                | 2 +-
 .../spring-ai-starter-mcp-client/pom.xml                        | 2 +-
 .../spring-ai-starter-mcp-server-webflux/pom.xml                | 2 +-
 .../spring-ai-starter-mcp-server-webmvc/pom.xml                 | 2 +-
 .../spring-ai-starter-mcp-server/pom.xml                        | 2 +-
 .../spring-ai-starter-model-anthropic/pom.xml                   | 2 +-
 .../spring-ai-starter-model-azure-openai/pom.xml                | 2 +-
 .../spring-ai-starter-model-bedrock-converse/pom.xml            | 2 +-
 .../spring-ai-starter-model-bedrock/pom.xml                     | 2 +-
 .../pom.xml                                                     | 2 +-
 .../spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml | 2 +-
 .../pom.xml                                                     | 2 +-
 .../spring-ai-starter-model-chat-memory/pom.xml                 | 2 +-
 .../spring-ai-starter-model-deepseek/pom.xml                    | 2 +-
 .../spring-ai-starter-model-huggingface/pom.xml                 | 2 +-
 .../spring-ai-starter-model-minimax/pom.xml                     | 2 +-
 .../spring-ai-starter-model-mistral-ai/pom.xml                  | 2 +-
 .../spring-ai-starter-model-oci-genai/pom.xml                   | 2 +-
 .../spring-ai-starter-model-ollama/pom.xml                      | 2 +-
 .../spring-ai-starter-model-openai/pom.xml                      | 2 +-
 .../spring-ai-starter-model-postgresml-embedding/pom.xml        | 2 +-
 .../spring-ai-starter-model-stability-ai/pom.xml                | 2 +-
 .../spring-ai-starter-model-transformers/pom.xml                | 2 +-
 .../spring-ai-starter-model-vertex-ai-embedding/pom.xml         | 2 +-
 .../spring-ai-starter-model-vertex-ai-gemini/pom.xml            | 2 +-
 .../spring-ai-starter-model-zhipuai/pom.xml                     | 2 +-
 .../spring-ai-starter-vector-store-aws-opensearch/pom.xml       | 2 +-
 .../spring-ai-starter-vector-store-azure-cosmos-db/pom.xml      | 2 +-
 .../spring-ai-starter-vector-store-azure/pom.xml                | 2 +-
 .../spring-ai-starter-vector-store-cassandra/pom.xml            | 2 +-
 .../spring-ai-starter-vector-store-chroma/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-couchbase/pom.xml            | 2 +-
 .../spring-ai-starter-vector-store-elasticsearch/pom.xml        | 2 +-
 .../spring-ai-starter-vector-store-gemfire/pom.xml              | 2 +-
 .../spring-ai-starter-vector-store-mariadb/pom.xml              | 2 +-
 .../spring-ai-starter-vector-store-milvus/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-mongodb-atlas/pom.xml        | 2 +-
 .../spring-ai-starter-vector-store-neo4j/pom.xml                | 2 +-
 .../spring-ai-starter-vector-store-opensearch/pom.xml           | 2 +-
 .../spring-ai-starter-vector-store-oracle/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-pgvector/pom.xml             | 2 +-
 .../spring-ai-starter-vector-store-pinecone/pom.xml             | 2 +-
 .../spring-ai-starter-vector-store-qdrant/pom.xml               | 2 +-
 .../spring-ai-starter-vector-store-redis/pom.xml                | 2 +-
 .../spring-ai-starter-vector-store-typesense/pom.xml            | 2 +-
 .../spring-ai-starter-vector-store-weaviate/pom.xml             | 2 +-
 spring-ai-spring-boot-testcontainers/pom.xml                    | 2 +-
 spring-ai-spring-cloud-bindings/pom.xml                         | 2 +-
 spring-ai-template-st/pom.xml                                   | 2 +-
 spring-ai-test/pom.xml                                          | 2 +-
 spring-ai-vector-store/pom.xml                                  | 2 +-
 vector-stores/spring-ai-azure-cosmos-db-store/pom.xml           | 2 +-
 vector-stores/spring-ai-azure-store/pom.xml                     | 2 +-
 vector-stores/spring-ai-cassandra-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-chroma-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-coherence-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-couchbase-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-elasticsearch-store/pom.xml             | 2 +-
 vector-stores/spring-ai-gemfire-store/pom.xml                   | 2 +-
 vector-stores/spring-ai-hanadb-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-mariadb-store/pom.xml                   | 2 +-
 vector-stores/spring-ai-milvus-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-mongodb-atlas-store/pom.xml             | 2 +-
 vector-stores/spring-ai-neo4j-store/pom.xml                     | 2 +-
 vector-stores/spring-ai-opensearch-store/pom.xml                | 2 +-
 vector-stores/spring-ai-oracle-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-pgvector-store/pom.xml                  | 2 +-
 vector-stores/spring-ai-pinecone-store/pom.xml                  | 2 +-
 vector-stores/spring-ai-qdrant-store/pom.xml                    | 2 +-
 vector-stores/spring-ai-redis-store/pom.xml                     | 2 +-
 vector-stores/spring-ai-typesense-store/pom.xml                 | 2 +-
 vector-stores/spring-ai-weaviate-store/pom.xml                  | 2 +-
 155 files changed, 155 insertions(+), 155 deletions(-)

diff --git a/advisors/spring-ai-advisors-vector-store/pom.xml b/advisors/spring-ai-advisors-vector-store/pom.xml
index f094c9df5ba..2dfeb371814 100644
--- a/advisors/spring-ai-advisors-vector-store/pom.xml
+++ b/advisors/spring-ai-advisors-vector-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-advisors-vector-store</artifactId>
diff --git a/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml b/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml
index ab878da5e05..9225b8a72d7 100644
--- a/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml
+++ b/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-retry</artifactId>
diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
index 7787504d4bf..4b12482593d 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-mcp-client</artifactId>
diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
index 7020ff827c2..2b68c6865fa 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-mcp-server</artifactId>
diff --git a/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
index 93780283062..c0247ef2823 100644
--- a/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
+++ b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml
index cdab80a7697..e0a9922ee30 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory-repository-cassandra</artifactId>
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml
index 84491eedaed..d2e3775607a 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory-repository-jdbc</artifactId>
diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml
index e584475c9b5..1bf93225513 100644
--- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml
+++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory-repository-neo4j</artifactId>
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
index ea73c148a8a..e36aeb75243 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
index d760a6df5c0..52401de913f 100644
--- a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
index 50455e01ef0..0225298c933 100644
--- a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
index 2eb5cc8e3e3..ff3399b584a 100644
--- a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
index 6c71b0a1309..8080090020d 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-anthropic</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
index 78d8ad35f11..6c8fe28124f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-azure-openai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
index 6a737d63508..1b690fefefb 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-bedrock-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml
index 5fc55ff2107..0ec957d7f20 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-deepseek</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
index 15b57fc893a..41f7a90e4b6 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-huggingface</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
index 18215041058..f548f96496e 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-minimax</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
index e300f07a5fd..a6ffde6b9c1 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-mistral-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
index d193038ac19..4b74cf7108a 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-oci-genai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
index dbebc20c2b7..b175d2d871f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-ollama</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml
index b60a19c411d..a4fa72a3a81 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-openai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
index 412b343ffae..845301f7098 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-postgresml-embedding</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
index 46f5aae607e..a9939e7d781 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-stability-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
index 446d5d28119..bb3c172237b 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-transformers</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
index 6d21e817c1e..88ab7bce36b 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-vertex-ai</artifactId>
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
index 5b52d6972d5..fdefbe7a143 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-zhipuai</artifactId>
diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
index 5eba0822b0e..a3dca67af36 100644
--- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml
index 974c4cdf40e..e5774a39ad3 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure-cosmos-db/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-azure-cosmos-db</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml
index d34dde6b534..ab74d5b3538 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-azure/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-azure</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml
index 87dd31ded66..162d323b055 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-cassandra</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
index e3da8946946..e89831b6f74 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-chroma</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml
index 0d55fd165ac..835b0774c26 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-couchbase</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml
index 99795e91067..28cf44d2a9e 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-elasticsearch</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml
index 302e9050b49..a04af88003f 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-gemfire</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml
index 9668b56a9f4..5fab5c1327d 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-mariadb</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml
index d73b264bcd8..edabe5be8d0 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-milvus</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml
index 2effc4bc27b..0f6436d6925 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-mongodb-atlas</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml
index 77ecfa0db84..fb23f0ac17a 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-neo4j</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml
index 7172731631f..3105a0b20ad 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-observation/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-observation</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml
index df3001f492e..5d61b93ac19 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-opensearch</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml
index 575ee6fbab7..030c840147d 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-oracle</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml
index e78c3e0a375..272e4a3d83c 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-pgvector</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml
index b615539d453..33ad3a1b7ab 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-pinecone</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml
index 21c90d36614..a85e7a58e92 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-qdrant</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml
index 7db21f5218d..1fade4cc79c 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-redis</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml
index 7bd3cf81edc..aeba47067ea 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-typesense</artifactId>
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml
index 3433194d1a4..f937b2c6265 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-autoconfigure-vector-store-weaviate</artifactId>
diff --git a/document-readers/jsoup-reader/pom.xml b/document-readers/jsoup-reader/pom.xml
index 07bb1ed8bca..eef2696438d 100644
--- a/document-readers/jsoup-reader/pom.xml
+++ b/document-readers/jsoup-reader/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 
diff --git a/document-readers/markdown-reader/pom.xml b/document-readers/markdown-reader/pom.xml
index c12ef0525ad..53b8eeac867 100644
--- a/document-readers/markdown-reader/pom.xml
+++ b/document-readers/markdown-reader/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-markdown-document-reader</artifactId>
diff --git a/document-readers/pdf-reader/pom.xml b/document-readers/pdf-reader/pom.xml
index 49798e78040..ab32df9a8fc 100644
--- a/document-readers/pdf-reader/pom.xml
+++ b/document-readers/pdf-reader/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-pdf-document-reader</artifactId>
diff --git a/document-readers/tika-reader/pom.xml b/document-readers/tika-reader/pom.xml
index b0e1cc7df42..2383e5a4037 100644
--- a/document-readers/tika-reader/pom.xml
+++ b/document-readers/tika-reader/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-tika-document-reader</artifactId>
diff --git a/mcp/common/pom.xml b/mcp/common/pom.xml
index 37f816f5302..171327cb4a2 100644
--- a/mcp/common/pom.xml
+++ b/mcp/common/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-mcp</artifactId>
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml
index 16a009839ca..41f80a61e3e 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
index cf2d0bd6825..76c3d329254 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../../pom.xml</relativePath>
 	</parent>
 
diff --git a/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml
index eed467274cc..49b2c331511 100644
--- a/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml
+++ b/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../../pom.xml</relativePath>
     </parent>
 
diff --git a/models/spring-ai-anthropic/pom.xml b/models/spring-ai-anthropic/pom.xml
index 8519252fa9e..5daf5754815 100644
--- a/models/spring-ai-anthropic/pom.xml
+++ b/models/spring-ai-anthropic/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-anthropic</artifactId>
diff --git a/models/spring-ai-azure-openai/pom.xml b/models/spring-ai-azure-openai/pom.xml
index c063ad02058..1cfe9d4a261 100644
--- a/models/spring-ai-azure-openai/pom.xml
+++ b/models/spring-ai-azure-openai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-azure-openai</artifactId>
diff --git a/models/spring-ai-bedrock-converse/pom.xml b/models/spring-ai-bedrock-converse/pom.xml
index f1e003c666b..c6d6ce5b7e1 100644
--- a/models/spring-ai-bedrock-converse/pom.xml
+++ b/models/spring-ai-bedrock-converse/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-bedrock-converse</artifactId>
diff --git a/models/spring-ai-bedrock/pom.xml b/models/spring-ai-bedrock/pom.xml
index 7acbde4dd28..89acbea3dcc 100644
--- a/models/spring-ai-bedrock/pom.xml
+++ b/models/spring-ai-bedrock/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-bedrock</artifactId>
diff --git a/models/spring-ai-deepseek/pom.xml b/models/spring-ai-deepseek/pom.xml
index bdbdbd7aa11..9534a3895d1 100644
--- a/models/spring-ai-deepseek/pom.xml
+++ b/models/spring-ai-deepseek/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-deepseek</artifactId>
diff --git a/models/spring-ai-huggingface/pom.xml b/models/spring-ai-huggingface/pom.xml
index 31f96931bcd..f798bb5886c 100644
--- a/models/spring-ai-huggingface/pom.xml
+++ b/models/spring-ai-huggingface/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-huggingface</artifactId>
diff --git a/models/spring-ai-minimax/pom.xml b/models/spring-ai-minimax/pom.xml
index 38d0c7c9833..9ea0720a9a0 100644
--- a/models/spring-ai-minimax/pom.xml
+++ b/models/spring-ai-minimax/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-minimax</artifactId>
diff --git a/models/spring-ai-mistral-ai/pom.xml b/models/spring-ai-mistral-ai/pom.xml
index fdb78e81784..31abe411b7f 100644
--- a/models/spring-ai-mistral-ai/pom.xml
+++ b/models/spring-ai-mistral-ai/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-mistral-ai</artifactId>
diff --git a/models/spring-ai-oci-genai/pom.xml b/models/spring-ai-oci-genai/pom.xml
index 0fe98c41dc6..08f187e26d6 100644
--- a/models/spring-ai-oci-genai/pom.xml
+++ b/models/spring-ai-oci-genai/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-oci-genai</artifactId>
diff --git a/models/spring-ai-ollama/pom.xml b/models/spring-ai-ollama/pom.xml
index fb01a7c79b2..509902490b7 100644
--- a/models/spring-ai-ollama/pom.xml
+++ b/models/spring-ai-ollama/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/models/spring-ai-openai/pom.xml b/models/spring-ai-openai/pom.xml
index d4418bee614..e1988938c35 100644
--- a/models/spring-ai-openai/pom.xml
+++ b/models/spring-ai-openai/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-openai</artifactId>
diff --git a/models/spring-ai-postgresml/pom.xml b/models/spring-ai-postgresml/pom.xml
index f5a4f65b268..05e9b8b3d74 100644
--- a/models/spring-ai-postgresml/pom.xml
+++ b/models/spring-ai-postgresml/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-postgresml</artifactId>
diff --git a/models/spring-ai-stability-ai/pom.xml b/models/spring-ai-stability-ai/pom.xml
index 71fbe1734ce..aee7068b2e9 100644
--- a/models/spring-ai-stability-ai/pom.xml
+++ b/models/spring-ai-stability-ai/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-stability-ai</artifactId>
diff --git a/models/spring-ai-transformers/pom.xml b/models/spring-ai-transformers/pom.xml
index 894c83e057f..307168757a0 100644
--- a/models/spring-ai-transformers/pom.xml
+++ b/models/spring-ai-transformers/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-transformers</artifactId>
diff --git a/models/spring-ai-vertex-ai-embedding/pom.xml b/models/spring-ai-vertex-ai-embedding/pom.xml
index 17c8acd5ab9..23c99afa628 100644
--- a/models/spring-ai-vertex-ai-embedding/pom.xml
+++ b/models/spring-ai-vertex-ai-embedding/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-vertex-ai-embedding</artifactId>
diff --git a/models/spring-ai-vertex-ai-gemini/pom.xml b/models/spring-ai-vertex-ai-gemini/pom.xml
index 7bcdfdfd3d1..1ed05665121 100644
--- a/models/spring-ai-vertex-ai-gemini/pom.xml
+++ b/models/spring-ai-vertex-ai-gemini/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-vertex-ai-gemini</artifactId>
diff --git a/models/spring-ai-zhipuai/pom.xml b/models/spring-ai-zhipuai/pom.xml
index 6a42df08f6d..351861074b2 100644
--- a/models/spring-ai-zhipuai/pom.xml
+++ b/models/spring-ai-zhipuai/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-zhipuai</artifactId>
diff --git a/pom.xml b/pom.xml
index b027a0fb7f9..6830c0d5541 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
 
 	<groupId>org.springframework.ai</groupId>
 	<artifactId>spring-ai-parent</artifactId>
-	<version>1.0.1-SNAPSHOT</version>
+	<version>1.0.1</version>
 
 	<packaging>pom</packaging>
 	<url>https://github.com/spring-projects/spring-ai</url>
diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml
index b93c11b924d..19e06e5bc6b 100644
--- a/spring-ai-bom/pom.xml
+++ b/spring-ai-bom/pom.xml
@@ -21,7 +21,7 @@
 
 	<groupId>org.springframework.ai</groupId>
     <artifactId>spring-ai-bom</artifactId>
-	<version>1.0.1-SNAPSHOT</version>
+	<version>1.0.1</version>
 
     <packaging>pom</packaging>
 
diff --git a/spring-ai-client-chat/pom.xml b/spring-ai-client-chat/pom.xml
index 47c39410654..40ff36ffdd0 100644
--- a/spring-ai-client-chat/pom.xml
+++ b/spring-ai-client-chat/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-client-chat</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-commons/pom.xml b/spring-ai-commons/pom.xml
index a1f324c63a0..095805d27f7 100644
--- a/spring-ai-commons/pom.xml
+++ b/spring-ai-commons/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-commons</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-docs/pom.xml b/spring-ai-docs/pom.xml
index d464f6d1169..09a7e40b14b 100644
--- a/spring-ai-docs/pom.xml
+++ b/spring-ai-docs/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
     <artifactId>spring-ai-docs</artifactId>
     <name>Spring AI Docs</name>
diff --git a/spring-ai-integration-tests/pom.xml b/spring-ai-integration-tests/pom.xml
index 54dbd7cea92..52f94bff769 100644
--- a/spring-ai-integration-tests/pom.xml
+++ b/spring-ai-integration-tests/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-integration-tests</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-model/pom.xml b/spring-ai-model/pom.xml
index ebd7be65fb1..4eaa24df522 100644
--- a/spring-ai-model/pom.xml
+++ b/spring-ai-model/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-model</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-rag/pom.xml b/spring-ai-rag/pom.xml
index 66b34ff4767..6e32e916dd0 100644
--- a/spring-ai-rag/pom.xml
+++ b/spring-ai-rag/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-rag</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-retry/pom.xml b/spring-ai-retry/pom.xml
index fe7ecc4d1c8..1994519726c 100644
--- a/spring-ai-retry/pom.xml
+++ b/spring-ai-retry/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-retry</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml
index b59661caec9..fe0b93afc93 100644
--- a/spring-ai-spring-boot-docker-compose/pom.xml
+++ b/spring-ai-spring-boot-docker-compose/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>spring-ai-spring-boot-docker-compose</artifactId>
     <packaging>jar</packaging>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml
index f7c5796be8a..80a2a98ff8e 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client-webflux/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml
index 6645f0f2970..779faf4de27 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-client/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-client</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml
index b94f718c1a6..edc98e5c6fb 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webflux/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml
index 16fa5fcd0ff..f0bd34c908f 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server-webmvc/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml
index 976bea2ba80..e83d8fa89e3 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-mcp-server/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-mcp-server</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml
index 1124debfeca..f3acdad5d04 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-anthropic/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-anthropic</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml
index 082bd729b5e..c9f48ec4310 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-azure-openai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-azure-openai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml
index e5aec20e6b4..aa7985d0254 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock-converse/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-bedrock-converse</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml
index 55867e4e989..b9c9c6548ee 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-bedrock/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-bedrock</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml
index dbdea050be5..c4ac3a1ddf6 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-cassandra/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory-repository-cassandra</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml
index d5c6521a5c0..d8f5c4a53fe 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-jdbc/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml
index 3b29bc040a2..f844c0c243d 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory-repository-neo4j/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory-repository-neo4j</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml
index 0f424b2de37..fdaa2794308 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-chat-memory/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-chat-memory</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml
index 2945d8480c7..22f9a1721d9 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-deepseek/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-deepseek</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml
index 78a3f0ddde9..f9894695501 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-huggingface/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-huggingface</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml
index dd4246f4ced..56d4afbc4d3 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-minimax/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-minimax</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml
index 8d7280848b1..400b42210ce 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-mistral-ai/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-mistral-ai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml
index ea4b1042a27..3a8c5a061f3 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-oci-genai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-oci-genai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml
index 78f17a61987..768c96a938e 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-ollama/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-ollama</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml
index bb6d4718ebd..a3f007f7ecf 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-openai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-openai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml
index 9614ca2bf52..c5f34d349dc 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-postgresml-embedding/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-postgresml-embedding</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml
index 2235ec7bcf0..8a0f00f3c58 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-stability-ai/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-stability-ai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml
index 64808f1617c..05511528eb5 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-transformers/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-transformers</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml
index f957def38c9..20e94dc51c9 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-embedding/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-vertex-ai-embedding</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml
index c2a22f1b169..f2c46d396c6 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-vertex-ai-gemini/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-model-vertex-ai-gemini</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml
index 615ae8b8500..58bf6a203d9 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-model-zhipuai/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-model-zhipuai</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml
index 8fee162d08e..e6d5be7cf3a 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-aws-opensearch/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-aws-opensearch</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml
index dc27d15123f..5f888d2ea3b 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure-cosmos-db/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-azure-cosmos-db</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml
index ae9664081cd..e04b5bad81b 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-azure/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-azure</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml
index 62574e2be66..f73f2670636 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-cassandra/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-cassandra</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml
index 597a569805b..88f9bb5a9a6 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-chroma/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-chroma</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml
index 2809074b13b..faca42c084b 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-couchbase/pom.xml
@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-couchbase</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml
index 5f13b557f5a..fb23f4330a2 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-elasticsearch/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml
index 51e1f7fd2fd..2b79b3ae2c7 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-gemfire/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-gemfire</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml
index 9b74ddaaf54..35ba88049a4 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mariadb/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-mariadb</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml
index 0ab3ef296ed..8ed90c20593 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-milvus/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml
index e194c653054..1b9051ba885 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-mongodb-atlas/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-mongodb-atlas</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml
index 43bcfd5661e..f72e2bd21b5 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-neo4j/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-neo4j</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml
index 39ad862d61f..a4284b51813 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-opensearch/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-starter-vector-store-opensearch</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml
index b2aba9a5907..68d2a386a48 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-oracle/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 	<artifactId>spring-ai-starter-vector-store-oracle</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml
index bdb5d6cbab1..d1eb9000ba3 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pgvector/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 	<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml
index 45da181b91e..cb75e43bdc8 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-pinecone/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-pinecone</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml
index 73efd8dfafa..e4edcd0f4c9 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-qdrant/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml
index afa9086a94b..df904ed6c27 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-redis/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-redis</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml
index 164d9a11cb0..440b2ecc6e4 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-typesense/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-typesense</artifactId>
diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml
index 2f12d1324a4..4d1fd4a7e27 100644
--- a/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml
+++ b/spring-ai-spring-boot-starters/spring-ai-starter-vector-store-weaviate/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-starter-vector-store-weaviate</artifactId>
diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml
index 276e9f73802..8c021ef2f1b 100644
--- a/spring-ai-spring-boot-testcontainers/pom.xml
+++ b/spring-ai-spring-boot-testcontainers/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-spring-boot-testcontainers</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-spring-cloud-bindings/pom.xml b/spring-ai-spring-cloud-bindings/pom.xml
index c685077f50e..45fb0aca230 100644
--- a/spring-ai-spring-cloud-bindings/pom.xml
+++ b/spring-ai-spring-cloud-bindings/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>spring-ai-spring-cloud-bindings</artifactId>
     <packaging>jar</packaging>
diff --git a/spring-ai-template-st/pom.xml b/spring-ai-template-st/pom.xml
index e0a63b66be3..005c4ae37ba 100644
--- a/spring-ai-template-st/pom.xml
+++ b/spring-ai-template-st/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
     </parent>
     <artifactId>spring-ai-template-st</artifactId>
     <packaging>jar</packaging>
diff --git a/spring-ai-test/pom.xml b/spring-ai-test/pom.xml
index c056c4a5d93..4f83923aafb 100644
--- a/spring-ai-test/pom.xml
+++ b/spring-ai-test/pom.xml
@@ -20,7 +20,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-test</artifactId>
 	<packaging>jar</packaging>
diff --git a/spring-ai-vector-store/pom.xml b/spring-ai-vector-store/pom.xml
index 2810dfd9648..6a1bcf20ca3 100644
--- a/spring-ai-vector-store/pom.xml
+++ b/spring-ai-vector-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 	</parent>
 	<artifactId>spring-ai-vector-store</artifactId>
 	<packaging>jar</packaging>
diff --git a/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml b/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml
index 4fc168b5125..6606c9ff326 100644
--- a/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml
+++ b/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-azure-cosmos-db-store</artifactId>
diff --git a/vector-stores/spring-ai-azure-store/pom.xml b/vector-stores/spring-ai-azure-store/pom.xml
index 5114ea5f003..049df0ac993 100644
--- a/vector-stores/spring-ai-azure-store/pom.xml
+++ b/vector-stores/spring-ai-azure-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-azure-store</artifactId>
diff --git a/vector-stores/spring-ai-cassandra-store/pom.xml b/vector-stores/spring-ai-cassandra-store/pom.xml
index b1c9e025dd0..195af3cc3a0 100644
--- a/vector-stores/spring-ai-cassandra-store/pom.xml
+++ b/vector-stores/spring-ai-cassandra-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-cassandra-store</artifactId>
diff --git a/vector-stores/spring-ai-chroma-store/pom.xml b/vector-stores/spring-ai-chroma-store/pom.xml
index afb332a980d..11c4fe88467 100644
--- a/vector-stores/spring-ai-chroma-store/pom.xml
+++ b/vector-stores/spring-ai-chroma-store/pom.xml
@@ -22,7 +22,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-chroma-store</artifactId>
diff --git a/vector-stores/spring-ai-coherence-store/pom.xml b/vector-stores/spring-ai-coherence-store/pom.xml
index 57497041f73..703e1103c4f 100644
--- a/vector-stores/spring-ai-coherence-store/pom.xml
+++ b/vector-stores/spring-ai-coherence-store/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-coherence-store</artifactId>
diff --git a/vector-stores/spring-ai-couchbase-store/pom.xml b/vector-stores/spring-ai-couchbase-store/pom.xml
index 9a7d99c1ddf..74c21361ab1 100644
--- a/vector-stores/spring-ai-couchbase-store/pom.xml
+++ b/vector-stores/spring-ai-couchbase-store/pom.xml
@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-couchbase-store</artifactId>
diff --git a/vector-stores/spring-ai-elasticsearch-store/pom.xml b/vector-stores/spring-ai-elasticsearch-store/pom.xml
index c58f3f55ea6..d7c607c5420 100644
--- a/vector-stores/spring-ai-elasticsearch-store/pom.xml
+++ b/vector-stores/spring-ai-elasticsearch-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-elasticsearch-store</artifactId>
diff --git a/vector-stores/spring-ai-gemfire-store/pom.xml b/vector-stores/spring-ai-gemfire-store/pom.xml
index 6bd940b5659..41a53a4801b 100644
--- a/vector-stores/spring-ai-gemfire-store/pom.xml
+++ b/vector-stores/spring-ai-gemfire-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-gemfire-store</artifactId>
diff --git a/vector-stores/spring-ai-hanadb-store/pom.xml b/vector-stores/spring-ai-hanadb-store/pom.xml
index 231af602805..27d0d5dca1d 100644
--- a/vector-stores/spring-ai-hanadb-store/pom.xml
+++ b/vector-stores/spring-ai-hanadb-store/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-hanadb-store</artifactId>
diff --git a/vector-stores/spring-ai-mariadb-store/pom.xml b/vector-stores/spring-ai-mariadb-store/pom.xml
index 0fba20cf107..99a80e5eab3 100644
--- a/vector-stores/spring-ai-mariadb-store/pom.xml
+++ b/vector-stores/spring-ai-mariadb-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-mariadb-store</artifactId>
diff --git a/vector-stores/spring-ai-milvus-store/pom.xml b/vector-stores/spring-ai-milvus-store/pom.xml
index 27c44fc6735..7e3a615a403 100644
--- a/vector-stores/spring-ai-milvus-store/pom.xml
+++ b/vector-stores/spring-ai-milvus-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-milvus-store</artifactId>
diff --git a/vector-stores/spring-ai-mongodb-atlas-store/pom.xml b/vector-stores/spring-ai-mongodb-atlas-store/pom.xml
index 872d91fc098..163b41cf21d 100644
--- a/vector-stores/spring-ai-mongodb-atlas-store/pom.xml
+++ b/vector-stores/spring-ai-mongodb-atlas-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-mongodb-atlas-store</artifactId>
diff --git a/vector-stores/spring-ai-neo4j-store/pom.xml b/vector-stores/spring-ai-neo4j-store/pom.xml
index 6c6ea72e5bb..e0fdcb0b3f9 100644
--- a/vector-stores/spring-ai-neo4j-store/pom.xml
+++ b/vector-stores/spring-ai-neo4j-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-neo4j-store</artifactId>
diff --git a/vector-stores/spring-ai-opensearch-store/pom.xml b/vector-stores/spring-ai-opensearch-store/pom.xml
index e7cd8a7dd60..b8aa56a4069 100644
--- a/vector-stores/spring-ai-opensearch-store/pom.xml
+++ b/vector-stores/spring-ai-opensearch-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-opensearch-store</artifactId>
diff --git a/vector-stores/spring-ai-oracle-store/pom.xml b/vector-stores/spring-ai-oracle-store/pom.xml
index 505abeac46f..eb3e541d5ff 100644
--- a/vector-stores/spring-ai-oracle-store/pom.xml
+++ b/vector-stores/spring-ai-oracle-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-oracle-store</artifactId>
diff --git a/vector-stores/spring-ai-pgvector-store/pom.xml b/vector-stores/spring-ai-pgvector-store/pom.xml
index 4adc860b230..72737b45164 100644
--- a/vector-stores/spring-ai-pgvector-store/pom.xml
+++ b/vector-stores/spring-ai-pgvector-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-pgvector-store</artifactId>
diff --git a/vector-stores/spring-ai-pinecone-store/pom.xml b/vector-stores/spring-ai-pinecone-store/pom.xml
index 9ebfa7210f3..930f0520288 100644
--- a/vector-stores/spring-ai-pinecone-store/pom.xml
+++ b/vector-stores/spring-ai-pinecone-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-pinecone-store</artifactId>
diff --git a/vector-stores/spring-ai-qdrant-store/pom.xml b/vector-stores/spring-ai-qdrant-store/pom.xml
index a83247b103a..caa7656eb07 100644
--- a/vector-stores/spring-ai-qdrant-store/pom.xml
+++ b/vector-stores/spring-ai-qdrant-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-qdrant-store</artifactId>
diff --git a/vector-stores/spring-ai-redis-store/pom.xml b/vector-stores/spring-ai-redis-store/pom.xml
index c856bc85903..b4f1651453a 100644
--- a/vector-stores/spring-ai-redis-store/pom.xml
+++ b/vector-stores/spring-ai-redis-store/pom.xml
@@ -21,7 +21,7 @@
 	<parent>
 		<groupId>org.springframework.ai</groupId>
 		<artifactId>spring-ai-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.0.1</version>
 		<relativePath>../../pom.xml</relativePath>
 	</parent>
 	<artifactId>spring-ai-redis-store</artifactId>
diff --git a/vector-stores/spring-ai-typesense-store/pom.xml b/vector-stores/spring-ai-typesense-store/pom.xml
index d5c90214494..39d5666b053 100644
--- a/vector-stores/spring-ai-typesense-store/pom.xml
+++ b/vector-stores/spring-ai-typesense-store/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
diff --git a/vector-stores/spring-ai-weaviate-store/pom.xml b/vector-stores/spring-ai-weaviate-store/pom.xml
index 1f7aa0434b8..0f5357f25f3 100644
--- a/vector-stores/spring-ai-weaviate-store/pom.xml
+++ b/vector-stores/spring-ai-weaviate-store/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <groupId>org.springframework.ai</groupId>
         <artifactId>spring-ai-parent</artifactId>
-        <version>1.0.1-SNAPSHOT</version>
+        <version>1.0.1</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
     <artifactId>spring-ai-weaviate-store</artifactId>

From e7255e9c4f70074e65d7ed41f48051517ecf47b9 Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Mon, 28 Jul 2025 17:08:13 -0400
Subject: [PATCH 161/165] Fix maven central publishing config

---
 .../workflows/new-maven-central-release.yml   |  4 +-
 pom.xml                                       | 75 ++++++++++---------
 2 files changed, 40 insertions(+), 39 deletions(-)

diff --git a/.github/workflows/new-maven-central-release.yml b/.github/workflows/new-maven-central-release.yml
index 6ef61226514..bc23b508342 100644
--- a/.github/workflows/new-maven-central-release.yml
+++ b/.github/workflows/new-maven-central-release.yml
@@ -27,8 +27,8 @@ jobs:
 
       - name: Release to Sonatype OSSRH
         env:
-          SONATYPE_USER: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
-          SONATYPE_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
+          SONATYPE_USER: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
+          SONATYPE_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
           MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
         run: |
           ./mvnw -B clean install -DskipTests
diff --git a/pom.xml b/pom.xml
index 6830c0d5541..7483e928134 100644
--- a/pom.xml
+++ b/pom.xml
@@ -864,43 +864,44 @@
 			</distributionManagement>
 		</profile>
 		<profile>
-			<id>sonatype</id>
-			<properties>
-				<maven.test.skip>true</maven.test.skip>
-			</properties>
-			<build>
-				<plugins>
-					<plugin>
-						<groupId>org.apache.maven.plugins</groupId>
-						<artifactId>maven-gpg-plugin</artifactId>
-						<executions>
-							<execution>
-								<id>sign-artifacts</id>
-								<phase>verify</phase>
-								<goals>
-									<goal>sign</goal>
-								</goals>
-							</execution>
-						</executions>
-						<configuration>
-							<!-- Passphrase consumed from MAVEN_GPG_PASSPHRASE environment variable. -->
-						</configuration>
-					</plugin>
-					<plugin>
-						<groupId>org.sonatype.plugins</groupId>
-						<artifactId>nexus-staging-maven-plugin</artifactId>
-						<version>1.7.0</version>
-						<extensions>true</extensions>
-						<configuration>
-							<serverId>sonatype-new</serverId>
-							<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
-							<autoReleaseAfterClose>true</autoReleaseAfterClose>
-							<keepStagingRepositoryOnFailure>true</keepStagingRepositoryOnFailure>
-						</configuration>
-					</plugin>
-				</plugins>
-			</build>
-		</profile>
+	    <id>sonatype</id>
+	    <properties>
+	        <maven.test.skip>true</maven.test.skip>
+	    </properties>
+	    <build>
+	        <plugins>
+	            <plugin>
+	                <groupId>org.sonatype.central</groupId>
+	                <artifactId>central-publishing-maven-plugin</artifactId>
+	                <version>0.8.0</version>
+	                <extensions>true</extensions>
+	                <configuration>
+	                    <publishingServerId>central</publishingServerId>
+	                    <autoPublish>true</autoPublish>
+	                </configuration>
+	            </plugin>
+	            <plugin>
+	                <groupId>org.apache.maven.plugins</groupId>
+	                <artifactId>maven-gpg-plugin</artifactId>
+	                <version>3.2.5</version>
+	                <executions>
+	                    <execution>
+	                        <id>sign-artifacts</id>
+	                        <phase>verify</phase>
+	                        <goals>
+	                            <goal>sign</goal>
+	                        </goals>
+	                    </execution>
+	                </executions>
+	                <configuration>
+	                    <!-- Passphrase consumed from MAVEN_GPG_PASSPHRASE environment variable. -->
+	                </configuration>
+	            </plugin>
+	        </plugins>
+	    </build>
+	</profile>
+
+
 	</profiles>
 
 	<dependencyManagement>

From fac4b03fe0a3e22c304913e7f686629737f34831 Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Mon, 28 Jul 2025 17:23:25 -0400
Subject: [PATCH 162/165] update settings.xml with maven central info

---
 settings.xml | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/settings.xml b/settings.xml
index 8e881c33164..2530423cd39 100644
--- a/settings.xml
+++ b/settings.xml
@@ -36,11 +36,12 @@
             <password>${env.ARTIFACTORY_PASSWORD}</password>
         </server>
 
-		<server>
-			<id>sonatype-new</id>
-			<username>${env.SONATYPE_USER}</username>
-			<password>${env.SONATYPE_PASSWORD}</password>
-		</server>
+      <server>
+        <id>central</id>
+        <username>${env.CENTRAL_TOKEN_USERNAME}</username>
+  			<password>${env.CENTRAL_TOKEN_PASSWORD}</password>
+      </server>
+
     </servers>
 
 </settings>

From ca064bf706908fa621bb40f7158db9780ab47f99 Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Mon, 28 Jul 2025 18:24:37 -0400
Subject: [PATCH 163/165] update spring-ai-bom pom.xml for maven central
 publishing

---
 spring-ai-bom/pom.xml | 75 +++++++++++++++++++++----------------------
 1 file changed, 37 insertions(+), 38 deletions(-)

diff --git a/spring-ai-bom/pom.xml b/spring-ai-bom/pom.xml
index 19e06e5bc6b..693dcbe638f 100644
--- a/spring-ai-bom/pom.xml
+++ b/spring-ai-bom/pom.xml
@@ -1328,44 +1328,43 @@
 				</repository>
 			</distributionManagement>
 		</profile>
-		<profile>
-			<id>sonatype</id>
-			<properties>
-				<maven.test.skip>true</maven.test.skip>
-			</properties>
-			<build>
-				<plugins>
-					<plugin>
-						<groupId>org.apache.maven.plugins</groupId>
-						<artifactId>maven-gpg-plugin</artifactId>
-						<executions>
-							<execution>
-								<id>sign-artifacts</id>
-								<phase>verify</phase>
-								<goals>
-									<goal>sign</goal>
-								</goals>
-							</execution>
-						</executions>
-						<configuration>
-							<!-- Passphrase consumed from MAVEN_GPG_PASSPHRASE environment variable. -->
-						</configuration>
-					</plugin>
-					<plugin>
-						<groupId>org.sonatype.plugins</groupId>
-						<artifactId>nexus-staging-maven-plugin</artifactId>
-						<version>1.7.0</version>
-						<extensions>true</extensions>
-						<configuration>
-							<serverId>sonatype-new</serverId>
-							<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
-							<autoReleaseAfterClose>true</autoReleaseAfterClose>
-							<keepStagingRepositoryOnFailure>true</keepStagingRepositoryOnFailure>
-						</configuration>
-					</plugin>
-				</plugins>
-			</build>
-		</profile>
+    <profile>
+	    <id>sonatype</id>
+	    <properties>
+	        <maven.test.skip>true</maven.test.skip>
+	    </properties>
+	    <build>
+	        <plugins>
+	            <plugin>
+	                <groupId>org.sonatype.central</groupId>
+	                <artifactId>central-publishing-maven-plugin</artifactId>
+	                <version>0.8.0</version>
+	                <extensions>true</extensions>
+	                <configuration>
+	                    <publishingServerId>central</publishingServerId>
+	                    <autoPublish>true</autoPublish>
+	                </configuration>
+	            </plugin>
+	            <plugin>
+	                <groupId>org.apache.maven.plugins</groupId>
+	                <artifactId>maven-gpg-plugin</artifactId>
+	                <version>3.2.5</version>
+	                <executions>
+	                    <execution>
+	                        <id>sign-artifacts</id>
+	                        <phase>verify</phase>
+	                        <goals>
+	                            <goal>sign</goal>
+	                        </goals>
+	                    </execution>
+	                </executions>
+	                <configuration>
+	                    <!-- Passphrase consumed from MAVEN_GPG_PASSPHRASE environment variable. -->
+	                </configuration>
+	            </plugin>
+	        </plugins>
+	    </build>
+	   </profile>
 	</profiles>
 
 </project>

From 35f81cc560a655fdaf9abe5fc4a7291d1e098b11 Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Tue, 29 Jul 2025 12:31:17 -0400
Subject: [PATCH 164/165] tweak maven central pluging - remove specific version

---
 pom.xml | 34 ++++++++++++++++------------------
 1 file changed, 16 insertions(+), 18 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7483e928134..9707bd36669 100644
--- a/pom.xml
+++ b/pom.xml
@@ -870,33 +870,31 @@
 	    </properties>
 	    <build>
 	        <plugins>
+						<plugin>
+								<groupId>org.apache.maven.plugins</groupId>
+								<artifactId>maven-gpg-plugin</artifactId>
+								<executions>
+										<execution>
+												<id>sign-artifacts</id>
+												<phase>verify</phase>
+												<goals>
+														<goal>sign</goal>
+												</goals>
+										</execution>
+								</executions>
+								<configuration>
+										<!-- Passphrase consumed from MAVEN_GPG_PASSPHRASE environment variable. -->
+								</configuration>
+						</plugin>
 	            <plugin>
 	                <groupId>org.sonatype.central</groupId>
 	                <artifactId>central-publishing-maven-plugin</artifactId>
-	                <version>0.8.0</version>
 	                <extensions>true</extensions>
 	                <configuration>
 	                    <publishingServerId>central</publishingServerId>
 	                    <autoPublish>true</autoPublish>
 	                </configuration>
 	            </plugin>
-	            <plugin>
-	                <groupId>org.apache.maven.plugins</groupId>
-	                <artifactId>maven-gpg-plugin</artifactId>
-	                <version>3.2.5</version>
-	                <executions>
-	                    <execution>
-	                        <id>sign-artifacts</id>
-	                        <phase>verify</phase>
-	                        <goals>
-	                            <goal>sign</goal>
-	                        </goals>
-	                    </execution>
-	                </executions>
-	                <configuration>
-	                    <!-- Passphrase consumed from MAVEN_GPG_PASSPHRASE environment variable. -->
-	                </configuration>
-	            </plugin>
 	        </plugins>
 	    </build>
 	</profile>

From 87e6c89e5671a18f30f87f821e2316bfb5062409 Mon Sep 17 00:00:00 2001
From: Mark Pollack <mark.pollack@broadcom.com>
Date: Tue, 29 Jul 2025 13:48:35 -0400
Subject: [PATCH 165/165] one more time for good measure

---
 .github/workflows/new-maven-central-release.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/new-maven-central-release.yml b/.github/workflows/new-maven-central-release.yml
index bc23b508342..bd5519b6c9d 100644
--- a/.github/workflows/new-maven-central-release.yml
+++ b/.github/workflows/new-maven-central-release.yml
@@ -27,8 +27,8 @@ jobs:
 
       - name: Release to Sonatype OSSRH
         env:
-          SONATYPE_USER: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
-          SONATYPE_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
+          CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
+          CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
           MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
         run: |
           ./mvnw -B clean install -DskipTests
