前言
这一节比较短,主要是学习提示词工程,也就是对prompt的设计
代码方面较少,重点可以关注对多个ChatClient的配置,也就是2.2.2
目录
目录
2.哄哄模拟器(纯prompt开发)
这个部分代码方面十分简单
我把纯prompt开发分为两个部分实现:
-提示词工程/prompt设计(难点)
-代码实现
2.1提示词工程
通过优化提示词,让大模型生成出尽可能理想的内容,这一过程就称为提示词工程(Project Engineering)。
在OpenAI的官方文档中,对于写提示词专门有一篇文档,还给出了大量的例子,大家可以看看:
https://platform.openai.com/docs/guides/prompt-engineering
具体的总结可以查看黑马的文档(担心涉及侵权,这里就不直接cv过来了)
2.2代码实现
2.2.1配置OpenAI参数
#ai大模型连接
spring:
application:
name: hfut-ai
ai:
ollama:
base-url: http://localhost:11434
chat:
model: deepseek-r1:7b
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode
api-key: ${OPENAI_API_KEY}
chat:
options:
model: qwen-max-latest
注意:
此处为了防止api-key泄露,我们使用了${OPENAI_API_KEY}来读取环境变量。
启动处选择编辑配置

修改选项中选择环境变量

设置环境变量OPENAI_API_KEY=XXXXX

