Spring AI(4)- 调整返回的Response - 今日运势
目录
简介#
Spring AI提供了一些功能来影响LLM返回的响应。
Setting generation options: 可以设置一些generation的参数,比如temperature,max tokens等,来影响LLM下个token的选择,从而影响最终的response。Structured Output Converter: 可以将LLM的response转为一个结构化的对象,方便后续处理。Streaming Response: 可以实时获取LLM生成的结果,生成一部分就返回一部分,提升用户体验。
指定Chat的options#
切换模型#
Spring AI中每个AI Provider都可以选择模型,选择较小的模型,可以加快响应速度。比如我本地用ollama,我可以切换成deepseek-r1:1.5b模型,来加快响应速度。
在application.yaml中指定模型
spring:
ai:
ollama:
chat:
# model: mistral:7b
model: deepseek-r1:1.5b
可以看到响应速度有了明显提升
| 模型 | Trial 1 | Trial 2 | Trial 3 |
|---|---|---|---|
| mistral:7b | 42.93s | 29.63s | 22.00s |
| deepseek-r1:1.5b | 21.26s | 14.68s | 16.79s |
但是较小模型的准确率会有所下降,有时生肖的数据都没有生成(虽然原先也是错误的)。
curl "http://localhost:8080/fortune-today?fullName=zhangsan&birthDate=1990-05-15" | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1416 0 1416 0 0 96 0 --:--:-- 0:00:14 --:--:-- 317
{
"fortune": "1. **基本信息** \n- 姓名:zhangsan \n- 星座:金牛座 \n- 生肖:【根据出生年份计算】(五岁入学的生肖是牛) \n\n2. **今日综合运势** (2025-11-22 ) \n 整体运势:⭐⭐⭐(5颗星) \n 运势简评:在当今社会,他们可能会经历一些压力和挑战,但总体而言感情观积极向上,事业潜力巨大。 \n\n3. **详细运势分析** \n - 爱情运势:金牛座的人性格热情且充满活力,在爱情方面可能会遇到一些年龄较大的限制,但只要注意调节心态,依然有很多机会寻找真爱。事业运势:虽然可能在工作中面临一些压力,但他们的创造力和抗压能力使得事业发展空间广阔,潜力巨大。财务运势:财务状况良好,有良好的收入来源,同时也可以合理规划理财,避免不必要的开支。健康运势:整体健康状况较好,适合追求快乐的人群,如果有身体不适应及时就医。 \n\n4. **幸运元素** \n - 幸运颜色:红色、黄色、银色 \n - 幸运数字:8、4、6 \n - 幸运方位:北极星(红色)、东方(黄色)、南方(银色) \n\n5. **今日建议** \n - 调整心态,避免因年龄带来的固执感,适当放松心情。 \n - 积极融入社会,多与他人交流,增强自信感。 \n - 关注健康问题,及时就医。"
}
影响token生成#
当生成一个响应时,它是一次一个Token生成的。API会考虑原始prompt,然后使用模型来选择紧随prompt的下一个Token。依此类推,直到整个响应生成完毕。最终的Token是通过结合统计概率和随机选择确定的。
使用诸如Temperature、Top-p和Top-k等ChatOption,你可以影响选择的随机程度,以及某些 Token 被选中的可能性。Spring AI对不同模型都提供了各自的参数设置,比如Ollama可以看Ollama Chat Options。
这些都可以在application.yaml中进行配置
spring:
ai:
ollama:
chat:
# http http://localhost:11434/api/tags -b to see available models
model: mistral:7b
# model: deepseek-r1:1.5b
options:
temperature: 0.8
# maxTokens: 1024
top-k: 40
top-p: 0.9
| Temperature | Trial 1 | Trial 2 | Trial 3 |
|---|---|---|---|
| 0.8 | 15.96s | 11.18s | 14.28s |
| 1.5 | 24.40s | 24.31s | 24.91s |
虽然返回的生肖还都是错误的,但是temperature较低的情况下,响应时间更快一些。
其他的各种options也可以逐个调整,观察对响应时间和结果的影响。
格式化Response输出#

StructuredOutputConverter只是尽力(best effort)将模型结果转为一个结构化的输出,但是AI模型本身并不保证一定会返回符合预期格式的结果。在必要的情况下,可以实现一个对response的校验机制,确保结果的格式符合预期。
我希望今日运势返回的结果是一个JSON对象。原先的prompt模版需要调整。
你是一位专业的星座和生肖运势分析师。请根据以下信息生成今日运势:
用户姓名:{name}
出生日期:{birthday}
星座:{zodiac}
今天是:{today_date}
{format}
请用温暖、积极的语气,让运势分析既专业又富有启发性。
返回的结果Entity为
public record FortuneTodayResponse(
String name,
String zodiac,
String chineseZodiac,
String overallRating,
String overallSummary,
String loveFortune,
String careerFortune,
String wealthFortune,
String healthFortune,
String luckyColor,
String luckyNumber,
String luckyDirection,
List<String> advice) {
}
@Override
public FortuneTodayResponse getFortuneToday(UserInfo userInfo) {
// 解析日期
LocalDate birthDate = LocalDate.parse(userInfo.birthDate());
// 计算星座
String zodiacSign = Objects.requireNonNull(ZodiacUtils.getZodiacSign(birthDate), "zodiacSign must not be null");
// 定义结构化输出转换器
var outputConverter = new BeanOutputConverter<>(FortuneTodayResponse.class);
var responseSpec = chatClient.prompt()
.user(userSpec -> userSpec.text(templateResource)
.param("name", userInfo.fullName())
.param("birthday", userInfo.birthDate())
.param("zodiac", zodiacSign)
.param("today_date", Objects.requireNonNull(String.valueOf(LocalDate.now())))
.param("format", outputConverter.getFormat())) // 注入格式指令
.call();
var chatResponse = responseSpec.chatResponse();
if (chatResponse == null) {
throw new RuntimeException("AI response is null");
}
if (chatResponse.getMetadata() != null) {
String model = chatResponse.getMetadata().getModel();
logger.info("Model used for generating fortune today: {}", model);
}
var content = chatResponse.getResult().getOutput().getText();
if (content == null) {
throw new RuntimeException("AI response content is null");
}
// 将文本响应转换为对象
return outputConverter.convert(content);
}
这里使用了BeanOutputConverter,它的getFormat()方法实际上是增加了返回结果的prompt.
/**
* Provides the expected format of the response, instructing that it should adhere to
* the generated JSON schema.
* @return The instruction format string.
*/
@Override
public String getFormat() {
String template = """
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:
```%s```
""";
return String.format(template, this.jsonSchema);
}
结果会变成这样

Streaming Response#
在Spring AI中,流式响应的核心是使用Project Reactor库中的Flux类型。
在LLM中的应用: 当使用流式API时, Flux<String> 会包含 LLM 逐个生成的简短字符串片段(通常是单个单词或Token)。每当一个片段可用时,它就会被发送给客户端。
限制#
流式响应和JSON输出转换(例如将结果直接映射到一个Java 对象)不能很好地结合使用。(上面的例子)
如果要进行输出转换,你必须先收集整个流,将其拼接成一个完整的 JSON 字符串,然后再进行解析。这样做就失去了使用流式响应(即时反馈)的意义。因此,最好不要将流式和输出转换混用。
将上面的例子进一步改成流式响应#
前端收到的将不再是一个完整的JSON对象,而是一连串的字符流,拼起来后才是一个完整的JSON。
如果还是原先的访问方式
可以写个简单的python脚本来拼接结果,效果如下
