src/main/java/cc/mrbird/febs/ai/controller/memberTalk/ApiMemberTalkController.java
New file @@ -0,0 +1,54 @@ package cc.mrbird.febs.ai.controller.memberTalk; import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto; import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto; import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkVo; import cc.mrbird.febs.ai.service.AiMemberTalkService; import cc.mrbird.febs.common.entity.FebsResponse; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author Administrator */ @Slf4j @Validated @RestController @RequiredArgsConstructor @RequestMapping(value = "/api/ai/memberTalk") @Api(value = "ApiMemberTalkController", tags = "AI-用户陪练") public class ApiMemberTalkController { private final AiMemberTalkService aiMemberTalkService; @ApiOperation(value = "开始陪练", notes = "开始陪练") @ApiResponses({ @ApiResponse(code = 200, message = "success", response = ApiMemberTalkVo.class) }) @PostMapping(value = "/start") public FebsResponse start(@RequestBody @Validated ApiMemberTalkDto dto) { return aiMemberTalkService.start(dto); } @ApiOperation(value = "回答", notes = "回答") @ApiResponses({ @ApiResponse(code = 200, message = "success", response = ApiMemberTalkVo.class) }) @PostMapping(value = "/answer") public FebsResponse answer(@RequestBody @Validated ApiMemberTalkAnswerDto dto) { return aiMemberTalkService.answer(dto); } } src/main/java/cc/mrbird/febs/ai/req/memberTalk/ApiMemberTalkAnswerDto.java
New file @@ -0,0 +1,26 @@ package cc.mrbird.febs.ai.req.memberTalk; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotBlank; /** * @author Administrator */ @Data @ApiModel(value = "ApiMemberTalkAnswerDto", description = "参数") public class ApiMemberTalkAnswerDto { @NotBlank(message = "会话ID不能为空") @ApiModelProperty(value = "会话ID", example = "10") private String id; @NotBlank(message = "回答不能为空") @ApiModelProperty(value = "回答", example = "10") private String reqContext; } src/main/java/cc/mrbird/febs/ai/req/memberTalk/ApiMemberTalkDto.java
New file @@ -0,0 +1,21 @@ package cc.mrbird.febs.ai.req.memberTalk; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotBlank; /** * @author Administrator */ @Data @ApiModel(value = "ApiMemberTalkDto", description = "参数") public class ApiMemberTalkDto { @NotBlank(message = "ID不能为空") @ApiModelProperty(value = "产品ID", example = "10") private String id; } src/main/java/cc/mrbird/febs/ai/res/ai/AiResponse.java
New file @@ -0,0 +1,16 @@ package cc.mrbird.febs.ai.res.ai; import io.swagger.annotations.ApiModel; import lombok.Data; /** * @author Administrator */ @Data @ApiModel(value = "AiResponse", description = "参数") public class AiResponse { private String code; private String description; private String resContext; } src/main/java/cc/mrbird/febs/ai/res/memberTalk/ApiMemberTalkVo.java
New file @@ -0,0 +1,35 @@ package cc.mrbird.febs.ai.res.memberTalk; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author Administrator */ @Data @ApiModel(value = "ApiMemberTalkVo", description = "参数") public class ApiMemberTalkVo { /** * 用户对话ID (UUID) */ @ApiModelProperty(value = "会话ID") private String memberTalkId; /** * 类型 1-AI提问 2-用户回答 3-AI分析结果 */ @ApiModelProperty(value = "类型 1-AI提问 2-用户回答 3-AI分析结果") private Integer type; /** * 内容 */ @ApiModelProperty(value = "内容") private String context; } src/main/java/cc/mrbird/febs/ai/service/AiMemberTalkItemService.java
@@ -1,7 +1,10 @@ package cc.mrbird.febs.ai.service; import cc.mrbird.febs.ai.entity.AiMemberTalkItem; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.IService; import java.util.Date; import java.util.List; /** @@ -13,4 +16,7 @@ public interface AiMemberTalkItemService extends IService<AiMemberTalkItem> { void add(String memberUuid, String id, int type, String resContext, Date createdTime); AiMemberTalkItem getByQuery(LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery); } src/main/java/cc/mrbird/febs/ai/service/AiMemberTalkService.java
@@ -1,8 +1,13 @@ package cc.mrbird.febs.ai.service; import cc.mrbird.febs.ai.entity.AiMemberTalk; import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto; import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto; import cc.mrbird.febs.common.entity.FebsResponse; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; import java.util.Date; /** * AI用户对话训练记录 Service接口 @@ -13,4 +18,15 @@ public interface AiMemberTalkService extends IService<AiMemberTalk> { AiMemberTalk getById(String id); FebsResponse start(ApiMemberTalkDto dto); AiMemberTalk getByQuery(LambdaQueryWrapper<AiMemberTalk> query); void updateTimeUpdate(Date nowTime, String id); FebsResponse answer(ApiMemberTalkAnswerDto dto); AiMemberTalk add(String memberUuid, String productId, Date nowTime); } src/main/java/cc/mrbird/febs/ai/service/AiProductRoleLinkService.java
@@ -1,10 +1,15 @@ package cc.mrbird.febs.ai.service; import cc.mrbird.febs.ai.entity.AiMemberTalk; import cc.mrbird.febs.ai.entity.AiProductRoleLink; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.IService; /** * @author Administrator */ public interface AiProductRoleLinkService extends IService<AiProductRoleLink> { AiProductRoleLink getByQuery(LambdaQueryWrapper<AiProductRoleLink> query); } src/main/java/cc/mrbird/febs/ai/service/AiService.java
New file @@ -0,0 +1,14 @@ package cc.mrbird.febs.ai.service; import cc.mrbird.febs.ai.res.ai.AiResponse; /** * @author Administrator */ public interface AiService { AiResponse start(String productRoleId, String content); AiResponse question(String promptTemplate,String linkId,String content); } src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkItemServiceImpl.java
@@ -3,6 +3,7 @@ import cc.mrbird.febs.ai.entity.AiMemberTalkItem; import cc.mrbird.febs.ai.mapper.AiMemberTalkItemMapper; import cc.mrbird.febs.ai.service.AiMemberTalkItemService; import cc.mrbird.febs.ai.utils.UUID; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; @@ -10,6 +11,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; /** @@ -26,4 +28,20 @@ private final AiMemberTalkItemMapper aiMemberTalkItemMapper; @Override public void add(String memberUuid, String id, int type, String resContext,Date createdTime) { AiMemberTalkItem aiMemberTalkItem = new AiMemberTalkItem(); aiMemberTalkItem.setId(UUID.getSimpleUUIDString()); aiMemberTalkItem.setCreatedTime(createdTime); aiMemberTalkItem.setMemberId(memberUuid); aiMemberTalkItem.setMemberTalkId(id); aiMemberTalkItem.setType(type); aiMemberTalkItem.setContext(resContext); aiMemberTalkItemMapper.insert(aiMemberTalkItem); } @Override public AiMemberTalkItem getByQuery(LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery) { return aiMemberTalkItemMapper.selectOne(memberTalkItemQuery); } } src/main/java/cc/mrbird/febs/ai/service/impl/AiMemberTalkServiceImpl.java
@@ -1,16 +1,30 @@ package cc.mrbird.febs.ai.service.impl; import cc.mrbird.febs.ai.entity.AiMemberTalk; import cc.mrbird.febs.ai.entity.AiMemberTalkItem; import cc.mrbird.febs.ai.entity.AiProductRoleLink; import cc.mrbird.febs.ai.mapper.AiMemberTalkMapper; import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkAnswerDto; import cc.mrbird.febs.ai.req.memberTalk.ApiMemberTalkDto; import cc.mrbird.febs.ai.res.ai.AiResponse; import cc.mrbird.febs.ai.res.memberTalk.ApiMemberTalkVo; import cc.mrbird.febs.ai.service.AiMemberTalkItemService; import cc.mrbird.febs.ai.service.AiMemberTalkService; import cc.mrbird.febs.ai.service.AiProductRoleLinkService; import cc.mrbird.febs.ai.service.AiService; import cc.mrbird.febs.ai.utils.UUID; import cc.mrbird.febs.common.entity.FebsResponse; import cc.mrbird.febs.common.utils.LoginUserUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Date; /** * AI用户对话训练记录 Service实现类 @@ -24,6 +38,125 @@ public class AiMemberTalkServiceImpl extends ServiceImpl<AiMemberTalkMapper, AiMemberTalk> implements AiMemberTalkService { private final AiMemberTalkMapper aiMemberTalkMapper; private final AiProductRoleLinkService aiProductRoleLinkService; private final AiMemberTalkItemService aiMemberTalkItemService; private final AiService aiService; @Override public AiMemberTalk getById(String id) { return aiMemberTalkMapper.selectById( id); } @Override public FebsResponse start(ApiMemberTalkDto dto) { String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid(); String productId = dto.getId(); LambdaQueryWrapper<AiProductRoleLink> productLinkQuery = Wrappers.lambdaQuery(AiProductRoleLink.class); productLinkQuery.eq(AiProductRoleLink::getProductId,productId); productLinkQuery.last("limit 1"); AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery); if(ObjectUtil.isNull(aiProductRoleLink)){ throw new RuntimeException("产品AI陪练不存在"); } Date nowTime = new Date(); LambdaQueryWrapper<AiMemberTalk> query = Wrappers.lambdaQuery(AiMemberTalk.class); query.eq(AiMemberTalk::getMemberId,memberUuid); query.eq(AiMemberTalk::getProductId,productId); query.last("limit 1"); AiMemberTalk aiMemberTalk = this.getByQuery(query); if (ObjectUtil.isNull(aiMemberTalk)){ aiMemberTalk = this.add(memberUuid,productId,nowTime); } AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(),"开始出题"); if(aiResponse.getCode().equals("200")){ aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),1,aiResponse.getResContext(),nowTime); this.updateTimeUpdate(nowTime,aiMemberTalk.getId()); }else{ throw new RuntimeException(aiResponse.getDescription()); } ApiMemberTalkVo apiMemberTalkVo = new ApiMemberTalkVo(); apiMemberTalkVo.setMemberTalkId(aiMemberTalk.getId()); apiMemberTalkVo.setType(1); apiMemberTalkVo.setContext(aiResponse.getResContext()); return new FebsResponse().success().data(apiMemberTalkVo); } @Override public AiMemberTalk getByQuery(LambdaQueryWrapper<AiMemberTalk> query) { return aiMemberTalkMapper.selectOne( query); } @Override public void updateTimeUpdate(Date nowTime, String id) { aiMemberTalkMapper.update( null, Wrappers.lambdaUpdate(AiMemberTalk.class) .set(AiMemberTalk::getUpdatedTime,nowTime) .eq(AiMemberTalk::getId,id)); } public static final String ANSWER_FORMAT = "{}/n回答:{}/n请根据回答给出以下四个方面的总结,这个四个方面分别是亮点、建议、参考答案和核心知识点回顾。重点:四个方面的总结都是必须要有内容。"; @Override public FebsResponse answer(ApiMemberTalkAnswerDto dto) { String memberUuid = LoginUserUtil.getLoginUser().getMemberUuid(); String memberTalkId = dto.getId(); AiMemberTalk aiMemberTalk = this.getById(memberTalkId); if (ObjectUtil.isNull(aiMemberTalk)){ throw new RuntimeException("产品AI陪练对话不存在"); } LambdaQueryWrapper<AiProductRoleLink> productLinkQuery = Wrappers.lambdaQuery(AiProductRoleLink.class); productLinkQuery.eq(AiProductRoleLink::getProductId,aiMemberTalk.getProductId()); productLinkQuery.last("limit 1"); AiProductRoleLink aiProductRoleLink = aiProductRoleLinkService.getByQuery(productLinkQuery); if(ObjectUtil.isNull(aiProductRoleLink)){ throw new RuntimeException("产品AI陪练不存在"); } String reqContext = dto.getReqContext(); LambdaQueryWrapper<AiMemberTalkItem> memberTalkItemQuery = Wrappers.lambdaQuery(AiMemberTalkItem.class); memberTalkItemQuery.eq(AiMemberTalkItem::getMemberId,memberUuid); memberTalkItemQuery.eq(AiMemberTalkItem::getMemberTalkId,memberTalkId); memberTalkItemQuery.eq(AiMemberTalkItem::getType,1); memberTalkItemQuery.orderByDesc(AiMemberTalkItem::getCreatedTime); memberTalkItemQuery.last("limit 1"); AiMemberTalkItem aiMemberTalkItem = aiMemberTalkItemService.getByQuery(memberTalkItemQuery); aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),2,reqContext,new Date()); String format = StrUtil.format(ANSWER_FORMAT, aiMemberTalkItem.getContext(), reqContext); log.info("format:{}",format); AiResponse aiResponse = aiService.start(aiProductRoleLink.getProductRoleId(), format); if(aiResponse.getCode().equals("200")){ Date nowTime = new Date(); aiMemberTalkItemService.add(memberUuid,aiMemberTalk.getId(),3,aiResponse.getResContext(),nowTime); this.updateTimeUpdate(nowTime,aiMemberTalk.getId()); }else{ throw new RuntimeException(aiResponse.getDescription()); } ApiMemberTalkVo apiMemberTalkVo = new ApiMemberTalkVo(); apiMemberTalkVo.setMemberTalkId(aiMemberTalk.getId()); apiMemberTalkVo.setType(1); apiMemberTalkVo.setContext(aiResponse.getResContext()); return new FebsResponse().success().data(apiMemberTalkVo); } @Override public AiMemberTalk add(String memberUuid, String productId, Date nowTime) { AiMemberTalk aiMemberTalk = new AiMemberTalk(); aiMemberTalk.setId(UUID.getSimpleUUIDString()); aiMemberTalk.setCreatedTime(nowTime); aiMemberTalk.setMemberId(memberUuid); aiMemberTalk.setProductId(productId); aiMemberTalkMapper.insert(aiMemberTalk); return aiMemberTalk; } } src/main/java/cc/mrbird/febs/ai/service/impl/AiProductRoleLinkServiceImpl.java
@@ -1,8 +1,10 @@ package cc.mrbird.febs.ai.service.impl; import cc.mrbird.febs.ai.entity.AiMemberTalk; import cc.mrbird.febs.ai.entity.AiProductRoleLink; import cc.mrbird.febs.ai.mapper.AiProductRoleLinkMapper; import cc.mrbird.febs.ai.service.AiProductRoleLinkService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,4 +17,10 @@ @Service @RequiredArgsConstructor public class AiProductRoleLinkServiceImpl extends ServiceImpl<AiProductRoleLinkMapper, AiProductRoleLink> implements AiProductRoleLinkService { private final AiProductRoleLinkMapper aiProductRoleLinkMapper; @Override public AiProductRoleLink getByQuery(LambdaQueryWrapper<AiProductRoleLink> query) { return aiProductRoleLinkMapper.selectOne( query); } } src/main/java/cc/mrbird/febs/ai/service/impl/AiServiceImpl.java
New file @@ -0,0 +1,147 @@ package cc.mrbird.febs.ai.service.impl; import cc.mrbird.febs.ai.entity.AiProductRole; import cc.mrbird.febs.ai.res.ai.AiResponse; import cc.mrbird.febs.ai.service.AiProductRoleService; import cc.mrbird.febs.ai.service.AiService; import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionChoice; import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest; import com.volcengine.ark.runtime.model.completion.chat.ChatMessage; import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole; import com.volcengine.ark.runtime.service.ArkService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import okhttp3.ConnectionPool; import okhttp3.Dispatcher; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * @author Administrator */ @Slf4j @Service @RequiredArgsConstructor public class AiServiceImpl implements AiService { private static final String CODE_SUCCESS = "200"; private static final String CODE_NOT_FOUND = "201"; private static final String CODE_ERROR = "500"; private final AiProductRoleService aiProductRoleService; @Value("${ai.service.ak}") private String ak; @Value("${ai.service.sk}") private String sk; @Value("${ai.service.base-url}") private String baseUrl; private ArkService service; @PostConstruct public void init() { ConnectionPool connectionPool = new ConnectionPool(10, 30, TimeUnit.SECONDS); Dispatcher dispatcher = new Dispatcher(); this.service = ArkService.builder() .dispatcher(dispatcher) .connectionPool(connectionPool) .baseUrl(baseUrl) .ak(ak) .sk(sk) .build(); } @PreDestroy public void destroy() { if (service != null) { service.shutdownExecutor(); } } @Override public AiResponse start(String productRoleId, String content) { if (!StringUtils.hasText(productRoleId)) { log.warn("productRoleId 不能为空"); return buildErrorResponse(CODE_NOT_FOUND, "AI陪练不存在"); } AiProductRole aiProductRole = aiProductRoleService.getById(productRoleId); if (aiProductRole == null) { log.warn("未找到对应的角色配置,productRoleId: {}", productRoleId); return buildErrorResponse(CODE_NOT_FOUND, "AI陪练不存在"); } String promptTemplate = aiProductRole.getPromptTemplate(); String linkId = aiProductRole.getLinkId(); if (!StringUtils.hasText(promptTemplate) || !StringUtils.hasText(linkId)) { log.warn("角色配置不完整,promptTemplate 或 linkId 为空,productRoleId: {}", productRoleId); return buildErrorResponse(CODE_ERROR, "角色配置不完整"); } return question(promptTemplate, linkId, content); } @Override public AiResponse question(String promptTemplate, String linkId, String content) { if (!StringUtils.hasText(promptTemplate) || !StringUtils.hasText(linkId) || !StringUtils.hasText(content)) { log.warn("请求参数不完整,promptTemplate: {}, linkId: {}, content: {}", promptTemplate, linkId, content); return buildErrorResponse(CODE_ERROR, "请求参数不完整"); } final List<ChatMessage> messages = new ArrayList<>(); final ChatMessage systemMessage = ChatMessage.builder().role(ChatMessageRole.SYSTEM).content(promptTemplate).build(); final ChatMessage userMessage = ChatMessage.builder().role(ChatMessageRole.USER).content(content).build(); messages.add(systemMessage); messages.add(userMessage); ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() .model(linkId) .messages(messages) .temperature(1.0) .topP(0.7) .maxTokens(4096) .frequencyPenalty(0.0) .build(); try { List<ChatCompletionChoice> choices = service.createChatCompletion(chatCompletionRequest).getChoices(); String result = choices.stream() .map(choice -> choice.getMessage().getContent()) .filter(contentObj -> contentObj != null) .map(Object::toString) .collect(Collectors.joining()); return buildSuccessResponse(result); } catch (Exception e) { log.error("调用AI服务失败,modelId: {}, content: {}", linkId, content, e); return buildErrorResponse(CODE_ERROR, "AI服务调用失败"); } } private AiResponse buildErrorResponse(String code, String description) { AiResponse response = new AiResponse(); response.setCode(code); response.setDescription(description); return response; } private AiResponse buildSuccessResponse(String result) { AiResponse response = new AiResponse(); response.setCode(CODE_SUCCESS); response.setDescription("成功"); response.setResContext(result); return response; } } src/main/java/cc/mrbird/febs/mall/controller/HSController.java
@@ -18,7 +18,7 @@ // 从环境变量中获取您的Key鉴权。此为默认方式,您可根据需要进行修改 private static String ak = "AKLTZTQxZjMyZTUxMWJmNDEyNDkzNWExOGQ3ODllNzhhNmQ"; private static String sk = "TmpFeE1qZ3haREExTW1JeE5HRTBZVGc1WlRRNVlqWXpORGd5TWpsak5HWQ=="; private static String ep_id = "ep-20250728114932-429wg"; private static String ep_id = "ep-20250805124033-lhxbf"; // 此为默认路径,您可根据业务所在地域进行配置 static String baseUrl = "https://ark.cn-beijing.volces.com/api/v3"; static ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS); src/main/resources/application-test.yml
@@ -89,4 +89,10 @@ path: /mnt/sdc/webresource/file/ system: job: false job: false ai: service: ak: AKLTZTQxZjMyZTUxMWJmNDEyNDkzNWExOGQ3ODllNzhhNmQ sk: TmpFeE1qZ3haREExTW1JeE5HRTBZVGc1WlRRNVlqWXpORGd5TWpsak5HWQ== base-url: https://ark.cn-beijing.volces.com/api/v3