平台智能化

目录

一、接入AI模块

1.智谱 AI 介绍

2.使用智谱 AI SDK

Java 中使用 SDK

其他用法

3、封装 AI 模块

二、AI 生成题目

1.需求分析

2.方案设计

1、明确输入参数

2、明确AI返回结果

3、编写和调试 Prompt

4、注意事项

3.后端开发

4.前端开发

1.抽屉组件

2.引入组件

5.验证测试

6.扩展思路

三、AI 智能评分

1.方案设计

1、明确输入参数

2、明确返回结果

3、编写和调试 Prompt

2.后端开发

3.前端开发

4.验证测试

5.扩展思路


一、接入AI模块

在智谱 AI 开放平台页面 进行题目生成的演示,然后后端 sdk 引入。

1.智谱 AI 介绍

智谱 AI 开放平台:智谱AI开放平台

创建应用:应用 = 预定义了一些规则的大模型

在线体验:可用来测试不同大模型和参数的效果,支持 AI 绘画

2.使用智谱 AI SDK

需要阅读官方文档

使用指南:智谱AI开放平台

可以看到很多能力,比如知识库、网络搜索等

接口文档:智谱AI开放平台

有 3 种调用方式,SDK、HTTP 和第三方框架。

比较推荐 SDK,方便,不用自己构造 HTTP 请求并构造响应对象。

跟着官方文档去学习使用即可:智谱AI开放平台

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,在智谱开放平台查看:智谱AI开放平台

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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
public class ZhiPuAiTest {

    @Resource
    private ClientV4 clientV4;

    @Test
    public void test() {
        // 初始化客户端
//        ClientV4 client = new ClientV4.Builder(KeyConstant.KEY).build();
        // 构建请求
        List<ChatMessage> messages = new ArrayList<>();
        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "作为一名营销专家,请为智谱开放平台创作一个吸引人的slogan");
        messages.add(chatMessage);
//        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                .model(Constants.ModelChatGLM4)
                .stream(Boolean.FALSE)
                .invokeMethod(Constants.invokeMethod)
                .messages(messages)
                .build();
        ModelApiResponse invokeModelApiResp = clientV4.invokeModelApi(chatCompletionRequest);
        System.out.println("model output:" + invokeModelApiResp.getData().getChoices().get(0));
    }
}

每个参数的含义可以参考官方文档:智谱AI开放平台

其他用法

异步调用:相当于提交任务,可以之后查询任务状态并获取结果。

SSE 调用:流式、实时地获取到结果直到结束,可以优化用户体验。

3、封装 AI 模块

目的是更快速灵活地调用 AI 的能力,各项目都可以复用。

1)在 application.yml 中定义 AI 配置

ai:
  apiKey: xxx

2)定义 AI 配置类,加载配置文件,初始化调用智谱的 Client 并将其注册为 bean。

@Configuration
@ConfigurationProperties(prefix = "ai")
@Data
public class AiConfig {

    /**
     * apiKey,需要从开放平台获取
     */

    private String apiKey;

    @Bean
    public ClientV4 getClientV4() {
        return new ClientV4.Builder(apiKey).build();
    }
}

可以自己定制客户端的一些配置,后续 service 即可注入使用,不用再重复创建 Client 了。

3)智谱 SDK 提供的客户端已经很方便了,但我们仍然可以根据实际业务需求,封装本项目内通用的 AI 请求构建模块。

比如将复杂的消息、请求构建、从返回值中获取结果的过程进行封装。

一般建议先封装通用的方法,然后提供不同参数默认值的简化调用方法,层层简化。

最通用的方法,可自定义消息、是否流式、随机数:

在manager包下(区别于工具类给整个项目提供一种能力)

/**
     * 通用请求
     *
     * @param messages
     * @param stream
     * @param temperature
     * @return
     */
    public String doRequest(List<ChatMessage> messages, Boolean stream, Float temperature) {
        // 构建请求
        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                .model(Constants.ModelChatGLM4)
                .stream(stream)
                .temperature(temperature)
                .invokeMethod(Constants.invokeMethod)
                .messages(messages)
                .build();
        try {
            ModelApiResponse invokeModelApiResp = clientV4.invokeModelApi(chatCompletionRequest);
            return invokeModelApiResp.getData().getChoices().get(0).toString();
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, e.getMessage());
        }
    }

