Spring with AI (3): 定制对话——Prompt模板引入

本文代码:https://github.com/JunTeamCom/ai-demo/tree/release-3.0

Spring with AI系列,只关注上层AI的应用程序(基于JAVA搭建),不关注底层的LLM原理、搭建等技术。

通过简单的自定义Prompt模板,即可定制一个AI,专注某一领域的知识回答。

1 创建模板

先在pom.xml引入验证Starter:

xml 复制代码
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

我们定义一个关于"世界各国地理历史知识"的AI,模板也简单明了:

实体定义:

java 复制代码
package com.junteam.ai.demo.model;

import jakarta.validation.constraints.NotBlank;

public record ChatQuestion(
        @NotBlank(message = "标题不能为空") String title,
        @NotBlank(message = "问题不能为空") String question) {
}

模板文件resources/promptTemplates/questionPromptTemplate.st定义:

复制代码
你是一个有用的助手,负责回答有关"代码编程题"的问题。
如果你对这个编程语言一无所知或不知道答案,请回答"我不知道"。

只给出实现代码。

编程语言是 {title}。

问题是:
{question}

2 实现逻辑

java 复制代码
package com.junteam.ai.demo.service.impl;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;

@Service
public class OpenAIChatServiceImpl implements ChatService {

    private final ChatClient chatClient;

    public OpenAIChatServiceImpl(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @Value("classpath:/promptTemplates/questionPromptTemplate.st")
    Resource questionPromptTemplate;

    @SuppressWarnings("null")
    @Override
    public ChatAnswer ask(ChatQuestion chatQuestion) {
        var answer = chatClient.prompt()
                .user(userSpec -> userSpec
                    .text(questionPromptTemplate)
                    .param("title", chatQuestion.title())
                    .param("question", chatQuestion.question())
                )
                .call();
        var answerText = answer.content();
        return new ChatAnswer(chatQuestion.title(), answerText);
    }
}

3 运行效果

测试用例:

bash 复制代码
curl http://localhost:8080/web/ask \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"title": "java", "question": "给定一个非递减排序的整数数组 nums 和一个目标值 target,请编写一个函数,返回 target 在数组中出现的第一个位置和最后一个位置(下标从 0 开始)。​\n  - 如果 target 未在数组中出现,返回 [-1, -1];​\n  - 要求:时间复杂度不超过 O(logn),空间复杂度 O(1)。​\n示例​\n  1. 输入:nums = [5,7,7,8,8,10], target = 8 → 输出:[3,4]​\n  2. 输入:nums = [5,7,7,8,8,10], target = 6 → 输出:[-1,-1]​\n  3. 输入:nums = [], target = 0 → 输出:[-1,-1]​\n  4. 输入:nums = [2,2], target = 2 → 输出:[0,1]"}' 

返回结果(为了排版,笔者进行了截断):

json 复制代码
{
  "title":"JAVA",
  "answer":"```java\nclass Solution {\n public int findMin(int[] nums) {\n ...```"
}

整理出来结果如下:

java 复制代码
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return nums[left];
    }
}

4 更多补充

4.1 基于模板扩展内容

再引入RAG之前,可以用简单的模板填充、来实现一些扩展内容。

比如再加一个langRules/java.txt,内容形如:

复制代码
- java中尽量使用基本数据、而非封装类型。
- java中尽量使用静态方法实现代码。

这样可以再模板可以修改为:

复制代码
你是一个有用的助手,负责回答有关"代码编程题"的问题。
如果你对这个编程语言一无所知或不知道答案,请回答"我不知道"。
如果可能,使用规则:{rules}。

只给出实现代码。

编程语言是 {title}。

问题是:
{question}

4.2 大模型选项

4.2.1 大模型类型

在配置讲解中已经提到,不再赘述

4.2.2 大模型温度

temperature参数是生成策略中的核心参数,直接影响输出的随机性与创造性。

  • 低Temperature(0.3-0.7):适用于客服机器人等需要精准回答的场景,减少错误信息
  • 中Temperature(0.7-1.2):适合创意写作,平衡逻辑性与多样性
  • 高Temperature(1.2-2.0):用于头脑风暴工具,激发非常规创意
java 复制代码
ChatOptions chatOptions = ChatOptions.builder()
  .temperature(0.7)
  .build();

String answerText = chatClient.prompt()
  .user(question.question())
  .options(chatOptions)
  .call()
  .content();

4.2.3 其他选项

  • topP拣选答案的比例,比如.topP(0.8)从排名前80%的结果中拣选
  • topK排除答案的比例,比如.topP(0.2)排名后20%的结果排除

4.3 格式化

例如前面所提的,在Prompt中,指定输出格式为json、或者只保留java代码。

这样可以快速实现热门歌单等json接口

另外可以把输出格式就设置为流式,这样客户端或网页前端,可以使用SSE协议接收结果、逐个Token显示。

java 复制代码
return chatClient.prompt()
  .system(systemSpec -> systemSpec
    .text(promptTemplate)
    .param("title", question.title())
    .param("rules", langRules))
    .user(question.question())
  .stream() // 流式
  .content();

4.4 响应元数据

LLM返回的内容,例如OpenAI,包含了Token使用相关的数据(元数据),形如:

json 复制代码
{
  "token_usage": {
    "completion_tokens": 164,
    "prompt_tokens": 17,
    "total_tokens": 181
  },
  "model_name": "gpt-4-turbo",
  "system_fingerprint": "fp_76f018034d",
  "finish_reason": "stop",
  "logprobs": null
}

这样可以再代码中获取和记录:

java 复制代码
var responseEntity = chatClient.prompt()
  .system(systemSpec -> systemSpec
    .text(promptTemplate)
    .param("gameTitle", question.gameTitle())
    .param("rules", gameRules))
    .user(question.question())
  .call()
.responseEntity(Answer.class);
var response = responseEntity.response();
var metadata = response.getMetadata();
log.info(metadata.getUsage()); // 获得Token使用量
return responseEntity.entity();
相关推荐
王小义笔记1 天前
CUDA 版本下 Transformers 报错排查与解决办法
llm·transformer·cuda
fanly111 天前
AgentForge 智能体组件:与云驿插件平台构建全生态化的微服务一体化智能开发引擎
微服务·ai·agent
冬奇Lab1 天前
Agent 系列(16):工具链设计——让 LLM 用对工具的五个原则
人工智能·llm·agent
Python私教1 天前
Cursor + Claude Code 全流程实战:搭一套生产级 AI 编程工作流(2026 最新版)
人工智能·语言模型·qwen·ollama·本地大模型·大模型部署·deepseek
武子康1 天前
调查研究-164-NVIDIA DGX Station for Windows 解析:不是新显卡,而是企业本地 AI 超算
人工智能·openai
AndrewHZ1 天前
【LLM技术全景】预训练与微调:大模型如何“学习“
人工智能·深度学习·大模型·llm·微调·预训练·rlhf
audyxiao0011 天前
ICLR 2026论文分享 | WorldGym:用世界模型打造机器人策略评估新范式
大数据·人工智能·大模型·智能体·世界模型
武子康1 天前
调查研究-163-MiniMax M3 正式发布:1M 上下文、多模态、Coding Agent 与 Sparse Attention 到底意味着什么?
人工智能·openai
Aqoo1 天前
给AI智能体装红灯:Recuse Signal让LLM学会主动退出
openai·claude
霸道流氓气质1 天前
大模型向量技术完全指南:从概念到实践
大模型·向量