response = springAIEmbedding.embedForResponse(request);
+```
+
+### Configuration Mapping
+
+```java
+// ADK config automatically mapped to Spring AI ChatOptions
+LlmRequest request = LlmRequest.builder()
+ .contents(contents)
+ .config(GenerateContentConfig.builder()
+ .temperature(0.7f)
+ .maxOutputTokens(1000)
+ .topP(0.9f)
+ .build())
+ .build();
+```
+
+## Supported Providers
+
+The library works with any Spring AI provider:
+
+### Tested Providers
+
+1. **OpenAI** (`spring-ai-openai`)
+ - Models: GPT-4o, GPT-4o-mini, GPT-3.5-turbo
+ - Features: Chat, streaming, function calling, embeddings
+
+2. **Anthropic** (`spring-ai-anthropic`)
+ - Models: Claude 3.5 Sonnet, Claude 3 Haiku
+ - Features: Chat, streaming, function calling
+ - **Note:** Requires proper function schema registration
+
+3. **Google Gemini** (`spring-ai-google-genai`)
+ - Models: Gemini 2.0 Flash, Gemini 1.5 Pro
+ - Features: Chat, streaming, function calling
+ - **Note:** Requires single system message (automatically handled)
+
+4. **Vertex AI** (`spring-ai-vertex-ai-gemini`)
+ - Models: Vertex AI Gemini models
+ - Features: Chat, streaming, function calling
+
+5. **Azure OpenAI** (`spring-ai-azure-openai`)
+ - Models: Azure-hosted OpenAI models
+ - Features: Chat, streaming, function calling
+
+6. **Ollama** (`spring-ai-ollama`)
+ - Models: Local Llama, Mistral, etc.
+ - Features: Chat, streaming
+
+### Provider-Specific Considerations
+
+#### Gemini
+- **System Messages:** Only one system message allowed - library automatically combines multiple system messages
+- **Model Names:** Use `gemini-2.0-flash`, `gemini-1.5-pro`
+- **API Key:** Requires `GOOGLE_API_KEY` environment variable
+
+#### Anthropic
+- **Function Calling:** Requires explicit schema registration using `inputSchema()` method
+- **Model Names:** Use full model names like `claude-3-5-sonnet-20241022`
+- **API Key:** Requires `ANTHROPIC_API_KEY` environment variable
+
+#### OpenAI
+- **Standard Support:** Full feature compatibility
+- **Model Names:** Use `gpt-4o-mini`, `gpt-4o`, etc.
+- **API Key:** Requires `OPENAI_API_KEY` environment variable
+
+## Auto-Configuration
+
+The library provides Spring Boot auto-configuration for seamless integration:
+
+### Configuration Properties
+
+```yaml
+adk:
+ spring-ai:
+ default-model: "gpt-4o-mini"
+ temperature: 0.7
+ max-tokens: 1000
+ top-p: 0.9
+ top-k: 40
+ auto-configuration:
+ enabled: true
+ validation:
+ enabled: true
+ fail-fast: false
+ observability:
+ enabled: true
+ metrics-enabled: true
+ include-content: false
+```
+
+### Auto-Configuration Beans
+
+The auto-configuration creates beans based on available Spring AI models:
+
+```java
+@Bean
+@ConditionalOnBean({ChatModel.class, StreamingChatModel.class})
+public SpringAI springAIWithBothModels(
+ ChatModel chatModel,
+ StreamingChatModel streamingChatModel,
+ SpringAIProperties properties) {
+ // Auto-configured SpringAI instance
+}
+
+@Bean
+@ConditionalOnBean(EmbeddingModel.class)
+public SpringAIEmbedding springAIEmbedding(
+ EmbeddingModel embeddingModel,
+ SpringAIProperties properties) {
+ // Auto-configured SpringAIEmbedding instance
+}
+```
+
+## Integration Testing
+
+The library includes comprehensive integration tests for different providers:
+
+### Test Classes
+
+1. **OpenAiApiIntegrationTest.java**
+ - Tests OpenAI integration with real API calls
+ - Covers blocking, streaming, and function calling
+
+2. **GeminiApiIntegrationTest.java**
+ - Tests Google Gemini integration with real API calls
+ - Covers blocking, streaming, and function calling
+ - Tests configuration options
+
+3. **MessageConverterTest.java**
+ - Unit tests for message conversion logic
+ - Tests system message combining for Gemini compatibility
+
+### Running Integration Tests
+
+```bash
+# Set required environment variables
+export OPENAI_API_KEY=your_key
+export GOOGLE_API_KEY=your_key
+export ANTHROPIC_API_KEY=your_key
+
+# Run specific integration test
+mvn test -Dtest=OpenAiApiIntegrationTest
+
+# Run all tests
+mvn test
+```
+
+## Error Handling
+
+The library provides comprehensive error handling through `SpringAIErrorMapper`:
+
+### Error Mapping
+- Spring AI exceptions → ADK-compatible errors
+- Provider-specific error normalization
+- Detailed error context preservation
+
+### Observability
+- Request/response logging
+- Token usage tracking
+- Error metrics collection
+- Performance monitoring
+
+## Best Practices
+
+### Model Configuration
+1. Always specify explicit model names rather than relying on defaults
+2. Use environment variables for API keys
+3. Configure appropriate timeouts for your use case
+4. Enable observability for production monitoring
+
+### Function Calling
+1. Ensure function schemas are properly defined in ADK tools
+2. Test function calling with each provider separately
+3. Handle provider-specific argument format differences
+4. Use debug logging to troubleshoot function calling issues
+
+### Performance
+1. Use streaming for long responses
+2. Implement proper backpressure handling
+3. Configure connection pooling for high-throughput scenarios
+4. Monitor token usage and costs
+
+### Error Handling
+1. Implement retry logic for transient failures
+2. Handle provider-specific error conditions
+3. Use circuit breakers for external API calls
+4. Log errors with sufficient context for debugging
+
+## Dependencies
+
+### Core Dependencies
+- Spring AI Model (`spring-ai-model`)
+- ADK Core (`google-adk`)
+- Google GenAI Types (`google-genai`)
+- RxJava3 for reactive programming
+- Jackson for JSON processing
+
+### Provider Dependencies (Test Scope)
+- `spring-ai-openai`
+- `spring-ai-anthropic`
+- `spring-ai-google-genai`
+- `spring-ai-vertex-ai-gemini`
+- `spring-ai-azure-openai`
+- `spring-ai-ollama`
+
+### Spring Boot Integration
+- `spring-boot-autoconfigure` (optional)
+- `spring-boot-configuration-processor` (optional)
+- `jakarta.validation-api` (optional)
+
+## Future Enhancements
+
+### Planned Features
+1. Enhanced provider-specific optimizations
+2. Advanced streaming aggregation
+3. Multi-modal content support
+4. Enhanced observability and metrics
+5. Performance optimization for high-throughput scenarios
+
+### Known Limitations
+1. Live connection mode not supported (returns `UnsupportedOperationException`)
+2. Some provider-specific features may not be fully supported
+3. Response schema and MIME type configuration limited
+4. Top-K parameter not directly mapped to Spring AI
+
+## Migration Guide
+
+### From Direct Spring AI Usage
+1. Replace Spring AI `ChatModel.call()` with `SpringAI.generateContent()`
+2. Update message formats from Spring AI to ADK format
+3. Configure auto-configuration properties
+4. Update dependency management to include ADK Spring AI
+
+### Version Compatibility
+- Spring AI: 1.1.0-M3+
+- Spring Boot: 3.0+
+- Java: 17+
+- ADK: 0.3.1+
+
+This library provides a robust foundation for integrating Spring AI models with the ADK framework, offering enterprise-grade features like observability, error handling, and multi-provider support while maintaining the flexibility and power of both frameworks.
\ No newline at end of file
diff --git a/contrib/spring-ai/pom.xml b/contrib/spring-ai/pom.xml
new file mode 100644
index 00000000..cd8c2a4a
--- /dev/null
+++ b/contrib/spring-ai/pom.xml
@@ -0,0 +1,247 @@
+
+
+
+ 4.0.0
+
+
+ com.google.adk
+ google-adk-parent
+ 0.3.1-SNAPSHOT
+ ../../pom.xml
+
+
+ google-adk-spring-ai
+ Agent Development Kit - Spring AI
+ Spring AI integration for the Agent Development Kit.
+
+
+ 1.1.0-M3
+ 1.21.3
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-bom
+ ${spring-ai.version}
+ pom
+ import
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+ org.testcontainers
+ testcontainers-bom
+ ${testcontainers.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-model
+
+
+ com.google.adk
+ google-adk
+ ${project.version}
+
+
+ com.google.adk
+ google-adk-dev
+ ${project.version}
+
+
+ com.google.genai
+ google-genai
+
+
+ io.modelcontextprotocol.sdk
+ mcp
+
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ io.micrometer
+ micrometer-core
+ true
+
+
+ jakarta.validation
+ jakarta.validation-api
+ true
+
+
+ org.hibernate.validator
+ hibernate-validator
+ true
+
+
+
+
+ org.springframework.ai
+ spring-ai-openai
+ test
+
+
+ org.springframework.ai
+ spring-ai-anthropic
+ test
+
+
+ org.springframework.ai
+ spring-ai-vertex-ai-gemini
+ test
+
+
+ org.springframework.ai
+ spring-ai-google-genai
+ test
+
+
+ org.springframework.ai
+ spring-ai-azure-openai
+ test
+
+
+ org.springframework.ai
+ spring-ai-ollama
+ test
+
+
+
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.springframework.boot
+ spring-boot-test
+ test
+
+
+ com.google.truth
+ truth
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ ${env.OPENAI_API_KEY}
+ ${env.ANTHROPIC_API_KEY}
+ ${env.VERTEX_AI_PROJECT_ID}
+ ${env.AZURE_OPENAI_API_KEY}
+ ${env.AZURE_OPENAI_ENDPOINT}
+
+
+
+
+
+
+
+
+ integration-tests
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ ${env.OPENAI_API_KEY}
+ ${env.ANTHROPIC_API_KEY}
+ ${env.VERTEX_AI_PROJECT_ID}
+ ${env.AZURE_OPENAI_API_KEY}
+ ${env.AZURE_OPENAI_ENDPOINT}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/contrib/spring-ai/src/main/java/com/google/adk/models/springai/ConfigMapper.java b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/ConfigMapper.java
new file mode 100644
index 00000000..2231813c
--- /dev/null
+++ b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/ConfigMapper.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * 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
+ *
+ * http://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 com.google.adk.models.springai;
+
+import com.google.genai.types.GenerateContentConfig;
+import java.util.Optional;
+import org.springframework.ai.chat.prompt.ChatOptions;
+
+/**
+ * Maps ADK GenerateContentConfig to Spring AI ChatOptions.
+ *
+ * This mapper handles the translation between ADK's GenerateContentConfig and Spring AI's
+ * ChatOptions, enabling configuration parameters like temperature, max tokens, and stop sequences
+ * to be passed through to Spring AI models.
+ */
+public class ConfigMapper {
+
+ /**
+ * Converts ADK GenerateContentConfig to Spring AI ChatOptions.
+ *
+ * @param config The ADK configuration to convert
+ * @return Spring AI ChatOptions or null if no config provided
+ */
+ public ChatOptions toSpringAiChatOptions(Optional config) {
+ if (config.isEmpty()) {
+ return null;
+ }
+
+ GenerateContentConfig contentConfig = config.get();
+ ChatOptions.Builder optionsBuilder = ChatOptions.builder();
+
+ // Map temperature (convert Float to Double)
+ contentConfig.temperature().ifPresent(temp -> optionsBuilder.temperature(temp.doubleValue()));
+
+ // Map max output tokens
+ contentConfig.maxOutputTokens().ifPresent(optionsBuilder::maxTokens);
+
+ // Map top P (convert Float to Double)
+ contentConfig.topP().ifPresent(topP -> optionsBuilder.topP(topP.doubleValue()));
+
+ // Map top K (convert Float to Integer)
+ contentConfig.topK().ifPresent(topK -> optionsBuilder.topK(topK.intValue()));
+
+ // Map stop sequences
+ contentConfig
+ .stopSequences()
+ .filter(sequences -> !sequences.isEmpty())
+ .ifPresent(optionsBuilder::stopSequences);
+
+ // Map presence penalty (if supported by Spring AI)
+ contentConfig
+ .presencePenalty()
+ .ifPresent(
+ penalty -> {
+ // Spring AI may support presence penalty through model-specific options
+ // This will be handled in provider-specific adapters
+ });
+
+ // Map frequency penalty (if supported by Spring AI)
+ contentConfig
+ .frequencyPenalty()
+ .ifPresent(
+ penalty -> {
+ // Spring AI may support frequency penalty through model-specific options
+ // This will be handled in provider-specific adapters
+ });
+
+ return optionsBuilder.build();
+ }
+
+ /**
+ * Creates default ChatOptions for cases where no ADK config is provided.
+ *
+ * @return Basic ChatOptions with reasonable defaults
+ */
+ public ChatOptions createDefaultChatOptions() {
+ return ChatOptions.builder().temperature(0.7).maxTokens(1000).build();
+ }
+
+ /**
+ * Validates that the configuration is compatible with Spring AI.
+ *
+ * @param config The ADK configuration to validate
+ * @return true if configuration is valid and supported
+ */
+ public boolean isConfigurationValid(Optional config) {
+ if (config.isEmpty()) {
+ return true; // No config is valid
+ }
+
+ GenerateContentConfig contentConfig = config.get();
+
+ // Check for unsupported features
+ if (contentConfig.responseSchema().isPresent()) {
+ // Response schema might not be supported by all Spring AI models
+ // This should be logged as a warning
+ return false;
+ }
+
+ if (contentConfig.responseMimeType().isPresent()) {
+ // Response MIME type might not be supported by all Spring AI models
+ return false;
+ }
+
+ // Check for reasonable ranges
+ if (contentConfig.temperature().isPresent()) {
+ float temp = contentConfig.temperature().get();
+ if (temp < 0.0f || temp > 2.0f) {
+ return false; // Temperature out of reasonable range
+ }
+ }
+
+ if (contentConfig.topP().isPresent()) {
+ float topP = contentConfig.topP().get();
+ if (topP < 0.0f || topP > 1.0f) {
+ return false; // topP out of valid range
+ }
+ }
+
+ if (contentConfig.topK().isPresent()) {
+ float topK = contentConfig.topK().get();
+ if (topK < 1 || topK > 64) {
+ return false; // topK out of valid range
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/contrib/spring-ai/src/main/java/com/google/adk/models/springai/EmbeddingConverter.java b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/EmbeddingConverter.java
new file mode 100644
index 00000000..2b0e8f5a
--- /dev/null
+++ b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/EmbeddingConverter.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * 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
+ *
+ * http://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 com.google.adk.models.springai;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.springframework.ai.embedding.Embedding;
+import org.springframework.ai.embedding.EmbeddingRequest;
+import org.springframework.ai.embedding.EmbeddingResponse;
+
+/**
+ * Utility class for converting between embedding formats and performing vector operations.
+ *
+ * This class provides helper methods for working with embeddings generated by Spring AI models,
+ * including format conversions and similarity calculations.
+ */
+public class EmbeddingConverter {
+
+ private EmbeddingConverter() {
+ // Utility class - prevent instantiation
+ }
+
+ /**
+ * Create an EmbeddingRequest for a single text input.
+ *
+ * @param text The text to embed
+ * @return EmbeddingRequest for the text
+ */
+ public static EmbeddingRequest createRequest(String text) {
+ return new EmbeddingRequest(List.of(text), null);
+ }
+
+ /**
+ * Create an EmbeddingRequest for multiple text inputs.
+ *
+ * @param texts The texts to embed
+ * @return EmbeddingRequest for the texts
+ */
+ public static EmbeddingRequest createRequest(List texts) {
+ return new EmbeddingRequest(texts, null);
+ }
+
+ /**
+ * Extract embedding vectors from an EmbeddingResponse.
+ *
+ * @param response The embedding response
+ * @return List of embedding vectors as float arrays
+ */
+ public static List extractEmbeddings(EmbeddingResponse response) {
+ List embeddings = new ArrayList<>();
+ for (Embedding embedding : response.getResults()) {
+ embeddings.add(embedding.getOutput());
+ }
+ return embeddings;
+ }
+
+ /**
+ * Extract the first embedding vector from an EmbeddingResponse.
+ *
+ * @param response The embedding response
+ * @return The first embedding vector, or null if no embeddings
+ */
+ public static float[] extractFirstEmbedding(EmbeddingResponse response) {
+ if (response.getResults().isEmpty()) {
+ return null;
+ }
+ return response.getResults().get(0).getOutput();
+ }
+
+ /**
+ * Calculate cosine similarity between two embedding vectors.
+ *
+ * @param embedding1 First embedding vector
+ * @param embedding2 Second embedding vector
+ * @return Cosine similarity score between -1 and 1
+ */
+ public static double cosineSimilarity(float[] embedding1, float[] embedding2) {
+ if (embedding1.length != embedding2.length) {
+ throw new IllegalArgumentException(
+ "Embedding vectors must have the same dimensions: "
+ + embedding1.length
+ + " vs "
+ + embedding2.length);
+ }
+
+ double dotProduct = 0.0;
+ double norm1 = 0.0;
+ double norm2 = 0.0;
+
+ for (int i = 0; i < embedding1.length; i++) {
+ dotProduct += embedding1[i] * embedding2[i];
+ norm1 += embedding1[i] * embedding1[i];
+ norm2 += embedding2[i] * embedding2[i];
+ }
+
+ if (norm1 == 0.0 || norm2 == 0.0) {
+ return 0.0; // Handle zero vectors
+ }
+
+ return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
+ }
+
+ /**
+ * Calculate Euclidean distance between two embedding vectors.
+ *
+ * @param embedding1 First embedding vector
+ * @param embedding2 Second embedding vector
+ * @return Euclidean distance
+ */
+ public static double euclideanDistance(float[] embedding1, float[] embedding2) {
+ if (embedding1.length != embedding2.length) {
+ throw new IllegalArgumentException(
+ "Embedding vectors must have the same dimensions: "
+ + embedding1.length
+ + " vs "
+ + embedding2.length);
+ }
+
+ double sum = 0.0;
+ for (int i = 0; i < embedding1.length; i++) {
+ double diff = embedding1[i] - embedding2[i];
+ sum += diff * diff;
+ }
+
+ return Math.sqrt(sum);
+ }
+
+ /**
+ * Normalize an embedding vector to unit length.
+ *
+ * @param embedding The embedding vector to normalize
+ * @return Normalized embedding vector
+ */
+ public static float[] normalize(float[] embedding) {
+ double norm = 0.0;
+ for (float value : embedding) {
+ norm += value * value;
+ }
+ norm = Math.sqrt(norm);
+
+ if (norm == 0.0) {
+ return Arrays.copyOf(embedding, embedding.length); // Return copy of zero vector
+ }
+
+ float[] normalized = new float[embedding.length];
+ for (int i = 0; i < embedding.length; i++) {
+ normalized[i] = (float) (embedding[i] / norm);
+ }
+
+ return normalized;
+ }
+
+ /**
+ * Find the most similar embedding from a list of candidates.
+ *
+ * @param query The query embedding
+ * @param candidates List of candidate embeddings
+ * @return Index of the most similar embedding, or -1 if no candidates
+ */
+ public static int findMostSimilar(float[] query, List candidates) {
+ if (candidates.isEmpty()) {
+ return -1;
+ }
+
+ int bestIndex = 0;
+ double bestSimilarity = cosineSimilarity(query, candidates.get(0));
+
+ for (int i = 1; i < candidates.size(); i++) {
+ double similarity = cosineSimilarity(query, candidates.get(i));
+ if (similarity > bestSimilarity) {
+ bestSimilarity = similarity;
+ bestIndex = i;
+ }
+ }
+
+ return bestIndex;
+ }
+
+ /**
+ * Calculate similarity scores between a query and all candidates.
+ *
+ * @param query The query embedding
+ * @param candidates List of candidate embeddings
+ * @return List of similarity scores
+ */
+ public static List calculateSimilarities(float[] query, List candidates) {
+ List similarities = new ArrayList<>();
+ for (float[] candidate : candidates) {
+ similarities.add(cosineSimilarity(query, candidate));
+ }
+ return similarities;
+ }
+
+ /**
+ * Convert float array to double array.
+ *
+ * @param floatArray The float array
+ * @return Equivalent double array
+ */
+ public static double[] toDoubleArray(float[] floatArray) {
+ double[] doubleArray = new double[floatArray.length];
+ for (int i = 0; i < floatArray.length; i++) {
+ doubleArray[i] = floatArray[i];
+ }
+ return doubleArray;
+ }
+
+ /**
+ * Convert double array to float array.
+ *
+ * @param doubleArray The double array
+ * @return Equivalent float array
+ */
+ public static float[] toFloatArray(double[] doubleArray) {
+ float[] floatArray = new float[doubleArray.length];
+ for (int i = 0; i < doubleArray.length; i++) {
+ floatArray[i] = (float) doubleArray[i];
+ }
+ return floatArray;
+ }
+}
diff --git a/contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConversionException.java b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConversionException.java
new file mode 100644
index 00000000..122ea86f
--- /dev/null
+++ b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConversionException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * 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
+ *
+ * http://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 com.google.adk.models.springai;
+
+/**
+ * Exception thrown when message conversion between ADK and Spring AI formats fails.
+ *
+ * This exception is thrown when there are issues converting between ADK's Content/Part format
+ * and Spring AI's Message/ChatResponse format, such as JSON parsing errors, invalid message
+ * structures, or unsupported content types.
+ */
+public class MessageConversionException extends RuntimeException {
+
+ /**
+ * Constructs a new MessageConversionException with the specified detail message.
+ *
+ * @param message the detail message
+ */
+ public MessageConversionException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new MessageConversionException with the specified detail message and cause.
+ *
+ * @param message the detail message
+ * @param cause the cause of the exception
+ */
+ public MessageConversionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs a new MessageConversionException with the specified cause.
+ *
+ * @param cause the cause of the exception
+ */
+ public MessageConversionException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a MessageConversionException for JSON parsing failures.
+ *
+ * @param context the context where the parsing failed (e.g., "tool call arguments")
+ * @param cause the underlying JSON processing exception
+ * @return a new MessageConversionException with appropriate message
+ */
+ public static MessageConversionException jsonParsingFailed(String context, Throwable cause) {
+ return new MessageConversionException(
+ String.format("Failed to parse JSON for %s", context), cause);
+ }
+
+ /**
+ * Creates a MessageConversionException for invalid message structure.
+ *
+ * @param message description of the invalid structure
+ * @return a new MessageConversionException
+ */
+ public static MessageConversionException invalidMessageStructure(String message) {
+ return new MessageConversionException("Invalid message structure: " + message);
+ }
+
+ /**
+ * Creates a MessageConversionException for unsupported content type.
+ *
+ * @param contentType the unsupported content type
+ * @return a new MessageConversionException
+ */
+ public static MessageConversionException unsupportedContentType(String contentType) {
+ return new MessageConversionException("Unsupported content type: " + contentType);
+ }
+}
diff --git a/contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConverter.java b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConverter.java
new file mode 100644
index 00000000..a287e54c
--- /dev/null
+++ b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConverter.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * 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
+ *
+ * http://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 com.google.adk.models.springai;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.adk.models.LlmRequest;
+import com.google.adk.models.LlmResponse;
+import com.google.genai.types.Content;
+import com.google.genai.types.FunctionCall;
+import com.google.genai.types.FunctionResponse;
+import com.google.genai.types.Part;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.chat.messages.ToolResponseMessage;
+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.prompt.ChatOptions;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.model.tool.ToolCallingChatOptions;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Converts between ADK and Spring AI message formats.
+ *
+ *
This converter handles the translation between ADK's Content/Part format (based on Google's
+ * genai.types) and Spring AI's Message/ChatResponse format. This is a simplified initial version
+ * that focuses on text content and basic function calling.
+ */
+public class MessageConverter {
+
+ private static final TypeReference