如何定制Spring AI对话,引入Prompt模板?

摘要:本文代码:https:github.comJunTeamComai-demotreerelease-3.0 Spring with AI系列,只关注上层AI的应用程序(基于JAVA搭建),不关注底层的LLM原理、搭建等技术。 通
本文代码:https://github.com/JunTeamCom/ai-demo/tree/release-3.0 Spring with AI系列,只关注上层AI的应用程序(基于JAVA搭建),不关注底层的LLM原理、搭建等技术。 通过简单的自定义Prompt模板,即可定制一个AI,专注某一领域的知识回答。 1 创建模板 先在pom.xml引入验证Starter: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> 我们定义一个关于“世界各国地理历史知识”的AI,模板也简单明了: 实体定义: 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 实现逻辑 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 运行效果 测试用例: 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]"}' 返回结果(为了排版,笔者进行了截断): { "title":"JAVA", "answer":"```java\nclass Solution {\n public int findMin(int[] nums) {\n ...```" } 整理出来结果如下: 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):用于头脑风暴工具,激发非常规创意 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显示。 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使用相关的数据(元数据),形如: { "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 } 这样可以再代码中获取和记录: 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();