
Structured AI Responses in Spring Boot with Kotlin
Master structured responses in Spring AI with Kotlin to get type-safe, predictable outputs from AI models. Learn how to map AI responses to your domain objects and handle them efficiently in your Spring Boot applications.
Working with AI responses can be tricky when you need specific data structures in your application. In this guide, we'll explore how to get structured, type-safe responses from AI models using Spring AI and Kotlin.
Key Takeaways
- Learn how to map AI responses to type-safe Kotlin data classes
- Understand how to use ParameterizedTypeReference for type safety
- Implement reusable AI response structures
- Handle structured responses efficiently in Spring Boot
Prerequisites
- Basic knowledge of Kotlin and Spring Boot
- Familiarity with Spring AI basics (see our getting started guide)
- OpenAI API key
Understanding Structured Responses
When working with AI models, getting free-form text responses isn't always ideal. Sometimes you need:
- Specific data formats
- Type-safe responses
- Consistent response structures
- Easy integration with your domain model
Let's see how to achieve this using Spring AI.
Implementation
First, let's create a data class for our structured response:
data class InstructionResponse(
val response: String,
val reasoning: String
)
Now, let's implement the AI client that returns structured responses:
class StructuredAIClient(
apiKey: String,
modelName: String = "gpt-4"
) {
private val chatModel = OpenAiChatModel(
OpenAiApi(apiKey),
OpenAiChatOptions().apply {
model = modelName
temperature = 0.5 // Lower temperature for more consistent outputs
}
)
private val client = ChatClient.create(chatModel)
fun getStructuredResponse(input: String): InstructionResponse {
val prompt = client.prompt("Before giving your final response, add some reasoning on how you got to that response.")
return prompt.call().entity(
object : ParameterizedTypeReference<InstructionResponse>() {}
)
}
}
Using the Structured Response Client
Here's how to use the client in your application:
@Service
class AIService(
@Value("\${openai.api-key}")
private val apiKey: String
) {
private val aiClient = StructuredAIClient(apiKey)
fun getInstructions(query: String): InstructionResponse {
return aiClient.getStructuredResponse(query)
}
}
And in your controller:
@RestController
@RequestMapping("/api/ai")
class AIController(private val aiService: AIService) {
@PostMapping("/instructions")
fun getInstructions(@RequestBody query: String): InstructionResponse {
return aiService.getInstructions(query)
}
}
Benefits of Structured Responses
- Type Safety: By mapping responses to Kotlin data classes, you get compile-time type checking.
- Predictable Format: Your application always receives data in a known structure.
- Easy Integration: Structured responses can be easily integrated with your domain model.
- Better Error Handling: Type-safe responses make it easier to handle errors and edge cases.
Advanced Usage: Generic Type Parameters
For more flexibility, you can create a generic method that handles different response types:
class StructuredAIClient(apiKey: String) {
// ... previous initialization code ...
fun <T> getTypedResponse(
input: String,
typeReference: ParameterizedTypeReference<T>
): T {
val prompt = client.prompt(input)
return prompt.call().entity(typeReference)
}
}
This allows you to handle multiple response types:
data class ProductSuggestion(
val name: String,
val price: Double,
val features: List<String>
)
// Usage
val suggestion = aiClient.getTypedResponse(
"Suggest a product under $100",
object : ParameterizedTypeReference<ProductSuggestion>() {}
)
Best Practices
- Clear Prompts: Always provide clear instructions in your prompts about the expected response format.
- Validation: Add validation to ensure the AI response matches your expected structure.
- Error Handling: Implement proper error handling for cases where the AI response doesn't match your data class.
- Temperature Setting: Use lower temperature values (0.1-0.5) for more consistent structured outputs.
Common Issues and Solutions
Issue: Inconsistent JSON Format
Sometimes the AI might return slightly different JSON structures. Handle this by:
@JsonIgnoreProperties(ignoreUnknown = true)
data class InstructionResponse(
val response: String,
val reasoning: String
)
Issue: Parsing Errors
Implement proper error handling:
try {
return prompt.call().entity(typeReference)
} catch (e: Exception) {
logger.error("Failed to parse AI response", e)
throw AIParsingException("Failed to parse AI response: ${e.message}")
}
Conclusion
Structured responses from AI models help maintain type safety and predictability in your Kotlin applications. By using Spring AI's features and Kotlin's type system, you can create robust AI-powered applications that are easier to maintain and debug.
Remember to check our OpenAI integration guide for more details on setting up Spring AI with OpenAI.
Accompanying Code
You can find the code accompanying this blog post on github.