陈昶聿
4 天以前 05c82d89b6df8c236feb0e4dc3f83f18e8414df0
【市一】大模型
已修改3个文件
已添加1个文件
321 ■■■■■ 文件已修改
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/java/com/smartor/common/QwenLLMUtil.java 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/smartor/ServiceSubtaskController.java
@@ -883,6 +883,16 @@
        ryTask.dealOutHospInfo();
    }
    @PostMapping("/compensateTasktest")
    public void compensateTasktest(@RequestParam("subId")Long subId) {
        ryTask.compensateTaskTest(subId);
    }
    @PostMapping("/longTaskSendtest")
    public void longTaskSendtest(@RequestParam("subId")Long subId) {
        ryTask.longTaskSendTest(subId);
    }
    @PostMapping("/syncMedInhospForShiyi")
    public void syncMedInhospForShiyi(@RequestParam("startTime") String startTime, @RequestParam("endTime") String endTime) {
        collectHISService.syncMedInhosp(startTime, endTime);
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -685,6 +685,86 @@
    }
    /**
     * é•¿æœŸä»»åŠ¡å‘é€
     */
    public void longTaskSendTest(Long subId) {
        //获取任务信息
        ServiceTask st = new ServiceTask();
        st.setDelFlag("0");
        st.setLongTask(1);
        List<ServiceTask> serviceTasks = serviceTaskMapper.selectServiceTaskList(st);
        log.info("【longTaskSend】获取到{}个长期任务", serviceTasks.size());
        for (ServiceTask serviceTask : serviceTasks) {
            CommonTaskcallMQ commonTaskcallMQ = new CommonTaskcallMQ();
            commonTaskcallMQ.setTaskid(serviceTask.getTaskid());
            commonTaskcallMQ.setPreachform(serviceTask.getPreachform());
            commonTaskcallMQ.setSendType("2");
            //通过任务ID拿到患者信息,并且随访时间得是今天之前的
            ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity();
            serviceSubtaskVO.setTaskid(commonTaskcallMQ.getTaskid());
            serviceSubtaskVO.setSendstate(2L);
            serviceSubtaskVO.setIsVisitAgain(1);
            serviceSubtaskVO.setSubId(subId);
            serviceSubtaskVO.setVisitTime(new Date());
            List<ServiceSubtask> selectServiceSubtaskList = serviceSubtaskMapper.queryServiceSubtaskList(serviceSubtaskVO);
            for (ServiceSubtask serviceSubtask : selectServiceSubtaskList) {
                sfHandlle(serviceSubtask);
            }
        }
    }
    /**
     * å¤„理补偿任务
     */
    public void compensateTaskTest(Long subId) {
        //获取到sendstate=3 å¹¶ä¸” visit_time为小于等于今天的subtask
        ServiceSubtaskEntity serviceSubtaskVO = new ServiceSubtaskEntity();
        serviceSubtaskVO.setSendstate(3L);
        serviceSubtaskVO.setSubId(subId);
        serviceSubtaskVO.setVisitTime(new Date());
        List<ServiceSubtask> serviceSubtaskList = serviceSubtaskMapper.getCompensateServiceSubtaskList(serviceSubtaskVO);
        for (ServiceSubtask serviceSubtask : serviceSubtaskList) {
            //根据当前的执行方式,获取下一种执行方式
            ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform();
            serviceSubtaskPreachform.setTaskid(serviceSubtask.getTaskid());
            serviceSubtaskPreachform.setSubid(serviceSubtask.getId());
            serviceSubtaskPreachform.setOrgid(serviceSubtask.getOrgid());
            List<ServiceSubtaskPreachform> serviceSubtaskPreachforms = serviceSubtaskPreachformMapper.selectServiceSubtaskPreachformList(serviceSubtaskPreachform);
            //获取当前执行方式的序号
            Optional<Long> currentSort = serviceSubtaskPreachforms.stream().filter(item -> serviceSubtask.getCurrentPreachform().equals(item.getPreachform())).map(ServiceSubtaskPreachform::getSort).findFirst();
            Optional<Long> id = serviceSubtaskPreachforms.stream().filter(item -> serviceSubtask.getCurrentPreachform().equals(item.getPreachform())).map(ServiceSubtaskPreachform::getId).findFirst();
            if (currentSort.isPresent()) {
                //1先检查一下,是不是有执行状态是完成的(怕之前已经有完的了,没有将servuce_subtask的状态改成功,这里再检查一下)
                boolean finishState = serviceSubtaskPreachforms.stream().allMatch(item -> item.getSendstate().equals("9"));
                if (finishState) {
                    serviceSubtask.setSendstate(6L);
                    serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                    continue;
                }
                //2判断一下,当前的sort是不是等于需要执行的总个数,如果等于的话,说明是最后一个,直接将servuce_subtask的状态改成5,执行失败就行了
                Long cs = currentSort.get();
                if (cs.equals(serviceSubtaskPreachforms.size())) {
                    serviceSubtask.setSendstate(7L);
                    serviceSubtask.setRemark("处理补偿任务,当前处理最后补偿,全部执行失败(超时)");
                    serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                    //修改发送方式的状态为失败
                    serviceSubtaskPreachform.setSendstate("5");
                    serviceSubtaskPreachform.setId(id.get());
                    serviceSubtaskPreachformMapper.updateServiceSubtaskPreachform(serviceSubtaskPreachform);
                    continue;
                }
                //3.不是最后一个,获取到下一个执行方式(因为都是在今天执行,那就直接发出去就完了)
                sfHandlle(serviceSubtask);
            }
        }
    }
    /**
     * è®¾ç½®å¤±è´¥ä»»åŠ¡é»˜è®¤å€¼,并将失败任务重新置为成功
     * <p>
     * *@param failDay   (失败天数:距离当前日期失败天数)
@@ -971,6 +1051,7 @@
                    //任务发送记录
                    ServiceSubtaskRecord serviceSubtaskRecord = new ServiceSubtaskRecord();
                    serviceSubtaskRecord.setTaskid(serviceSubtask.getTaskid().toString());
                    serviceSubtaskRecord.setSubtaskId(serviceSubtask.getId());
                    serviceSubtaskRecord.setUuid(UUID.randomUUID().toString());
                    serviceSubtaskRecord.setTasktype(serviceSubtask.getType());
                    serviceSubtaskRecord.setPreachform("4");
@@ -1028,8 +1109,6 @@
                    if (active.equals("hzszlyy")) {
                        //处理中文乱码问题
                        wxCode = smsUtils.sendChat(url, patArchive.getTelcode(), serviceSubtask.getSfzh());
                        log.info(wxCode);
                    } else {
                        wxCode = getWXCode(serviceSubtask.getSfzh(), url, serviceSubtask.getTaskName(), serviceSubtask.getTaskDesc(), patArchive.getTelcode(), serviceSubtask.getSendname(), patArchive.getPatidHis(), wxqqxx);
                    }
@@ -1346,9 +1425,7 @@
                    //serviceSubtask.setRemark("setSuccessPreachForm方法,当前的preachform已经是最后一个了,全部执行失败");
                    serviceSubtaskMapper.updateServiceSubtask(serviceSubtask);
                    return true;
                }
            }
        } else {
            log.error("【定时任务中该患者没有查询到属于他的发送方式,subid:{},prechform:{},orgid:{}】", serviceSubtask.getId(), preachform, serviceSubtask.getOrgid());
smartor/src/main/java/com/smartor/common/QwenLLMUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,214 @@
package com.smartor.common;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.http.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * é€šä¹‰åƒé—®ï¼ˆQwen)大模型工具类。
 * <p>
 * åŸºäºŽé˜¿é‡Œäº‘百炼 DashScope çš„ OpenAI å…¼å®¹æŽ¥å£ï¼ˆ{@code /compatible-mode/v1/chat/completions}),
 * ä¸»è¦ç”¨äºŽè¯­ä¹‰åŒ¹é…ï¼šæŠŠè¯­éŸ³è¯†åˆ«å¾—到的自由文本,归一化到一组预设选项中最接近的一个。
 * <p>
 * é…ç½®é¡¹ï¼ˆapplication.yml):
 * <pre>
 * qwen:
 *   api-key: sk-xxxxxxxx          # ç™¾ç‚¼ API Key,必填
 *   model: qwen-plus             # æ¨¡åž‹åç§°ï¼Œé»˜è®¤ qwen-plus
 *   url: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
 * </pre>
 */