通用请求,简化了消息传递:

    /**
     * 通用请求(简化消息传递)
     *
     * @param systemMessage
     * @param userMessage
     * @param stream
     * @param temperature
     * @return
     */
    public String doRequest(String systemMessage, String userMessage, Boolean stream, Float temperature) {
        List<ChatMessage> chatMessageList = new ArrayList<>();
        ChatMessage systemChatMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), systemMessage);
        chatMessageList.add(systemChatMessage);
        ChatMessage userChatMessage = new ChatMessage(ChatMessageRole.USER.value(), userMessage);
        chatMessageList.add(userChatMessage);
        return doRequest(chatMessageList, stream, temperature);
    }

通用同步请求:

    /**
     * 同步请求
     *
     * @param systemMessage
     * @param userMessage
     * @param temperature
     * @return
     */
    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 doSyncUnstableRequest(String systemMessage, String userMessage) {
        return doRequest(systemMessage, userMessage, Boolean.FALSE, UNSTABLE_TEMPERATURE);
    }

    /**
     * 同步请求(答案较稳定)
     *
     * @param systemMessage
     * @param userMessage
     * @return
     */
    public String doSyncStableRequest(String systemMessage, String userMessage) {
        return doRequest(systemMessage, userMessage, Boolean.FALSE, STABLE_TEMPERATURE);
    }

这样做之后,不仅满足了本项目的需求,其他项目也可以复用部分方法。

整个项目可以引入 AI 的地方有很多,核心使用场景是 “AI 生成题目” 和 “AI 智能评分”。

二、AI 生成题目

1.需求分析

原本创建题目需要人工一个个添加标题和选项,比较麻烦。

可以使用 AI,根据已经填写的应用信息,自动生成题目,然后再由人工进行编辑确认,提高创建题目的效率。

2.方案设计

AI 生成内容的核心是编写 Prompt,好的、精准的 Prompt 才能帮助我们得到预期的结果。

首先明确我们能提供或者需要输入给 AI 的参数,然后构建 Prompt 并输入给 AI,让 AI 生成题目并处理成我们需要的格式。

1、明确输入参数

明确需要提供给 AI 的参数列表:

  1. 应用信息,包括应用名称、描述、类别(得分 / 测评)
  2. 题目信息,包括要生成的题目数、每题选项数

当然,还可以有其他信息,比如限定单个选项的得分范围、选项对应的属性集合等。

不过因为 AI 给出的结果存在随机性,建议大家先一切从简,只提供核心参数,保证 AI 输出的结果尽可能地稳定。

2、明确AI返回结果

需要 JSON 格式的题目列表,比如:

3、编写和调试 Prompt

推荐阅读智谱 AI 官方提供的 Prompt 工程指南:智谱AI开放平台

对于精准度要求不高的生成需求,可以先通过智谱 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

用到的技巧:

  1. 定义 System Prompt
  2. 让 GLM 进行角色扮演
  3. 使用分隔符标示不同的输入部分
  4. 少样本学习
  5. 指定输出长度的示例
  6. 将复杂任务分解为简单的子任务
  7. 指定固定的输出格式
  8. 通过按顺序输入参数来节约空间。
  9. 对于描述这种多行内容,可以通过特殊字符括起来,防止用户的输入干扰生成结果。

先利用界面进行初步调试,分别调试测评类和得分类应用:

然后编写单元测试(调用之前封装的方法并查看其返回的结果)来进一步调试,得到的结果可能会包含额外的开头和结尾字符串。

对应的解决方案是,利用字符串查找,找到开头的 '[' 和结尾的 ']',进行截取。

最后,将 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 会显著降低接口响应速度。

3.后端开发

按照上述方案,进行编码实现。

本节先使用同步的方式调用 AI,后续会进行优化。

1)AI 生成题目请求类参数:

/**
 * AI 生成题目请求
 */
@Data
public class AiGenerateQuestionRequest implements Serializable {

    /**
     * 应用 id
     */
    private Long appId;

    /**
     * 题目数
     */
    int questionNumber = 10;

    /**
     * 选项数
     */
    int optionNumber = 2;

    private static final long serialVersionUID = 1L;
}

2)定义模板常量和构造用户模板的方法(controller):

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 数组";

    /**
     * 生成题目的用户消息
     *
     * @param app
     * @param questionNumber
     * @param optionNumber
     * @return
     */
    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 生成接口(不写在service层的接口,业务不复杂):

 @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.doSyncRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMessage, null);
        // 截取需要的 JSON 信息
        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);
    }

4.前端开发

效果如图:

1.抽屉组件

允许用户填写生成参数并执行,生成成功后回调题目页面(父组件)的函数,插入题目。

代码如下:

