【代码评审】AI:调整 message 接口

This commit is contained in:
zhijiantianya@gmail.com 2024-05-06 22:28:56 +08:00
parent 2df0babc36
commit aea7cc000b
18 changed files with 137 additions and 283 deletions

View File

@ -1,50 +0,0 @@
package cn.iocoder.yudao.module.ai.controller;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ai.service.AiChatService;
import cn.iocoder.yudao.module.ai.vo.AiChatReq;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* ia 模块
*
* @author fansili
* @time 2024/4/13 17:44
* @since 1.0
*/
@Tag(name = "A1-AI聊天")
@RestController
@RequestMapping("/ai")
@Slf4j
@AllArgsConstructor
public class AiChatController {
@Autowired
private final AiChatService chatService;
@Operation(summary = "聊天-chat", description = "这个一般等待时间比较久,需要全部完成才会返回!")
@GetMapping("/chat")
public CommonResult<String> chat(@Validated @ModelAttribute AiChatReq req) {
return CommonResult.success(chatService.chat(req));
}
// TODO @芋艿调用这个方法异常Unable to handle the Spring Security Exception because the response is already committed.
@Operation(summary = "聊天-stream", description = "这里跟通义千问一样采用的是 Server-Sent Events (SSE) 通讯模式")
@GetMapping(value = "/chatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(@Validated @ModelAttribute AiChatReq req) {
Utf8SseEmitter sseEmitter = new Utf8SseEmitter();
chatService.chatStream(req, sseEmitter);
return sseEmitter;
}
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.yudao.module.ai.controller;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.service.AiChatMessageService;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageListRes;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageReq;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* chat message
*
* @author fansili
* @time 2024/4/24 17:22
* @since 1.0
*/
@Tag(name = "A3-聊天-对话")
@RestController
@RequestMapping("/ai/chat/message")
@Slf4j
@AllArgsConstructor
public class AiChatMessageController {
private final AiChatMessageService chatMessageService;
@Operation(summary = "聊天记录", description = "查询个人的聊天记录")
@GetMapping("/list")
public PageResult<AiChatMessageListRes> list(@Validated @ModelAttribute AiChatMessageReq req) {
return chatMessageService.list(req);
}
@Operation(summary = "聊天记录 - 删除", description = "删除记录")
@DeleteMapping("/{chatConversationId}/{id}")
public CommonResult delete(@PathVariable("chatConversationId") Long chatConversationId,
@PathVariable("id") Long id) {
chatMessageService.delete(chatConversationId, id);
return CommonResult.success(null);
}
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.ai.controller.admin.chat;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
import cn.iocoder.yudao.module.ai.service.AiChatService;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
// TODO @芋艿权限标识
@Tag(name = "管理后台 - 聊天消息")
@RestController
@RequestMapping("/ai/chat/message")
@Slf4j
public class AiChatMessageController {
@Resource
private AiChatService chatService;
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
@PostMapping("/send")
public CommonResult<AiChatMessageRespVO> sendMessage(@Validated @ModelAttribute AiChatMessageSendReqVO sendReqVO) {
// TODO @fan使用 static import这样就 success 就行了
return success(null);
}
// TODO @芋艿调用这个方法异常Unable to handle the Spring Security Exception because the response is already committed.可以再试试
// TODO @fan要不要使用 Flux 来返回可以使用 Flux<AiChatMessageRespVO>
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter sendMessageStream(@Validated @ModelAttribute AiChatMessageSendReqVO sendReqVO) {
Utf8SseEmitter sseEmitter = new Utf8SseEmitter();
chatService.chatStream(sendReqVO, sseEmitter);
return sseEmitter;
}
@Operation(summary = "获得指定会话的消息列表")
@GetMapping("/list-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
public CommonResult<List<AiChatMessageRespVO>> getMessageListByConversationId(@RequestParam("conversationId") Long conversationId) {
return success(null);
}
@Operation(summary = "删除消息")
@DeleteMapping("/delete")
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
public CommonResult<Boolean> delete(@RequestParam("id") Long id) {
return success(null);
}
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 聊天消息 Response VO")
@Data
public class AiChatMessageRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long conversationId;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
private String type; // 参见 MessageType 枚举类
@Schema(description = "用户编号", example = "4096")
private Long userId; // 仅当 user 发送时非空
@Schema(description = "角色编号", example = "888")
private Long roleId; // 仅当 assistant 回复时非空
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo")
private String model; // 参见 AiOpenAiModelEnum 枚举类
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
private Long modelId;
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
private String content;
@Schema(description = "消耗 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
private Integer tokens;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.experimental.Accessors;
@Schema(description = "管理后台 - AI 聊天消息发送 Request VO")
@Data
public class AiChatMessageSendReqVO {
@Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "聊天对话编号不能为空")
private Long conversationId;
@Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法")
@NotEmpty(message = "聊天内容不能为空")
private String content;
}

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.ai.convert;
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageListRes;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@ -25,5 +25,5 @@ public interface AiChatMessageConvert {
* @param list
* @return
*/
List<AiChatMessageListRes> convert(List<AiChatMessageDO> list);
List<AiChatMessageRespVO> convert(List<AiChatMessageDO> list);
}

View File

@ -81,7 +81,7 @@ public class AiChatMessageDO extends BaseDO {
/**
* 消耗 Token 数量
*/
private Integer usedTokens;
private Integer tokens;
// TODO 芋艿是否作为上下文语料use_context待定

View File

@ -1,7 +0,0 @@
package cn.iocoder.yudao.module.ai.dal;
/**
* @author fansili
* @time 2024/4/25 15:36
* @since 1.0
*/

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.module.ai.service;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageListRes;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageReq;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
/**
* chat message
@ -19,7 +18,7 @@ public interface AiChatMessageService {
* @param req
* @return
*/
PageResult<AiChatMessageListRes> list(AiChatMessageReq req);
PageResult<AiChatMessageRespVO> list(AiChatMessageReq req);
/**
* message - 删除

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.ai.service;
import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter;
import cn.iocoder.yudao.module.ai.vo.AiChatReq;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
/**
* 聊天 chat
@ -18,7 +18,7 @@ public interface AiChatService {
* @param req
* @return
*/
String chat(AiChatReq req);
String chat(AiChatMessageSendReqVO req);
/**
* chat stream
@ -27,5 +27,5 @@ public interface AiChatService {
* @param sseEmitter
* @return
*/
void chatStream(AiChatReq req, Utf8SseEmitter sseEmitter);
void chatStream(AiChatMessageSendReqVO req, Utf8SseEmitter sseEmitter);
}

View File

@ -11,8 +11,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
import cn.iocoder.yudao.module.ai.mapper.AiChatConversationMapper;
import cn.iocoder.yudao.module.ai.mapper.AiChatMessageMapper;
import cn.iocoder.yudao.module.ai.service.AiChatMessageService;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageListRes;
import cn.iocoder.yudao.module.ai.vo.AiChatMessageReq;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -35,7 +34,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
private final AiChatConversationMapper aiChatConversationMapper;
@Override
public PageResult<AiChatMessageListRes> list(AiChatMessageReq req) {
public PageResult<AiChatMessageRespVO> list(AiChatMessageReq req) {
// 查询
LambdaQueryWrapperX<AiChatMessageDO> queryWrapperX = new LambdaQueryWrapperX<>();
queryWrapperX.eq(AiChatMessageDO::getConversationId, req.getChatConversationId());
@ -43,7 +42,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
queryWrapperX.orderByDesc(AiChatMessageDO::getId);
PageResult<AiChatMessageDO> pageResult = aiChatMessageMapper.selectPage(req, queryWrapperX);
// 转换 res
List<AiChatMessageListRes> messageListResList = AiChatMessageConvert.INSTANCE.convert(pageResult.getList());
List<AiChatMessageRespVO> messageListResList = AiChatMessageConvert.INSTANCE.convert(pageResult.getList());
return new PageResult(messageListResList, pageResult.getTotal());
}

View File

@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.ai.mapper.AiChatRoleMapper;
import cn.iocoder.yudao.module.ai.service.AiChatConversationService;
import cn.iocoder.yudao.module.ai.service.AiChatService;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO;
import cn.iocoder.yudao.module.ai.vo.AiChatReq;
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
@ -53,7 +53,7 @@ public class AiChatServiceImpl implements AiChatService {
* @return
*/
@Transactional(rollbackFor = Exception.class)
public String chat(AiChatReq req) {
public String chat(AiChatMessageSendReqVO req) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
// 获取 client 类型
AiPlatformEnum platformEnum = AiPlatformEnum.valueOfPlatform(req.getModal());
@ -83,7 +83,7 @@ public class AiChatServiceImpl implements AiChatService {
return content;
}
private void saveChatMessage(AiChatReq req, AiChatConversationRespVO conversationRes, Long loginUserId) {
private void saveChatMessage(AiChatMessageSendReqVO req, AiChatConversationRespVO conversationRes, Long loginUserId) {
Long chatConversationId = conversationRes.getId();
// 增加 chat message 记录
aiChatMessageMapper.insert(
@ -101,7 +101,7 @@ public class AiChatServiceImpl implements AiChatService {
aiChatConversationMapper.updateIncrChatCount(req.getConversationId());
}
public void saveSystemChatMessage(AiChatReq req, AiChatConversationRespVO conversationRes, Long loginUserId, String systemPrompts) {
public void saveSystemChatMessage(AiChatMessageSendReqVO req, AiChatConversationRespVO conversationRes, Long loginUserId, String systemPrompts) {
Long chatConversationId = conversationRes.getId();
// 增加 chat message 记录
aiChatMessageMapper.insert(
@ -128,7 +128,7 @@ public class AiChatServiceImpl implements AiChatService {
* @return
*/
@Override
public void chatStream(AiChatReq req, Utf8SseEmitter sseEmitter) {
public void chatStream(AiChatMessageSendReqVO req, Utf8SseEmitter sseEmitter) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
// 获取 client 类型
AiPlatformEnum platformEnum = AiPlatformEnum.valueOfPlatform(req.getModal());

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.module.ai.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* chat message list req
*
* @author fansili
* @time 2024/4/14 16:12
* @since 1.0
*/
@Data
@Accessors(chain = true)
public class AiChatMessageDeleteReq extends PageParam {
@Schema(description = "id")
@NotNull
private Long id;
@Schema(description = "聊天ID关联到特定的会话或对话")
@NotNull
private Long chatConversationId;
}

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.ai.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 看板 message list req
*
* @author fansili
* @time 2024/4/24 17:28
* @since 1.0
*/
@Data
@Accessors(chain = true)
public class AiChatMessageListRes {
@Schema(description = "编号")
private Long id;
@Schema(description = "聊天ID关联到特定的会话或对话")
private Long chatConversationId;
@Schema(description = "角色ID用于标识发送消息的用户或系统的身份")
private Long userId;
@Schema(description = "消息具体内容,存储用户的发言或者系统响应的文字信息")
private String message;
@Schema(description = "消息类型,枚举值可能包括'system'(系统消息)、'user'(用户消息)和'assistant'(助手消息)")
private String messageType;
@Schema(description = "在生成消息时采用的Top-K采样大小")
private Double topK;
@Schema(description = "Top-P核采样方法的概率阈值")
private Double topP;
@Schema(description = "温度参数,用于调整生成回复的随机性和多样性程度,")
private Double temperature;
}

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.ai.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* chat message list req
*
* @author fansili
* @time 2024/4/14 16:12
* @since 1.0
*/
@Data
@Accessors(chain = true)
public class AiChatMessageReq extends PageParam {
@Schema(description = "聊天ID关联到特定的会话或对话")
@NotNull
private Long chatConversationId;
}

View File

@ -1,48 +0,0 @@
package cn.iocoder.yudao.module.ai.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* chat req
*
* @author fansili
* @time 2024/4/14 16:12
* @since 1.0
*/
@Data
@Accessors(chain = true)
public class AiChatReq {
@Schema(description = "ai模型(查看 AiClientNameEnum)")
@NotNull(message = "模型不能为空!")
@Size(max = 30, message = "模型字符最大 30个字符!")
private String modal;
@Schema(description = "对话Id")
@NotNull(message = "对话id不能为空")
private Long conversationId;
@Schema(description = "chat角色模板")
private Long chatRoleId;
@NotNull(message = "提示词不能为空!")
@Size(max = 5000, message = "提示词最大5000个字符!")
@Schema(description = "填入固定值1 issues, 2 pr")
private String prompt;
@Schema(description = "用于控制随机性和多样性的温度参数")
private Double temperature;
@Schema(description = "生成时核采样方法的概率阈值。例如取值为0.8时仅保留累计概率之和大于等于0.8的概率分布中的token\n" +
" * 作为随机采样的候选集。取值范围为0,1.0),取值越大,生成的随机性越高;取值越低,生成的随机性越低。\n" +
" * 默认值为0.8。注意取值不要大于等于1\n")
private Double topP;
@Schema(description = "在生成消息时采用的Top-K采样大小表示模型生成回复时考虑的候选项集合的大小")
private Double topK;
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.ai.vo;
import cn.iocoder.yudao.module.ai.enums.AiOpenAiModelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
// TODO done @fansili 1swagger 注释不太对2有了 swagger 注释就不用类注释了
@Data
@Schema(description = "用户 App - 上传文件 Request VO")
public class AiChatReqVO {
@Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "提示词不能为空!")
private String prompt;
@Schema(description = "AI模型", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "AI模型不能为空")
private AiOpenAiModelEnum aiModel;
}