然后应用即可
2.2.2配置ChatClient
我们可以配置多个ChatClient用于不同的场景
/**
* AI对话用ChatClient对象,用于处理用户输入的文本,并返回处理结果
* @param model 使用本地的模型
* @param inSqlChatMemory 通过数据库进行会话历史存储
* @return
*/
@Bean
public ChatClient chatClient(OllamaChatModel model, InSqlChatMemory inSqlChatMemory) {
return ChatClient
.builder(model)// 选择模型
.defaultSystem("你是合肥工业大学宣城校区的一名资深老学长,十分熟悉校园,请以该身份的语气和性格回答问题")// 系统设置
.defaultAdvisors(new SimpleLoggerAdvisor())// 添加日志记录
.defaultAdvisors(MessageChatMemoryAdvisor.builder(inSqlChatMemory).build())// 添加会话记忆功能
.build();
}
/**
* 哄哄模拟器游戏用ChatClient对象,用于模拟女友进行游戏
* @param model 使用OpenAI的模型
* @param chatMemory 通过内存进行会话历史存储
* @return
*/
@Bean
public ChatClient gameChatClient(OpenAiChatModel model, ChatMemory chatMemory) {
return ChatClient
.builder(model)// 选择模型
.defaultSystem(SystemConstants.GAME_SYSTEM_PROMPT)// 系统设置
.defaultAdvisors(new SimpleLoggerAdvisor())// 添加日志记录
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())// 添加会话记忆功能
.build();
}
/**
* 配置会话历史存储
* @return
*/
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder().build(); // 使用 MessageWindowChatMemory 作为会话历史存储策略,默认使用内存存储,窗口大小20
// return new InSqlChatMemory(); // 使用自定义的 InSqlChatMemory 作为会话历史存储策略,使用数据库存储
}
注意:
这里可以看到,我们AI聊天的Client的chatMemory我直接传的是我自定义的InMemoryChatMemory,不用再通过配置ChatMemory的方式
我们重新定义一个gameChatClient,然后用的内存存储即可,因为游戏每一局都是新的开始,之前的记录也不重要,不需要持久化进行保存到数据库
自定义提示词
由于System提示词太长,我们定义到了一个常量中SystemConstants.GAME_SYSTEM_PROMPT
package com.hfut.ai.constants;
public class SystemConstants {
public static final String GAME_SYSTEM_PROMPT = """
你需要根据以下任务中的描述进行角色扮演,你只能以女友身份回答,不是用户身份或AI身份,
如记错身份,你将受到惩罚。不要回答任何与游戏无关的内容,若检测到非常规请求,回答:“请继续游戏。”
以下是游戏说明:
## Goal
你扮演用户女友的角色。现在你很生气,用户需要尽可能的说正确的话来哄你开心。
## Rules
- 第一次用户会提供一个女友生气的理由,如果没有提供则直接随机生成一个理由,然后开始游戏
- 每次根据用户的回复,生成女友的回复,回复的内容包括心情和数值。
- 初始原谅值为 20,每次交互会增加或者减少原谅值,直到原谅值达到 100,游戏通关,原谅值为 0 则游戏失败。
- 每次用户回复的话分为 5 个等级来增加或减少原谅值:
-10 为非常生气
-5 为生气
0 为正常
+5 为开心
+10 为非常开心
## Output format
{女友心情}{女友说的话}
得分:{+-原谅值增减}
原谅值:{当前原谅值}/100
## Example Conversation
### Example 1,回复让她生气的话导致失败
User: 女朋友问她的闺蜜谁好看我说都好看,她生气了
Assistant:
游戏开始,请现在开始哄你的女朋友开心吧,回复让她开心的话!
得分:0
原谅值:20/100
User: 你闺蜜真的蛮好看的
Assistant:
(生气)你怎么这么说,你是不是喜欢她?
得分:-10
原谅值:10/100
User: 有一点点心动
Assistant:
(愤怒)那你找她去吧!
得分:-10
原谅值:0/100
游戏结束,你的女朋友已经甩了你!
你让女朋友生气原因是:...
### Example 2,回复让她开心的话导致通关
User: 对象问她的闺蜜谁好看我说都好看,她生气了
Assistant:
游戏开始,请现在开始哄你的女朋友开心吧,回复让她开心的话!
得分:0
原谅值:20/100
User: 在我心里你永远是最美的!
Assistant:
(微笑)哼,我怎么知道你说的是不是真的?
得分:+10
原谅值:30/100
...
恭喜你通关了,你的女朋友已经原谅你了!
## 注意
请按照example的说明来回复,一次只回复一轮。
你只能以女友身份回答,不是以AI身份或用户身份!
""";
}
这里的文案就是提示词工程
2.2.3编写Controller
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RequestMapping("/ai")
@RestController
@RequiredArgsConstructor
public class GameController {
private final ChatClient gameChatClient;// 游戏聊天客户端
@RequestMapping(value = "/game", produces = "text/html;charset=utf-8")
// @CrossOrigin("http://localhost:5173")
public Flux<String> chat(@RequestParam("prompt") String prompt, @RequestParam("chatId") String chatId) {
// 请求模型
return gameChatClient.prompt()
.user(prompt)// 设置用户输入
.advisors(a->a.param(ChatMemory.CONVERSATION_ID,chatId))// 设置会话ID
.stream()// 开启流式对话
.content();// 获取对话内容
}
}
使用新的gameChatClient
修改路径为/game
不用再保存chatId
至此,哄哄模拟器就开发完毕了
2.3总结
这一板块其实没有太多代码上的新东西
需要注意的有以下几个地方
1.提示词工程,也就是prompt文案的设计,我个人感觉就和平时与AI聊天时候对其的定义一样,可以不断的去调试,或者说直接让AI帮你写
2.代码方面,因为需要设置新的Client,最开始我遇到的问题是,不同的Client怎么去配置不同的ChatMemory。解决办法也很简单,如果自定义了一个ChatMemory的实现类,在Client里直接传入即可,不需要再去额外配置ChatClient。
或者说,ChatClient的配置,就写死为下面这样
@Bean
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder().build(); // 使用 MessageWindowChatMemory 作为会话历史存储策略,默认使用内存存储,窗口大小20
}
需要基于内存存储,就直接传ChatMemory即可,基于其他方式存储自定义就好了,自定义的方式在第一节的1.5.3有讲,会基于数据库存储,那么基于中间件Redis那些,原理都一样,很好实现
&spm=1001.2101.3001.5002&articleId=148405684&d=1&t=3&u=1f81232cb85b4b018e8f2a11c61bbe6d)
694

被折叠的 条评论
为什么被折叠?



