摸索AI(五)Spring AI定制JSON结果
Spring Aiadmin 发布于:2026-03-12 13:53:21
阅读:loading
之前也是花了巨多的时间从Github上下载了一些AI摸索的实践项目,涉及到的有文生图、文生视频、图生图、语音克隆、数字人等,它们的实践对电脑配置的依赖有一定的要求,而且实践的过程复杂程度较高,属于摸索着玩玩而已。本次摸索AI的范围则是面向对擅长领域的代码接入实践。如果你对Java代码交互的AI大模型有一些兴趣,又或者是跟我一样不知从哪里入手,或许看看我这里分享的前后实践过程与实践的案例范围,也是不错的选择。
1.基本介绍
所以,本系列教程相关的实践是在本地部署大模型,并且使用Java代码与本地的大模型进行交互。除了本地部署的大模型以为,也是可以付费接入网络上的一些付费大模型,比如Deepseek、千问等等,但是对我个人来讲,私有化的本地大模型更加有意义,毕竟可以免费的集成到企业级应用实践当中。
本次实践围绕使用Spring AI在企业级应用中的专业层面进行的实践,主要考虑在使用Spring AI调用大模型处理接口响应时,根据要求来定制结果。在接口调用处理过程中,AI返回的结果更多的是Markdown结构的,有文本和HTML标签或代码块等形式,在实际应用中,如果用于数据分析处理,则更多的是期望响应与程序解析处理契合度更高的JSON格式。
本篇文章的示例实践则是从本站拷贝了一些个人信息的文本,使用Spring AI来调用本地的一个大模型,让其从这些文本数据中提取我所指定的三个参数,并以JSON格式的结果返回,相关信息参考如下:
String content = """
站点域名:https://www.chendd.cn
博主名称:陈冬冬(本站域名不出手)
故乡:湖北省老河口市(曾经的小汉口,一个被历史遗忘的城市)
博主是一名酷(ku)比(bi)的80后老爷们,现役搬土质砖人士,也会持续的在行业内摸爬滚打。别轻易加我,一对一答疑解惑(教学)将占用我的时间,知识付费。
QQ:88911006
微信:java-dong
邮箱:88911006@qq.com
抖音:haiyangyiba
""";(内容信息)
请帮我提取出来数据,按照兼容同时存在多条数据信息的方式,将关键要素提取为json数组的格式。请严格按照这个结构的JSON对象输出:[{"name":"姓名","website":"域名","weiChat":"微信"}]。注意:输出的信息不要包含思考过程、提取说明、解答思路、数据分析、内容总结、以及Markdown代码块,等等,仅需要返回符合条件的JSON格式参数。信息如下:${content}
(内容提取要求)
{
"id": "chatcmpl-176",
"object": "chat.completion",
"created": 1773285781,
"model": "qwen3-vl:2b",
"system_fingerprint": "fp_ollama",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "[\n {\n \"name\": \"陈冬冬\",\n \"website\": \"https://www.chendd.cn\",\n \"weiChat\": \"java-dong\"\n }\n]",
"reasoning": "好的,我现在需要帮用户提取数据,按照指定的JSON数组格式输出。首先,用户给了一个信息,里面包含站点域名、博主名称、故乡、一些联系方式等。我需要仔细看看每个字段对应的关键要素。\n\n首先,用户提到的站点域名是https://www.chendd.cn,这个应该对应到website字段。然后博主名称是陈冬冬,但要注意用户说“本站域名不出手”,可能意味着这个域名是网站的,但博主名称可能需要确认。不过根据用户提供的信息,博主名称是陈冬冬,所以name应该是这个。\n\n接下来,故乡是湖北省老河口市,但用户可能希望这个作为某个字段的值,不过根据要求,关键要素包括name、website、weiChat。这里可能需要确认,但用户给的信息里有“故乡”这个字段,但可能不是关键要素,所以可能不需要包含。不过用户说“可能包含如下关键要素”,所以需要看哪些是必须的。\n\n用户给出的结构是name、website、weiChat。所以需要从提供的信息中提取这三个。现在看用户提供的信息:\n\n站点域名:https://www.chendd.cn → website\n\n博主名称:陈冬冬(本站域名不出手)→ name\n\nQQ:88911006 → 但用户说“QQ:88911006”,可能需要看是否对应到其他字段。但用户要求的三个字段中,weiChat是微信,而用户提供的信息里有微信:java-dong,所以weiChat应该是java-dong。\n\n另外,用户还提到邮箱和抖音,但这些可能不是关键要素,所以可能不需要包含。但用户说“可能包含如下关键要素”,所以需要看哪些是必须的。根据用户给的示例,关键要素是name、website、weiChat,所以其他信息如QQ、微信、邮箱、抖音等可能不需要,但用户可能希望将微信作为weiChat,而QQ可能属于其他,但用户要求的三个字段中,weiChat是微信,所以可能需要将微信的信息提取出来。\n\n现在,用户提供的信息中:\n\n- 站点域名:https://www.chendd.cn → website\n- 博主名称:陈冬冬 → name\n- 微信:java-dong → weiChat\n- QQ:88911006 → 但用户可能没有要求,所以可能不需要\n- 邮箱:88911006@qq.com → 但用户可能没有要求\n- 抖音:haiyangyiba → 但用户可能没有要求\n\n所以,根据用户的要求,需要提取name、website、weiChat。因此,name是陈冬冬,website是https://www.chendd.cn,weiChat是java-dong。\n\n但用户提到“博主名称:陈冬冬(本站域名不出手)”,这里可能需要注意,是否博主名称和站点域名有关联,但用户说“本站域名不出手”,可能意味着这个域名是网站的,但博主名称是陈冬冬,所以name应该是陈冬冬。\n\n另外,用户还提到“故乡:湖北省老河口市”,但这个可能不是关键要素,所以不需要包含在JSON中。\n\n现在,检查是否有其他信息需要提取。用户说“按照兼容同时存在多条数据信息的方式”,但这里可能只有一条数据,所以输出一个JSON数组,包含一个对象。\n\n现在,确认每个字段:\n\nname: 陈冬冬\n\nwebsite: https://www.chendd.cn\n\nweiChat: java-dong\n\n其他字段如QQ、邮箱、抖音等可能不需要,所以只保留这三个。\n\n但用户提到“可能包含如下关键要素”,所以需要确认是否所有提到的信息都属于这三个字段。例如,用户说“博主是一名酷(ku)比(bi)的80后老爷们,现役搬土质砖人士,也会持续的在行业内摸爬滚打。别轻易加我,一对一答疑解惑(教学)将占用我的时间,知识付费。” 这里可能有“微信:java-dong”,所以weiChat是java-dong。\n\n所以,最终的JSON数组应该包含这三个字段,每个字段的值对应正确。\n\n现在,检查是否有错误。例如,用户说“本站域名不出手”,可能意味着这个域名是网站的,但博主名称是陈冬冬,所以name是陈冬冬,website是https://www.chendd.cn,weiChat是java-dong。\n\n所以,最终的JSON应该是:\n\n[{\"name\": \"陈冬冬\", \"website\": \"https://www.chendd.cn\", \"weiChat\": \"java-dong\"}]\n\n但用户提到“站点域名:https://www.chendd.cn”,所以website是这个。而博主名称是陈冬冬,所以name是陈冬冬。\n\n另外,用户提到“微信:java-dong”,所以weiChat是java-dong。\n\n确认无误后,输出这个JSON数组。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 354,
"completion_tokens": 1084,
"total_tokens": 1438
}
}
(全量数据结果)
2.示例代码
@RunWith(JUnit4.class)
public class AiHelloJsonTest {
@Test
public void helloJsonTest() {
String content = """
站点域名:https://www.chendd.cn
博主名称:陈冬冬(本站域名不出手)
故乡:湖北省老河口市(曾经的小汉口,一个被历史遗忘的城市)
博主是一名酷(ku)比(bi)的80后老爷们,现役搬土质砖人士,也会持续的在行业内摸爬滚打。别轻易加我,一对一答疑解惑(教学)将占用我的时间,知识付费。
QQ:88911006
微信:java-dong
邮箱:88911006@qq.com
抖音:haiyangyiba
""";
String prompt = this.reload(content);
final ResponseEntity<OpenAiApi.ChatCompletion> responseEntity = RestClient.builder(new RestTemplateConfig().restTemplate())
.build().post().uri("/v1/chat/completions")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(prompt).retrieve().toEntity(OpenAiApi.ChatCompletion.class);
final OpenAiApi.ChatCompletion body = responseEntity.getBody();
System.out.println(body);
if (body != null) {
final String result = body.choices().get(0).message().content();
System.out.println(result);
}
}
private String reload(String content) {
String prompt;
try(InputStream resourceAsStream = getClass().getResourceAsStream("/prompt.template.json")) {
prompt = StreamUtils.copyToString(resourceAsStream , StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException(e);
}
String jsonString = JSON.toJSONString(content);
String temp = StringUtils.substring(jsonString,1,jsonString.length() - 1);
return StringUtils.replace(prompt, "[{content}]", temp);
}
}3.参考截图

(1.调试参数)

(2.全量参数输出)

(3.最终数据)
4.其它说明
(1)本次实践的代码是使用Spring AI调用本地大模型并返回定制格式JSON的实现;
(2)示例在执行过程中有一定的等待,受电脑性能影响实际的执行耗费了5分钟;
(3)示例执行的结果包含了$root.choices[0].message.content为最终数据的JSON Array字符串;$root.choices[0].message.reasoning为最终数据的思考过程;
(4)给出两个案例实际执行的全过程预览效果图,上述的图片省流只摘选了一部分,完整的预览参考《
示例下载.zip》,包含完整的示例请求和响应结果;
(5)许多案例给出的是结合Spring Boot后的配置注入方式,这种把大模型的服务URL、模型名称等等参数细节配置在application配置文件中的形式,我感觉不够灵活,后续相关的示例均使用硬编码的形式给出,面向底层代码更加便于新手水平理解掌握;
点赞
欢迎来到陈冬冬的学习天地 | 学习与经验整理分享平台