@Slf4j
@Component
public class QwenLLMUtil {
    /** ç™¾ç‚¼ API Key */
    @Value("${qwen.api-key:}")
    private String apiKey;
    /** æ¨¡åž‹åç§° */
    @Value("${qwen.model:qwen-plus}")
    private String model;
    /** æŽ¥å£åœ°å€ï¼ˆOpenAI å…¼å®¹æ¨¡å¼ï¼‰ */
    @Value("${qwen.url:https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions}")
    private String url;
    /**
     * åˆ¤æ–­è¯­éŸ³æ–‡æœ¬æœ€æŽ¥è¿‘哪个选项。
     *
     * @param voiceText è¯­éŸ³è¯†åˆ«å¾—到的文本
     * @param options   å€™é€‰é€‰é¡¹åˆ—表
     * @return å‘½ä¸­çš„选项原文;无法匹配任一选项时返回 {@code null}
     */
    public String matchOption(String voiceText, List<String> options) {
        int index = matchOptionIndex(voiceText, options);
        return index < 0 ? null : options.get(index);
    }
    /**
     * åˆ¤æ–­è¯­éŸ³æ–‡æœ¬æœ€æŽ¥è¿‘哪个选项,返回选项在列表中的下标。
     *
     * @param voiceText è¯­éŸ³è¯†åˆ«å¾—到的文本
     * @param options   å€™é€‰é€‰é¡¹åˆ—表
     * @return å‘½ä¸­é€‰é¡¹çš„下标(从 0 å¼€å§‹ï¼‰ï¼›æ— æ³•匹配任一选项时返回 {@code -1}
     */
    public int matchOptionIndex(String voiceText, List<String> options) {
        if (StringUtils.isBlank(voiceText) || options == null || options.isEmpty()) {
            return -1;
        }
        // åªæœ‰ä¸€ä¸ªé€‰é¡¹æ—¶æ— éœ€è°ƒç”¨æ¨¡åž‹
        if (options.size() == 1) {
            return 0;
        }
        StringBuilder optionText = new StringBuilder();
        for (int i = 0; i < options.size(); i++) {
            optionText.append(i + 1).append(". ").append(options.get(i)).append('\n');
        }
        String systemPrompt = "你是一个语义匹配助手。用户会给出一段语音识别文本和若干个带编号的选项,"
                + "请判断这段文本在语义上最接近哪一个选项。只允许从给定选项中选择,"
                + "不要做任何解释。直接输出最匹配选项的编号数字;若没有任何选项与文本相关,则输出 0。";
        String userPrompt = "语音文本:" + voiceText + "\n\n选项:\n" + optionText
                + "\n请只输出一个数字(最匹配选项的编号,没有匹配则输出 0)。";
        String content = chat(systemPrompt, userPrompt);
        if (StringUtils.isBlank(content)) {
            return -1;
        }
        Integer number = extractFirstNumber(content);
        if (number == null || number <= 0 || number > options.size()) {
            log.warn("Qwen é€‰é¡¹åŒ¹é…æœªå‘½ä¸­ï¼ŒvoiceText={}, options={}, modelReturn={}", voiceText, options, content);
            return -1;
        }
        return number - 1;
    }
    /**
     * åˆ¤æ–­è¯­éŸ³æ–‡æœ¬æ˜¯å¦ç¬¦åˆè¿™ä¸ªæ„æ€
     *
     * @param voiceText è¯­éŸ³è¯†åˆ«å¾—到的文本
     * @return å‘½ä¸­çš„选项原文;无法匹配任一选项时返回 {@code null}
     */
    public int matchRegex(String voiceText, String value, String regexText) {
        if (StringUtils.isBlank(voiceText) || regexText == null || regexText.isEmpty()) {
            return -1;
        }
        String systemPrompt = "你是一个语义匹配助手。用户会给出一段语音识别文本、正则匹配文本、对应指标值"
                + "请判断这段文本是否接近正则匹配规则或者对应指标值的意思"
                + "不要做任何解释。若有相关意思,能匹配的上,直接输出 1;若与文本意思完全不相关,则输出 0。";
        String userPrompt = "语音文本:" + voiceText + "\n\n正则匹配文本:\n" + regexText
                +  "\n\n对应指标值:\n" + value
                + "\n请只输出一个数字(匹配输出 1,没有匹配则输出 0)。";
        String content = chat(systemPrompt, userPrompt);
        if (StringUtils.isBlank(content)) {
            return -1;
        }
        Integer number = extractFirstNumber(content);
        if (number == null || number <= 0) {
            log.warn("Qwen é€‰é¡¹åŒ¹é…æœªå‘½ä¸­ï¼ŒvoiceText={}, regexText={}, modelReturn={}", voiceText, regexText, content);
            return -1;
        }
        return number - 1;
    }
    /**
     * é€šç”¨å¯¹è¯è°ƒç”¨ï¼Œè¿”回模型回复的文本内容。
     *
     * @param systemPrompt ç³»ç»Ÿæç¤ºè¯ï¼Œå¯ä¸ºç©º
     * @param userPrompt   ç”¨æˆ·æç¤ºè¯
     * @return æ¨¡åž‹å›žå¤æ­£æ–‡ï¼›è°ƒç”¨å¤±è´¥è¿”回 {@code null}
     */
    public String chat(String systemPrompt, String userPrompt) {
        if (StringUtils.isBlank(apiKey)) {
            throw new IllegalStateException("通义千问 API Key æœªé…ç½®ï¼ˆqwen.api-key)");
        }
        if (StringUtils.isBlank(userPrompt)) {
            throw new IllegalArgumentException("userPrompt ä¸èƒ½ä¸ºç©º");
        }
        JSONArray messages = new JSONArray();
        if (StringUtils.isNotBlank(systemPrompt)) {
            messages.add(message("system", systemPrompt));
        }
        messages.add(message("user", userPrompt));
        JSONObject body = new JSONObject();
        body.put("model", model);
        body.put("messages", messages);
        // åŒ¹é…åœºæ™¯éœ€è¦ç¨³å®šç»“果,温度调低
        body.put("temperature", 0.01);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + apiKey);
        headers.put("Content-Type", "application/json");
        String response = HttpUtils.sendPostByHeader(url, body.toJSONString(), headers);
        if (StringUtils.isBlank(response)) {
            log.error("通义千问返回为空,url={}, body={}", url, body.toJSONString());
            return null;
        }
        try {
            JSONObject json = JSONObject.parseObject(response);
            JSONArray choices = json.getJSONArray("choices");
            if (choices == null || choices.isEmpty()) {
                log.error("通义千问响应无 choices,response={}", response);
                return null;
            }
            JSONObject msg = choices.getJSONObject(0).getJSONObject("message");
            return msg == null ? null : StringUtils.trim(msg.getString("content"));
        } catch (Exception e) {
            log.error("解析通义千问响应失败,response={}", response, e);
            return null;
        }
    }
    private JSONObject message(String role, String content) {
        JSONObject msg = new JSONObject();
        msg.put("role", role);
        msg.put("content", content);
        return msg;
    }
    /**
     * ä»Žæ¨¡åž‹å›žå¤ä¸­æå–第一个整数。模型偶尔会回复 â€œé€‰é¡¹2” â€œ2。” ä¹‹ç±»ï¼Œåšä¸€æ¬¡å…œåº•解析。
     */
    private Integer extractFirstNumber(String text) {
        List<Character> digits = new ArrayList<>();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c >= '0' && c <= '9') {
                digits.add(c);
            } else if (!digits.isEmpty()) {
                break;
            }
        }
        if (digits.isEmpty()) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (char c : digits) {
            sb.append(c);
        }
        try {
            return Integer.parseInt(sb.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
}
smartor/src/main/resources/mapper/smartor/ServiceSubtaskMapper.xml
@@ -275,6 +275,9 @@
        <if test="orgid != null and orgid != ''">
            and orgid = #{orgid}
        </if>
        <if test="subId != null">
            AND id = #{subId}
        </if>
        <if test="taskid != null ">and taskid = #{taskid}</if>
        <if test="visitTime != null">
            AND date_format(visit_time,'%y%m%d') &lt;= date_format(#{visitTime},'%y%m%d')
@@ -291,6 +294,9 @@
        and sendstate = 5
        <if test="orgid != null and orgid != ''">
            and orgid = #{orgid}
        </if>
        <if test="subId != null">
            AND id = #{subId}
        </if>
        <if test="taskid != null ">and taskid = #{taskid}</if>
        <if test="visitTime != null">
@@ -570,6 +576,9 @@
        <if test="visitTime != null">
            AND date_format(visit_time,'%y%m%d') &lt;= date_format(#{visitTime},'%y%m%d')
        </if>
        <if test="subId != null">
            AND id = #{subId}
        </if>
        <if test="sendstate != null ">and sendstate = #{sendstate}</if>
        <if test="continueFlag != null ">and continue_flag = #{continueFlag}</if>
        <if test="continueTimeNow != null ">and continue_time_now = #{continueTimeNow,jdbcType=TIMESTAMP}</if>
@@ -585,6 +594,9 @@
        <if test="visitTime != null">
            AND date_format(visit_time,'%y%m%d') &lt;= date_format(DATE_ADD(#{visitTime}, INTERVAL 1 DAY),'%y%m%d')
        </if>
        <if test="subId != null">
            AND id = #{subId}
        </if>
        <if test="sendstate != null ">and sendstate = #{sendstate}</if>
        <if test="continueFlag != null ">and continue_flag = #{continueFlag}</if>
        <if test="continueTimeNow != null ">and continue_time_now = #{continueTimeNow,jdbcType=TIMESTAMP}</if>