平台智能化
本节重点
平台智能化
- 接入 AI 模块
-
- 智谱 AI 介绍
- 使用智谱 AI SDK
- 封装 AI 模块
- AI 生成题目
-
- 需求分析
- 方案设计(实现原理),包括使用智谱页面调试 Prompt
- 后端开发
- 前端开发
- AI 智能评分
-
- 需求分析
- 方案设计(实现原理),包括使用智谱页面调试 Prompt
- 后端开发
- 前端开发
- 扩展知识
-
- Spring AI
- 智谱 AI + Spring AI 整合应用
一、接入 AI 模块
在智谱 AI 开放平台页面 进行题目生成的演示,然后后端 sdk 引入
1、智谱 AI 介绍
智谱 AI 开放平台:https://open.bigmodel.cn/overview
大模型介绍
AI 的应用场景
知识库管理
创建应用:应用 = 预定义了一些规则的大模型
在线体验:可用来测试不同大模型和参数的效果,支持 AI 绘画
2、使用智谱 AI SDK
需要阅读官方文档
使用指南:https://open.bigmodel.cn/dev/howuse/introduction
可以看到很多能力,比如知识库、网络搜索等
接口文档:https://open.bigmodel.cn/dev/api
有 3 种调用方式,SDK、HTTP 和第三方框架。
比较推荐 SDK,方便,不用自己构造 HTTP 请求并构造响应对象。
跟着官方文档去学习使用即可:https://open.bigmodel.cn/dev/api#sdk_install
Java 中使用 SDK
1)引入 maven:
<dependency>
<groupId>cn.bigmodel.openapi</groupId>
<artifactId>oapi-java-sdk</artifactId>
<version>release-V4-2.0.2</version>
</dependency>
2)创建单元测试来跑通 Demo
注意配置 apiKey,在智谱开放平台查看:https://open.bigmodel.cn/usercenter/apikeys
package com.yupi.yudada;
import com.zhipu.oapi.ClientV4;
import com.zhipu.oapi.Constants;
import com.zhipu.oapi.service.v4.model.ChatCompletionRequest;
import com.zhipu.oapi.service.v4.model.ChatMessage;
import com.zhipu.oapi.service.v4.model.ChatMessageRole;
import com.zhipu.oapi.service.v4.model.ModelApiResponse;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class ZhiPuAiTest {
@Test
public void test() {
String apiKey = "";
// 创建客户端
ClientV4 client = new ClientV4.Builder(apiKey).build();
// 构造请求
List<ChatMessage> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "作为一名营销专家,请为智谱开放平台创作一个吸引人的slogan");
messages.add(chatMessage);
String requestId = String.valueOf(System.currentTimeMillis());
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(Boolean.FALSE)
.invokeMethod(Constants.invokeMethod)
.messages(messages)
.requestId(requestId)
.build();
// 调用
ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
System.out.println("model output:" + invokeModelApiResp.getMsg());
}
}
每个参数的含义可以参考官方文档:https://open.bigmodel.cn/dev/api#glm-4
3)Debug 程序,如果无法 Debug,开启如下配置
查看到 AI 返回的结果如图:
其他用法
异步调用:相当于提交任务,可以之后查询任务状态并获取结果。
SSE 调用:流式、实时地获取到结果直到结束,可以优化用户体验。
3、封装 AI 模块
目的是更快速灵活地调用 AI 的能力,各项目都可以复用。
1)在 application.yml 中定义 AI 配置
ai:
apiKey: xxx
2)定义 AI 配置类,加载配置文件,初始化调用智谱的 Client 并将其注册为 bean。
@Configuration
@ConfigurationProperties(prefix = "ai")
@Data
public class AiConfig {
private String apiKey;
@Bean
public ClientV4 getClientV4() {
return new ClientV4.Builder(apiKey).build();
}
}
可以自己定制客户端的一些配置,后续 service 即可注入使用,不用再重复创建 Client 了。
3)智谱 SDK 提供的客户端已经很方便了,但我们仍然可以根据实际业务需求,封装本项目内通用的 AI 请求构建模块。
比如将复杂的消息、请求构建、从返回值中获取结果的过程进行封装。
一般建议先封装通用的方法,然后提供不同参数默认值的简化调用方法,层层简化。
最通用的方法,可自定义消息、是否流式、随机数:
public String doRequest(List<ChatMessage> messages, Boolean stream, Float temperature) {
// 构造请求
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(stream)
.invokeMethod(Constants.invokeMethod)
.temperature(temperature)
.messages(messages)
.build();
ModelApiResponse invokeModelApiResp = clientV4.invokeModelApi(chatCompletionRequest);
ChatMessage result = invokeModelApiResp.getData().getChoices().get(0).getMessage();
return result.getContent().toString();
}
通用请求,简化了消息传递:
public String doRequest(String systemMessage, String userMessage, Boolean stream, Float temperature) {
// 构造请求
List<ChatMessage> messages = new ArrayList<>();
ChatMessage systemChatMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), systemMessage);
ChatMessage userChatMessage = new ChatMessage(ChatMessageRole.USER.value(), userMessage);
messages.add(systemChatMessage);
messages.add(userChatMessage);
return doRequest(messages, stream, temperature);
}
通用同步请求:
public String doSyncRequest(String systemMessage, String userMessage, Float temperature) {
return doRequest(systemMessage, userMessage, Boolean.FALSE, temperature);
}
预置两种不同稳定性的方法,以满足我们之后的需求。
- AI 生成题目不应该稳定(需提高随机性),让思维发散
- AI 评分需要稳定(降低随机性)避免相同的答案每次得到不一样的分析结果
// 较稳定的随机数
private static final float STABLE_TEMPERATURE = 0.05f;
// 不稳定的随机数
private static final float UNSTABLE_TEMPERATURE = 0.99f;
/**
* 同步调用(答案较稳定)
*
* @param systemMessage
* @param userMessage
* @return
*/
public String doSyncStableRequest(String systemMessage, String userMessage) {
return doSyncRequest(systemMessage, userMessage, STABLE_TEMPERATURE);
}
/**
* 同步调用(答案较随机)
*
* @param systemMessage
* @param userMessage
* @return
*/
public String doSyncUnstableRequest(String systemMessage, String userMessage) {
return doSyncRequest(systemMessage, userMessage, UNSTABLE_TEMPERATURE);
}
这样做之后,不仅满足了本项目的需求,其他项目也可以复用部分方法。
整个项目可以引入 AI 的地方有很多,核心使用场景是 “AI 生成题目” 和 “AI 智能评分”。
二、AI 生成题目
需求分析
原本创建题目需要人工一个个添加标题和选项,比较麻烦。
可以使用 AI,根据已经填写的应用信息,自动生成题目,然后再由人工进行编辑确认,提高创建题目的效率。
方案设计
AI 生成内容的核心是编写 Prompt,好的、精准的 Prompt 才能帮助我们得到预期的结果。
首先明确我们能提供或者需要输入给 AI 的参数,然后构建 Prompt 并输入给 AI,让 AI 生成题目并处理成我们需要的格式。
1、明确输入参数
明确需要提供给 AI 的参数列表:
- 应用信息,包括应用名称、描述、类别(得分 / 测评)
- 题目信息,包括要生成的题目数、每题选项数
当然,还可以有其他信息,比如限定单个选项的得分范围、选项对应的属性集合等。
不过因为 AI 给出的结果存在随机性,建议大家先一切从简,只提供核心参数,保证 AI 输出的结果尽可能地稳定。
2、明确返回结果
需要 JSON 格式的题目列表,比如:
[
{
"options": [
{
"result": "I",
"value": "独自工作",
"key": "A"
},
{
"result": "E",
"value": "与他人合作",
"key": "B"
}
],
"title": "你通常更喜欢"
}
]
3、编写和调试 Prompt
推荐阅读智谱 AI 官方提供的 Prompt 工程指南:https://open.bigmodel.cn/dev/howuse/prompt
对于精准度要求不高的生成需求,可以先通过智谱 AI 体验中心进行简单调试。但如果要追求更准确的结果,最好使用 SDK 调用来调试,因为 web 端和通过 SDK 调用的效果可能有细微差别。
遵循 Prompt 工程指南中的技巧,编写出系统 Prompt 和用户 Prompt。
系统 Prompt 如下:
你是一位严谨的出题专家,我会给你如下信息:
应用名称, 【【【应用描述】】】, 应用类别, 要生成的题目数, 每个题目的选项数
请你根据上述信息,按照以下步骤来出题:
1. 要求:题目和选项尽可能地短,题目不要包含序号,每题的选项数以我提供的为主,题目不能重复
2. 严格按照下面的 json 格式输出题目和选项
[{"options":[{"value":"选项内容","key":"A"},{"value":"","key":"B"}],"title":"题目标题"}]
title 是题目,options 是选项,每个选项的 key 按照英文字母序(比如 A、B、C、D)以此类推,value 是选项内容
3. 检查题目是否包含序号,若包含序号则去除序号
4. 返回的题目列表格式必须为 JSON 数组
用户 Prompt 按照顺序提供信息即可,示例 Prompt 如下。
测评类应用:
MBTI 性格测试,
【【【快来测测你的 MBTI 性格】】】,
测评类,
10,
3
得分类应用:
小学数学测验,
【【【小学三年级的数学题】】】,
得分类,
10,
3
用到的技巧:
- 定义 System Prompt
- 让 GLM 进行角色扮演
- 使用分隔符标示不同的输入部分
- 少样本学习
- 指定输出长度的示例
- 将复杂任务分解为简单的子任务
- 指定固定的输出格式
- 通过按顺序输入参数来节约空间。
- 对于描述这种多行内容,可以通过特殊字符括起来,防止用户的输入干扰生成结果。
先利用界面进行初步调试,分别调试测评类和得分类应用:
然后编写单元测试来进一步调试,得到的结果可能会包含额外的开头和结尾字符串。
对应的解决方案是,利用字符串查找,找到开头的 '[' 和结尾的 ']',进行截取。
最后,将 JSON 字符串转换为题目对象,返回给前端即可。
4、注意事项
任何大模型都有单次提问 token 限制,注意题目和选项数不能太多,太多的话可以分批生成。
经过尝试,建议最多每 20 道题生成调用一次 AI 接口。
Token:是模型用来表示自然语言文本的基本单位,可以直观的理解为“字”或“词”;通常1个中文词语、1个英文单词、1个数字或1个符号计为 1 个token。
ChatGLM 的最大单次调用的 token 量和对应的汉字数是多少?大概单次消耗多少 token?
一般情况下 ChatGLM 模型中 token 和字数的换算比例约为 1:1.6,但因为不同模型的分词不同,所以换算比例也存在差异,每一次实际处理 token 数量以模型返回为准,可以从返回结果的 usage 中查看。
经过测试,生成 10 道题目导致需要消耗 1700 左右的 token,10 道题的智能评分大致 600 左右的 token。
上下文最大 tokens 量是 128k(不同的模型不一样,要以官方文档为准),输出 token 数量还受到请求参数 max_tokens 的限制,默认1024,最大 8192,不建议超过 4096,超 4096 会显著降低接口响应速度。
后端开发
按照上述方案,进行编码实现。
本节先使用同步的方式调用 AI,后续会进行优化。
1)AI 生成题目请求类:
@Data
public class AiGenerateQuestionRequest implements Serializable {
/**
* id
*/
private Long appId;
/**
* 题目数
*/
int questionNumber = 10;
/**
* 选项数
*/
int optionNumber = 2;
private static final long serialVersionUID = 1L;
}
2)定义模板常量和构造用户模板的方法:
private static final String GENERATE_QUESTION_SYSTEM_MESSAGE = "你是一位严谨的出题专家,我会给你如下信息:\n" +
"```\n" +
"应用名称,\n" +
"【【【应用描述】】】,\n" +
"应用类别,\n" +
"要生成的题目数,\n" +
"每个题目的选项数\n" +
"```\n" +
"\n" +
"请你根据上述信息,按照以下步骤来出题:\n" +
"1. 要求:题目和选项尽可能地短,题目不要包含序号,每题的选项数以我提供的为主,题目不能重复\n" +
"2. 严格按照下面的 json 格式输出题目和选项\n" +
"```\n" +
"[{\"options\":[{\"value\":\"选项内容\",\"key\":\"A\"},{\"value\":\"\",\"key\":\"B\"}],\"title\":\"题目标题\"}]\n" +
"```\n" +
"title 是题目,options 是选项,每个选项的 key 按照英文字母序(比如 A、B、C、D)以此类推,value 是选项内容\n" +
"3. 检查题目是否包含序号,若包含序号则去除序号\n" +
"4. 返回的题目列表格式必须为 JSON 数组";
private String getGenerateQuestionUserMessage(App app, int questionNumber, int optionNumber) {
StringBuilder userMessage = new StringBuilder();
userMessage.append(app.getAppName()).append("\n");
userMessage.append(app.getAppDesc()).append("\n");
userMessage.append(AppTypeEnum.getEnumByValue(app.getAppType()).getText() + "类").append("\n");
userMessage.append(questionNumber).append("\n");
userMessage.append(optionNumber);
return userMessage.toString();
}
3)AI 生成接口:
@PostMapping("/ai_generate")
public BaseResponse<List<QuestionContentDTO>> aiGenerateQuestion(@RequestBody AiGenerateQuestionRequest aiGenerateQuestionRequest) {
ThrowUtils.throwIf(aiGenerateQuestionRequest == null, ErrorCode.PARAMS_ERROR);
// 获取参数
Long appId = aiGenerateQuestionRequest.getAppId();
int questionNumber = aiGenerateQuestionRequest.getQuestionNumber();
int optionNumber = aiGenerateQuestionRequest.getOptionNumber();
App app = appService.getById(appId);
ThrowUtils.throwIf(app == null, ErrorCode.NOT_FOUND_ERROR);
// 封装 Prompt
String userMessage = getGenerateQuestionUserMessage(app, questionNumber, optionNumber);
// AI 生成
String result = aiManager.doSyncUnstableRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMessage);
// 结果处理
int start = result.indexOf("[");
int end = result.lastIndexOf("]");
String json = result.substring(start, end + 1);
List<QuestionContentDTO> questionContentDTOList = JSONUtil.toList(json, QuestionContentDTO.class);
return ResultUtils.success(questionContentDTOList);
}
前端开发
效果如图:
抽屉组件
允许用户填写生成参数并执行,生成成功后回调题目页面(父组件)的函数,插入题目。
代码如下:
<template>
<a-button type="outline" @click="handleClick">AI 生成题目</a-button>
<a-drawer
:width="340"
:visible="visible"
@ok="handleOk"
@cancel="handleCancel"
unmountOnClose
>
<template #title> AI 生成题目</template>
<div>
<a-form
:model="form"
label-align="left"
auto-label-width
@submit="handleSubmit"
>
<a-form-item label="应用 id">
{{ appId }}
</a-form-item>
<a-form-item field="questionNumber" label="题目数量">
<a-input-number
v-model="form.questionNumber"
min="0"
max="20"
placeholder="请输入题目数量"
/>
</a-form-item>
<a-form-item field="optionNumber" label="选项数量">
<a-input-number
v-model="form.optionNumber"
min="0"
max="6"
placeholder="请输入选项数量"
/>
</a-form-item>
<a-form-item>
<a-button
:loading="submitting"
type="primary"
html-type="submit"
style="width: 120px"
>
一键生成
</a-button>
</a-form-item>
</a-form>
</div>
</a-drawer>
</template>
import { defineProps, reactive, ref, withDefaults } from "vue";
import API from "@/api";
import { aiGenerateQuestionUsingPost } from "@/api/questionController";
import message from "@arco-design/web-vue/es/message";
interface Props {
appId: string;
onSuccess?: (result: API.QuestionContentDTO[]) => void;
}
const props = withDefaults(defineProps<Props>(), {
appId: () => {
return "";
},
});
const visible = ref(false);
const submitting = ref(false);
const form = reactive({
questionNumber: 10,
optionNumber: 2,
} as API.UserLoginRequest);
const handleClick = () => {
visible.value = true;
};
const handleOk = () => {
visible.value = false;
};
const handleCancel = () => {
visible.value = false;
};
/**
* 提交
*/
const handleSubmit = async () => {
if (!props.appId) {
return;
}
submitting.value = true;
const res = await aiGenerateQuestionUsingPost({
appId: props.appId as any,
...form,
});
if (res.data.code === 0 && res.data.data.length > 0) {
if (props.onSuccess) {
props.onSuccess(res.data.data);
} else {
message.success("生成成功");
}
handleCancel();
} else {
message.error("操作失败," + res.data.message);
}
submitting.value = false;
};
引入组件
设置题目页面引入抽屉组件:
<a-space size="medium">
<a-button @click="addQuestion(questionContent.length)">
底部添加题目
</a-button>
<!-- AI 生成题目抽屉 -->
<AiGenerateQuestionDrawer
:appId="appId"
:onSuccess="onAiGenerateSuccess"
/>
</a-space>
编写 AI 生成题目成功的处理,插入题目:
const API.QuestionContentDTO[]) => {
questionContent.value = [...questionContent.value, ...result];
message.success(`AI 生成题目成功,已新增 ${result.length} 道题目`);
};
验证测试
1)AddAppPage 创建应用成功后无法正确跳转到详情页 Bug
因为 id 的默认值为空字符串导致,需将 "??" 改为 "||",代码如下:
if (res.data.code === 0) {
message.success("操作成功,即将跳转到应用详情页");
setTimeout(() => {
router.push(`/app/detail/${props.id || res.data.data}`);
}, 3000);
}
2)AI 生成中断,需要调整请求配置中的超时时间
const myAxios = axios.create({
baseURL: "http://localhost:8101",
timeout: 60000,
withCredentials: true,
});
3)实际测试的生成结果有点太飘(随机)了,如图:
还是将随机性调整为默认值:
// AI 生成
String result = aiManager.doSyncRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMessage, null);
调整参数后,生成的结果正常多了:
扩展思路
1)可以将历史生成的题目标题关联到上下文中,让 AI 排除掉重复题目的生成。建议做一个通用的支持上下文自动关联并调用的组件。
示例代码仅供参考:
private String AICreateTitle(int number) {
String appAiSysMessageConfig = "系统提示词"
String appAiUserMessageConfig = "用户提示词";
//经过测试,需要每 20 道题一组,ChatGLM 拒绝一次性输出大量题目
List<ChatMessage> assistantChatMessageList = new ArrayList<>();
while (number >= 10) {
invokeAiLoop(number, appAiSysMessageConfig, appAiUserMessageConfig, assistantChatMessageList);
number = number - 10;
}
if (number > 0) {
invokeAiLoop(number, appAiSysMessageConfig, appAiUserMessageConfig, assistantChatMessageList);
}
//将结果合并成一个 List 返回
JSONArray result = new JSONArray();
for (ChatMessage message : assistantChatMessageList) {
JSONArray jsonArray = JSONArray.parseArray(String.valueOf(message.getContent()));
result.addAll(jsonArray);
}
return JSON.toJSONString(result);
}
private void invokeAiLoop(int number, String appAiSysMessageConfig, String appAiUserMessageConfig, List<ChatMessage> assistantChatMessages) {
//每次需要重建request,因为一次调用后sokcet通道已经被关闭,复用老的 request 会复用老链接会报错
ChatCompletionRequest chatCompletionRequest = ChatGLMUtil.buildSyncUnStableRequest(appAiSysMessageConfig, String.format(appAiUserMessageConfig, Math.min(number, 10), 2));
chatCompletionRequest.getMessages().addAll(assistantChatMessages);
ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
String s = invokeModelApiResp.getData().getChoices().get(0).getMessage().getContent().toString();
int start = s.indexOf("[");
int end = s.lastIndexOf("]");
String content = s.substring(start, end + 1);
//保存上下文
assistantChatMessages.add(new ChatMessage(ChatMessageRole.ASSISTANT.value(), content));
}
2)对于测评类应用,给定属性集,生成题目选项对应的属性。
3)对于得分类应用,给定得分范围,生成题目选项对应的得分。
4)思考如何进一步优化 Prompt,比如通过简化 JSON key 的长度来节约 token 数。
三、AI 智能评分
需求分析
原本题目评分需要让应用创建者自己创建评分结果,并且给题目选项设置得分和对应的属性,比较麻烦。
可以使用 AI,根据应用信息、题目和用户的答案进行评分,直接返回评分结果。
这种评分策略更适用于测评类应用,提高创建应用效率的同时,给结果更多的可能性。
所以下面我们主要实现测评类应用的 AI 评分策略,暂时不关注得分类应用的 AI 评分结果,
也可以在前端做一些控制,如果是得分类应用,不支持选择 AI 评分策略等。
方案设计
1、明确输入参数
明确需要提供给 AI 的参数列表:
- 应用信息,包括应用名称、描述
- 题目信息
- 用户答案
但是需要注意,题目数量可能会很多,如果将完整的题目结构(包括选项列表)输入给 AI,可能会超出最大 token 限制,所以可以进行优化。只存储用户答案对应的选项。
示例结构如下:
{
"appName": "MBTI 性格测试",
"appDesc": "测试你的 MBTI 性格",
"question": [
{
"title": "你喜欢和人交流",
"answer": "喜欢"
}
]
}
2、明确返回结果
需要 JSON 格式的评分结果对象,比如:
{
"resultName": "INTJ",
"resultDesc": "INTJ被称为'策略家'或'建筑师',是一个高度独立和具有战略思考能力的性格类型"
}
3、编写和调试 Prompt
遵循 Prompt 工程指南中的技巧,编写出系统 Prompt 和用户 Prompt。
系统 Prompt 如下:
你是一位严谨的判题专家,我会给你如下信息:
应用名称, 【【【应用描述】】】, 题目和用户回答的列表:格式为 [{"title": "题目","answer": "用户回答"}]
请你根据上述信息,按照以下步骤来对用户进行评价:
1. 要求:需要给出一个明确的评价结果,包括评价名称(尽量简短)和评价描述(尽量详细,大于 200 字)
2. 严格按照下面的 json 格式输出评价名称和评价描述
{"resultName": "评价名称", "resultDesc": "评价描述"}
3. 返回格式必须为 JSON 对象
用户 Prompt 按照顺序提供信息即可,示例 Prompt 如下。
测评类应用:
MBTI 性格测试,
【【【快来测测你的 MBTI 性格】】】,
[{"title": "你通常更喜欢","answer": "独自工作"}, {"title": "当安排活动时","answer": "更愿意随机应变"}]
用到的技巧:
- 定义 System Prompt
- 让 GLM 进行角色扮演
- 使用分隔符标示不同的输入部分
- 少样本学习
- 指定输出长度的示例
- 将复杂任务分解为简单的子任务
- 指定固定的输出格式
- 通过按顺序输入参数来节约空间。
- 对于描述这种多行内容,可以通过特殊字符括起来,防止用户的输入干扰生成结果。
利用界面进行初步调试,可以尝试修改随机性:
然后可以通过编写单元测试来调试。
得到结果后,仍然需要利用字符串查找,找到开头的 '{' 和结尾的 '}',截取到实际内容。
后端开发
1)编写题目答案封装类:
public class QuestionAnswerDTO {
/**
* 题目
*/
private String title;
/**
* 用户答案
*/
private String userAnswer;
}
2)编写 AI 测评类应用评分策略类,需要指定对应的注解:
@ScoringStrategyConfig(appType = 1, scoringStrategy = 1)
定义模板常量和构造用户模板的方法:
private static final String AI_TEST_SCORING_SYSTEM_MESSAGE = "你是一位严谨的判题专家,我会给你如下信息:\n" +
"```\n" +
"应用名称,\n" +
"【【【应用描述】】】,\n" +
"题目和用户回答的列表:格式为 [{\"title\": \"题目\",\"answer\": \"用户回答\"}]\n" +
"```\n" +
"\n" +
"请你根据上述信息,按照以下步骤来对用户进行评价:\n" +
"1. 要求:需要给出一个明确的评价结果,包括评价名称(尽量简短)和评价描述(尽量详细,大于 200 字)\n" +
"2. 严格按照下面的 json 格式输出评价名称和评价描述\n" +
"```\n" +
"{\"resultName\": \"评价名称\", \"resultDesc\": \"评价描述\"}\n" +
"```\n" +
"3. 返回格式必须为 JSON 对象";
private String getAiTestScoringUserMessage(App app, List<QuestionContentDTO> questionContentDTOList, List<String> choices) {
StringBuilder userMessage = new StringBuilder();
userMessage.append(app.getAppName()).append("\n");
userMessage.append(app.getAppDesc()).append("\n");
List<QuestionAnswerDTO> questionAnswerDTOList = new ArrayList<>();
for (int i = 0; i < questionContentDTOList.size(); i++) {
QuestionAnswerDTO questionAnswerDTO = new QuestionAnswerDTO();
questionAnswerDTO.setTitle(questionContentDTOList.get(i).getTitle());
questionAnswerDTO.setUserAnswer(choices.get(i));
questionAnswerDTOList.add(questionAnswerDTO);
}
userMessage.append(JSONUtil.toJsonStr(questionAnswerDTOList));
return userMessage.toString();
}
实现应用评分策略:
@Override
public UserAnswer doScore(List<String> choices, App app) throws Exception {
Long appId = app.getId();
// 1. 根据 id 查询到题目
Question question = questionService.getOne(
Wrappers.lambdaQuery(Question.class).eq(Question::getAppId, appId)
);
QuestionVO questionVO = QuestionVO.objToVo(question);
List<QuestionContentDTO> questionContent = questionVO.getQuestionContent();
// 2. 调用 AI 获取结果
// 封装 Prompt
String userMessage = getAiTestScoringUserMessage(app, questionContent, choices);
// AI 生成
String result = aiManager.doSyncStableRequest(AI_TEST_SCORING_SYSTEM_MESSAGE, userMessage);
// 结果处理
int start = result.indexOf("{");
int end = result.lastIndexOf("}");
String json = result.substring(start, end + 1);
// 3. 构造返回值,填充答案对象的属性
UserAnswer userAnswer = JSONUtil.toBean(json, UserAnswer.class);
userAnswer.setAppId(appId);
userAnswer.setAppType(app.getAppType());
userAnswer.setScoringStrategy(app.getScoringStrategy());
userAnswer.setChoices(JSONUtil.toJsonStr(choices));
return userAnswer;
}
前端开发
前端不需要开发新的页面,给做题页面的“查看结果”按钮补充一个提交等待的 loading 效果即可。
验证测试
效果如图:
可以多次测试,调试 Prompt 和模型参数,以便获得更好的结果。
扩展思路
1)实现得分类应用的 AI 评分,可以给 AI 多传递每道题目正确的得分参数。
2)支持用户额外定义结果集,将 AI 生成的结果限定在一个范围内,更精确。
四、扩展知识
Spring AI
简化 AI 应用程序开发的工具库。
官方文档地址:https://docs.spring.io/spring-ai/reference/
主要功能如下:
简单来说就是适配了市面上主流的 AI 模型,提供统一接口便于我们的开发。
注意,整个 Spring AI 是基于 jdk 17 构建的。
默认支持国外主流的 AI 大模型,也支持自己定制。
智谱 AI + Spring AI 整合应用
Spring AI 官方也整合了智谱 AI:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-starters/spring-ai-starter-zhipuai/pom.xml
现在有两个版本可以选择:https://central.sonatype.com/artifact/io.springboot.ai/spring-ai-zhipu-ai-spring-boot-starter/versions
引入依赖:
<dependency>
<groupId>io.springboot.ai</groupId>
<artifactId>spring-ai-zhipu-ai-spring-boot-starter</artifactId>
<version>1.0.3</version>
</dependency>
使用起来仅需在配置文件中加下对应模型的配置:
spring:
ai:
zhipuai:
api-key: xxxx
chat:
enabled: true
options:
model: glm-4
然后引入对应模型的 client,即可直接使用:
@Resource
private ZhipuAiChatClient chatClient;
//简单调用
chatClient.call("给我讲一个笑话");
//也可以配置一些参数
ChatResponse response = chatClient.call(
new Prompt(
"帮我随机生成五个名字",
ZhipuAiChatOptions.builder()
.withModel("glm-4")
.withTemperature(0.8)
.build()
));
相比起来其实代码并没有简化很多,只不过项目同时接入多个 AI 平台,调用方式上会得到统一。
项目分区导航:⬅️ 06-用户模块RBAC模型搭建 | 01-平台智能化 | ➡️ 02-性能优化
💬 评论