<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
            min="0"
            max="20"
            v-model="form.questionNumber"
            placeholder="请输入题目数量"
          />
        </a-form-item>
        <a-form-item field="optionNumber" label="选项数量">
          <a-input-number
            min="0"
            max="6"
            v-model="form.optionNumber"
            placeholder="请输入选项数量"
          />
        </a-form-item>
        <a-form-item>
          <a-button
            :loading="submitting"
            type="primary"
            html-type="submit"
            style="width: 120px"
          >
            {{ submitting ? "生成中" : "一键生成" }}
          </a-button>
        </a-form-item>
      </a-form>
    </div>
  </a-drawer>
</template>

<script setup lang="ts">
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 form = reactive({
  optionNumber: 2,
  questionNumber: 10,
} as API.AiGenerateQuestionRequest);

const visible = ref(false);
const submitting = ref(false);

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;
};
</script>

编写 AI 生成题目成功的处理,插入题目:

/**
 * AI 生成题目成功后执行
 */
const onAiGenerateSuccess = (result: API.QuestionContentDTO[]) => {
  message.success(`AI 生成题目成功,生成 ${result.length} 道题目`);
  questionContent.value = [...questionContent.value, ...result];
};

2.引入组件

设置题目页面引入抽屉组件:

5.验证测试

1)AddAppPage 创建应用成功后无法正确跳转到详情页 Bug

因为 id 的默认值为空字符串导致,需将 "??" 改为 "||",代码如下:

2)AI 生成中断,需要调整请求配置中的超时时间

3)实际测试的生成结果有点太飘(随机)了,如图:

还是将随机性调整为默认值:

// AI 生成
String result = aiManager.doSyncRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMessage, null);

调整参数后,生成的结果正常多了:

6.扩展思路

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,因为一次调用后socket通道已经被关闭,复用老的 request 会复用老链接会报错
    ChatCompletionRequest chatCompletionRequest = ChatGLMUtil.buildSyncUnStableRequest(appAiSysMessageConfig, String.format(appAiUserMessageConfig, Math.min(number, 10)));
    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.方案设计

1、明确输入参数

明确需要提供给 AI 的参数列表:

  1. 应用信息,包括应用名称、描述
  2. 题目信息
  3. 用户答案

但是需要注意,题目数量可能会很多,如果将完整的题目结构(包括选项列表)输入给 AI,可能会超出最大 token 限制,所以可以进行优化。只存储用户答案对应的选项。

示例结构如下:

2、明确返回结果

需要 JSON 格式的评分结果对象,比如:

3、编写和调试 Prompt

遵循 Prompt 工程指南中的技巧,编写出系统 Prompt 和用户 Prompt。

系统 Prompt 如下:

用户 Prompt 按照顺序提供信息即可,示例 Prompt 如下。

测评类应用:

用到的技巧:

  1. 定义 System Prompt
  2. 让 GLM 进行角色扮演
  3. 使用分隔符标示不同的输入部分
  4. 少样本学习
  5. 指定输出长度的示例
  6. 将复杂任务分解为简单的子任务
  7. 指定固定的输出格式
  8. 通过按顺序输入参数来节约空间。
  9. 对于描述这种多行内容,可以通过特殊字符括起来,防止用户的输入干扰生成结果。

利用界面进行初步调试,可以尝试修改随机性:

然后可以通过编写单元测试来调试。

得到结果后,仍然需要利用字符串查找,找到开头的 '{' 和结尾的 '}',截取到实际内容。

2.后端开发

1)编写题目答案封装类:

/**
 * 题目答案封装类(用于 AI 评分)
 */
@Data
public class QuestionAnswerDTO {

    /**
     * 题目
     */
    private String title;

    /**
     * 用户答案
     */
    private String userAnswer;
}

2)编写 AI 测评类应用评分策略类,需要指定对应的注解:

@ScoringStrategyConfig(appType = 1, scoringStrategy = 1)

定义模板常量和构造用户模板的方法:

    /**
     * AI 评分系统消息
     */
    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 对象";
    /**
     * AI 评分用户消息封装
     *
     * @param app
     * @param questionContentDTOList
     * @param choices
     * @return
     */
    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);
        // 截取需要的 JSON 信息
        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;
    }

3.前端开发

前端不需要开发新的页面,给做题页面的“查看结果”按钮补充一个提交等待的 loading 效果即可。

4.验证测试

效果如图:

可以多次测试,调试 Prompt 和模型参数,以便获得更好的结果。

5.扩展思路

1)实现得分类应用的 AI 评分,可以给 AI 多传递每道题目正确的得分参数。

2)支持用户额外定义结果集,将 AI 生成的结果限定在一个范围内,更精确。

  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值