Spring AI 騷操作:讓大模型乖乖聽話,直接返回 Java 對象!
還在為解析大模型返回的非結(jié)構(gòu)化文本而頭疼嗎?還在用一堆 if-else 和正則表達式做著繁瑣的字符串切割嗎?現(xiàn)在,有了 Spring AI 的 結(jié)構(gòu)化輸出轉(zhuǎn)換器(Structured Output Converter),這一切都將成為過去式!
這個神器能將大語言模型(LLM)返回的原始文本,精準地轉(zhuǎn)換為你想要的任何結(jié)構(gòu)化數(shù)據(jù),無論是 JSON、XML 還是一個具體的 Java 對象。對于需要穩(wěn)定、可靠地處理 AI 輸出的應用程序來說,這簡直是天降福音!
工作原理:AI 如何秒懂你的數(shù)據(jù)需求?
結(jié)構(gòu)化輸出轉(zhuǎn)換器的魔法主要分兩步:
? 調(diào)用前 - “約法三章”: 在你向大模型發(fā)送請求前,轉(zhuǎn)換器會自動在你的提示詞(Prompt)末尾附加上清晰的格式指令。這就像告訴一位廚師:“我點的這道菜,請務必做成五角星形狀。” 它明確告知模型,你的答案必須符合某種格式。
? 調(diào)用后 - “格式轉(zhuǎn)換”: 模型返回文本后,轉(zhuǎn)換器會立即施展“變形術(shù)”,將文本精準地轉(zhuǎn)換成你指定的 Java 類實例,比如 List、Map 或者自定義的 Bean。
圖1: 結(jié)構(gòu)化輸出工作流:先“約法三章”,再“格式轉(zhuǎn)換”
溫馨提示:結(jié)構(gòu)化輸出轉(zhuǎn)換器會盡最大努力(Best-Effort)完成任務。但 AI 模型偶爾也會“調(diào)皮”,不完全按指令辦事。因此,在代碼中加入驗證和異常處理機制,是保證程序健壯性的好習慣。
深入探秘:揭開 StructuredOutputConverter 的神秘面紗
想知道這背后的技術(shù)實現(xiàn)嗎?核心在于 StructuredOutputConverter<T> 接口,它像一個多面手,同時扮演兩個角色:
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {
}? FormatProvider 接口:負責生成“格式說明書”,告訴 AI 模型應該如何輸出。
? Spring Converter<String, T> 接口:負責將模型返回的字符串,轉(zhuǎn)換為你想要的目標類型 T。
public interface FormatProvider {
String getFormat();
}Spring AI 已經(jīng)內(nèi)置了多種開箱即用的轉(zhuǎn)換器,滿足你不同場景的需求:
? BeanOutputConverter:將輸出轉(zhuǎn)換為 Java Bean 對象,最常用的神器!
? MapOutputConverter:將輸出轉(zhuǎn)換為 Map 結(jié)構(gòu)。
? ListOutputConverter:將輸出轉(zhuǎn)換為 List 結(jié)構(gòu)。
? AbstractConversionServiceOutputConverter:提供通用轉(zhuǎn)換服務的基類。
? AbstractMessageOutputConverter:支持 Spring AI Message 格式的轉(zhuǎn)換。
它們的家族關(guān)系如下圖所示:
圖2: StructuredOutputConverter 核心類圖
現(xiàn)在,我們再來梳理一遍完整的工作流程:
1. 生成格式指令:FormatProvider 會生成類似下面的指令,并附加到你的提示詞中。這相當于給 AI 劃重點,告訴它必須按這個 JSON Schema 來回答。
Your response should be in JSON format.
The data structure for the JSON should match this Java class: java.util.HashMap
Do not include any explanations, only provide a RFC8259 compliant JSON response...通常,我們會用 PromptTemplate 來優(yōu)雅地實現(xiàn)這一點:
StructuredOutputConverter outputConverter = ...
String userInputTemplate = """
... 你的業(yè)務提示詞 ....
{format}
"""; // 預留一個 {format} 占位符
Prompt prompt = new Prompt(
new PromptTemplate(
userInputTemplate,
Map.of(..., "format", outputConverter.getFormat()) // 將格式指令填入占位符
).createMessage());2. 轉(zhuǎn)換輸出:Converter 將模型返回的 JSON 字符串,反序列化為你指定的 Java 對象。
整個過程無縫銜接,對開發(fā)者極其友好。
圖3: 提示詞與轉(zhuǎn)換器協(xié)同工作流程
上手實戰(zhàn):三行代碼,讓 AI 輸出“言聽計從”
官方文檔提供了豐富的示例,我們來看幾個最經(jīng)典的。
1. BeanOutputConverter:將 AI 輸出直接轉(zhuǎn)換為自定義 Java 類。
// 1. 定義一個簡單的 Java Record
record ActorsFilms(String actor, List<String> movies) {}
// 2. 一行代碼調(diào)用并完成轉(zhuǎn)換!
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate 5 movies for Tom Hanks.")
.call()
.entity(ActorsFilms.class); // 指定目標類型,搞定!處理復雜的泛型列表也同樣簡單,只需使用 ParameterizedTypeReference:
// 輕松轉(zhuǎn)換為對象列表
List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});2. MapOutputConverter:將輸出轉(zhuǎn)換為 Map。
Map<String, Object> result = ChatClient.create(chatModel).prompt()
.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>>() {});3. ListOutputConverter:將輸出轉(zhuǎn)換為字符串列表。
List<String> flavors = ChatClient.create(chatModel).prompt()
.user(u -> u.text("List five {subject}")
.param("subject", "ice cream flavors"))
.call()
.entity(new ListOutputConverter(new DefaultConversionService()));兼容性王者:主流模型全支持
根據(jù)官方文檔,以下主流 AI 模型均已通過測試,完美支持 List、Map 和 Bean 結(jié)構(gòu)化輸出:
AI 模型 | 示例測試代碼 |
OpenAI | OpenAiChatModelIT |
Anthropic Claude 3 | AnthropicChatModelIT.java |
Azure OpenAI | AzureOpenAiChatModelIT.java |
Mistral AI | MistralAiChatModelIT.java |
Ollama | OllamaChatModelIT.java |
Vertex AI Gemini | VertexAiGeminiChatModelIT.java |
更棒的是,許多模型提供了內(nèi)置 JSON 模式,這讓結(jié)構(gòu)化輸出的可靠性更上一層樓。啟用后,模型會保證輸出嚴格符合 JSON 格式。
? OpenAI: 提供 JSON_OBJECT 響應格式。
? Azure OpenAI: 設(shè)置 { "type": "json_object" } 啟用 JSON 模式。
? Ollama: 提供 format: 'json' 選項。
? Mistral AI: 設(shè)置 responseFormat: { "type": "json_object" } 啟用 JSON 模式。
實戰(zhàn)演練:AI 變身“旅游規(guī)劃師”,自動生成旅行報告
下面,我們來構(gòu)建一個實用的功能:讓 AI 為用戶生成一份包含標題和建議列表的旅游報告。
1. 引入 JSON Schema 依賴:這是讓轉(zhuǎn)換器理解 Java 類結(jié)構(gòu)的關(guān)鍵。
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>4.38.0</version>
</dependency>2. 定義旅游報告類:使用 Java Record,代碼簡潔優(yōu)雅。
record SightSeeingReport(String title, List<String> suggestions) {
}3. 編寫業(yè)務代碼:在原有的 ChatClient 基礎(chǔ)上,只需一行 .entity() 即可實現(xiàn)結(jié)構(gòu)化輸出。
/**
* 為用戶生成一份專屬的旅游報告
* @param message 用戶的問題
* @param chatId 會話ID
* @return 結(jié)構(gòu)化的旅游報告對象
*/
public SightSeeingReport doChatWithReport(String message, String chatId) {
SightSeeingReport sightSeeingReport = chatClient
.prompt()
// 強化系統(tǒng)提示,要求生成包含標題和建議的報告
.system(SYSTEM_PROMPT + "每次對話后都要生成旅游結(jié)果,標題為{用戶名}的旅游報告,內(nèi)容為建議列表")
.user(message)
.advisors(spec ->
spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)
)
.call()
// 魔法發(fā)生的地方!直接將結(jié)果轉(zhuǎn)換為 SightSeeingReport 對象
.entity(SightSeeingReport.class);
log.info("AI 生成的旅游報告: {}", sightSeeingReport);
return sightSeeingReport;
}4. 編寫單元測試:
@Resource
private App app;@Test
void doChatWithReport() {
String chatId = UUID.randomUUID().toString();
String message = "你好,我想去北京旅游,請幫我規(guī)劃一下";
App.SightSeeingReport sightSeeingReport = app.doChatWithReport(message, chatId);
Assertions.assertNotNull(sightSeeingReport);
}運行測試,通過 Debug 我們可以清晰地看到,框架自動將我們的 SightSeeingReport 類轉(zhuǎn)換為了詳細的 JSON Schema,并添加到了提示詞中,指導 AI 生成了我們期望的 JSON 格式數(shù)據(jù),并最終成功轉(zhuǎn)換為了 SightSeeingReport 對象實例。整個過程如絲般順滑!
圖4: Debug 模式下查看自動生成的格式指令和最終轉(zhuǎn)換的對象
格式指令的完整內(nèi)容如下,我們發(fā)現(xiàn)對象被轉(zhuǎn)換為了 JSON Schema 描述語言:
Your response should be in JSON format.
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
Do not include markdown code blocks in your response.
Remove the ```json markdown from the output.
Here is the JSON Schema instance your output must adhere to:
```{
"$schema" : "https://json-schema.org/draft/2020-12/schema",
"type" : "object",
"properties" : {
"suggestions" : {
"type" : "array",
"items" : {
"type" : "string"
}
},
"title" : {
"type" : "string"
}
},
"additionalProperties" : false
}```AI 生成的內(nèi)容如圖,是 JSON 格式文本:
圖片

轉(zhuǎn)換器成功將 JSON 文本轉(zhuǎn)換為了對象:
圖片
高手秘籍:用好結(jié)構(gòu)化輸出的四大心法
1. 指令清晰:給模型的格式指導越清晰、越具體越好。
2. 驗證兜底:務必實現(xiàn)輸出驗證和異常處理邏輯,應對 AI 的“小脾氣”。
3. 選對模型:優(yōu)先選擇官方支持或提供內(nèi)置 JSON 模式的模型,可靠性更高。
4. 巧用泛型:處理復雜數(shù)據(jù)結(jié)構(gòu)(如 List<Map<String, MyObject>>)時,ParameterizedTypeReference 是你的得力助手。
Spring AI 的結(jié)構(gòu)化輸出功能,極大地簡化了與大模型交互的復雜度,讓開發(fā)者能更專注于業(yè)務邏輯,而不是繁瑣的數(shù)據(jù)解析。它就像一座橋梁,無縫連接了 AI 的創(chuàng)造力與 Java 應用的嚴謹性。
趕快在你的項目中試試吧!體驗一下讓 AI “言聽計從”的快感。



































