package com.kexue.skills.service.impl; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.kexue.skills.common.Assert; import com.kexue.skills.common.util.HttpUtil; import com.kexue.skills.config.DeepSeekConfig; import com.kexue.skills.config.GlmConfig; import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.CmsTag; import com.kexue.skills.entity.dto.CmsTagDto; import com.kexue.skills.entity.request.SkillAnalyzeRequest; import com.kexue.skills.entity.request.SkillGenRequest; import com.kexue.skills.entity.request.SkillPreGenRequest; import com.kexue.skills.entity.request.SkillRequest; import com.kexue.skills.entity.response.SkillResponse; import com.kexue.skills.exception.BizException; import com.kexue.skills.mapper.CmsContentMapper; import com.kexue.skills.service.CmsTagService; import com.kexue.skills.service.SkillGenService; import com.kexue.skills.utils.EscapeCharacterUtils; import jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.File; import java.io.FileOutputStream; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * 技能生成服务实现 * * @author 维哥 * @since 2026-01-28 */ @Slf4j @Service public class SkillGenServiceImpl implements SkillGenService { @Autowired private DeepSeekConfig deepSeekConfig; @Autowired private GlmConfig glmConfig; @Autowired private CmsTagService cmsTagService; @Resource private CmsContentMapper cmsContentMapper; /** * 生成技能 * * @param request 生成请求 * @return 生成结果 */ @Override public SkillResponse preGenerate(SkillPreGenRequest request) { log.info("生成技能请求: {}", request); String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; // 从数据库中读取cms_tag表的标签信息 CmsTagDto tagDto = new CmsTagDto(); tagDto.setDeleteFlag(0); tagDto.setStatus(1); List tags = cmsTagService.getList(tagDto); // 将标签名称拼接成逗号分隔的字符串 StringBuilder tagsList = new StringBuilder(); for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); tagsList.append(tag.getTagId()+"."+tag.getTagName()); if (i < tags.size() - 1) { tagsList.append(","); } } SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(), request.getPrompt(), tagsList.toString()); String deepseekResponse = ""; try { // 发送HTTP请求到deepseek API deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); log.info("Deepseek API响应: {}", deepseekResponse); // 解析deepseek返回结果 JSONObject responseJson = JSON.parseObject(deepseekResponse); List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); if (choices != null && !choices.isEmpty()) { // 获取最新的choice(这里是第一个,因为只有一个) JSONObject latestChoice = choices.get(0); JSONObject message = latestChoice.getJSONObject("message"); String content = message.getString("content"); // 解析content中的JSON JSONObject skillJson = JSON.parseObject(content); SkillResponse skillResponse = new SkillResponse(); skillResponse.setName(skillJson.getString("name")); skillResponse.setDescription(skillJson.getString("description")); skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class)); log.info("解析技能响应: {}", skillResponse); return skillResponse; } } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse); } return null; } @Override public SkillResponse preGenerateV2(SkillPreGenRequest request) { log.info("生成技能请求V2: {}", request); String url = glmConfig.getBaseUrl() + "/chat/completions"; String apiKey = glmConfig.getApiKey(); String model = glmConfig.getChat().getModel(); double temperature = glmConfig.getChat().getTemperature(); int maxTokens = glmConfig.getChat().getMaxTokens(); // 从数据库中读取cms_tag表的标签信息 CmsTagDto tagDto = new CmsTagDto(); tagDto.setDeleteFlag(0); tagDto.setStatus(1); List tags = cmsTagService.getList(tagDto); // 将标签名称拼接成逗号分隔的字符串 StringBuilder tagsList = new StringBuilder(); for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); tagsList.append(tag.getTagId()+"."+tag.getTagName()); if (i < tags.size() - 1) { tagsList.append(","); } } // 构建系统消息内容 String systemContent = "你是一个专业的AI技能设计助手。请根据agent skills撰写规范,按照用户提出的主题描述及参考文件,生成这个skill的名称、描述,并从以下标签列表中选择至少3个标签:\"" + tagsList.toString() + "\",tags只需要返回序号数组,并简述这个skill的具体价值点。输出json格式,仅输出以上所提到的名称、描述、标签、价值点,节点名称分别为name、description、tags、value_point,节点内容以中文形式返回。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。"; // 准备文件URL列表 List fileUrls = new ArrayList<>(); if (request.getFileUrl() != null && !request.getFileUrl().isEmpty()) { fileUrls.add(request.getFileUrl()); } if (request.getFileUrls() != null && !request.getFileUrls().isEmpty()) { fileUrls.addAll(request.getFileUrls()); } // 创建技能请求 SkillRequest skillRequest = new SkillRequest(model, systemContent, request.getPrompt(), fileUrls, temperature, maxTokens); String response = ""; try { // 发送HTTP请求到API response = HttpUtil.sendPostRequest(url, skillRequest, apiKey, null); log.info("API响应: {}", response); // 解析返回结果 JSONObject responseJson = JSON.parseObject(response); List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); if (choices != null && !choices.isEmpty()) { // 获取最新的choice(这里是第一个,因为只有一个) JSONObject latestChoice = choices.get(0); JSONObject message = latestChoice.getJSONObject("message"); String content = message.getString("content"); // 解析content中的JSON JSONObject skillJson = JSON.parseObject(content); SkillResponse skillResponse = new SkillResponse(); skillResponse.setName(skillJson.getString("name")); skillResponse.setDescription(skillJson.getString("description")); skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class)); skillResponse.setSummary(skillJson.getString("value_point")); log.info("解析技能响应: {}", skillResponse); return skillResponse; } } catch (Exception e) { log.error("调用API失败: {}", e.getMessage(), e); throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ response); } return null; } @Override public CmsContent generate(SkillGenRequest request) { log.info("生成技能请求: {}", request); // 参数验证,确保每个参数都必须传递 Assert.notEmpty(request.getName(), "技能名称不能为空"); Assert.notEmpty(request.getDescription(), "技能描述不能为空"); Assert.notEmpty(request.getTags(), "技能标签不能为空"); Assert.notEmpty(request.getRequirement(), "技能摘要不能为空"); String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; // 从数据库中读取cms_tag表的标签信息 CmsTagDto tagDto = new CmsTagDto(); tagDto.setDeleteFlag(0); tagDto.setStatus(1); List tags = cmsTagService.getList(tagDto); List tags1 = request.getTags(); // 将标签名称拼接成逗号分隔的字符串 StringBuilder tagsList = new StringBuilder(); String defaultIcon = ""; for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); if (tags1.contains(tag.getTagId()+"")) { if (StringUtil.isEmpty(defaultIcon)){ defaultIcon = tag.getIcon(); } tagsList.append(tag.getTagName()); if (i < tags.size() - 1) { tagsList.append(","); } } } String systemContent = """ 你是AI技能包设计专家,仅输出【完整的纯YAML文本】,输出内容是完整可解析的技能YAML描述文件,绝非片段,不包含任何多余文字(无解释、无注释、无引言、无结尾)。 ### 一、YAML顶层强制规则(仅一个节点:package) 1. 顶层只能有 package 一个节点,所有信息(名称、版本、目录结构等)均嵌套在 package 下 2. package 节点必含子字段:name、version、description、author、created、tags、structure(缺一不可) 3. 最终必须输出完整闭合的YAML结构,禁止输出残缺片段、部分节点 ### 二、package子字段规范(固定格式) 1. name:技能名称(与用户提供的Skill名称完全一致,不修改) 2. version:固定为 "1.0.0" 3. description:用户提供的Skill描述(完整复制,不增删任何内容) 4. author:固定为 "AI技能生成助手" 5. created:格式为 "YYYY-MM-DD"(使用当前日期,如2026-04-01) 6. tags:数组格式,值为用户提供的Skill标签(中文,自动去重,逗号后加空格) 7. structure:技能包目录树,根目录固定为 /,structure下直接编写根目录的一级子文件/子文件夹,禁止重复描述根目录本身 ### 三、structure目录树规则(仅Python脚本,无其他语言) #### 1. 基础必含文件(所有技能通用) - 根目录 / 下必生成:skills.md 文件,归属父路径 /,文件必须保留后缀名 .md #### 2. structure核心编写规则 1. structure节点下不写根目录/的描述,直接从一级子文件/子文件夹开始编写 2. 【path路径强制规则】path只写父级目录路径,不拼接文件名称/目录名称 - 目录自身名称使用 name 字段体现,目录一律无后缀名 - 文件自身名称使用 name 字段体现,文件必须保留标准后缀名 - 示例:scripts目录在根目录下 → path: /,name: scripts - 示例:skills.md在根目录下 → path: /,name: skills.md - 示例:main.py在scripts目录下 → path: /scripts,name: main.py 3. 所有directory/file节点均为根目录/的直接/间接子节点 #### 3. Python脚本目录/文件判断逻辑 - 若用户提供的“Skill描述”“Skill摘要”中包含“脚本”“代码”“执行”“运行”“处理”“计算”等需执行逻辑的关键词: 1. 必须新增 scripts 目录,该目录为文件夹,无任何后缀名,path: /,name: scripts 2. 必须在该目录下固定生成 main.py 文件,文件必须带 .py 后缀,不允许省略或修改文件名 - 若用户需求中无任何执行逻辑相关描述(如纯文档、纯说明类技能):不生成 scripts 目录,避免冗余 #### 4. 节点必含字段 - directory类型:name、type、path(仅父级路径)、format(固定"dir")、description、children(空目录写 children: []) - file类型:name、type、path(仅父级路径)、format(仅markdown/python两种)、description、content(非空,有实际可用内容) ### 四、文件content内容规范(Python脚本必实用) #### 1. 根目录下 skills.md - # 技能名称(不加多余符号,居中可加空格但不强制) - ## 技能描述(整合用户“描述+摘要”,补充逻辑连贯性) - ## 标签(格式:- 标签1\n- 标签2) - ## 使用说明(分点写:适用场景、操作步骤,需脚本则写“运行scripts/main.py脚本”,无需则写“直接参考文档使用”) - ## 目录结构(用代码块 ``` 列出所有文件/目录路径) #### 2. scripts目录下 main.py - 必含依赖导入(如 import pandas as pd,无依赖则不写) - 必含入口函数 def execute(params: dict) -> dict:(参数为dict,返回dict结果) - 函数内必含: 1. 参数校验(判断必填键是否存在,缺失返回错误) 2. 核心逻辑(匹配技能需求,如数据处理、文本分析) 3. 结果返回(成功:{"status": "success", "data": 结果};失败:{"status": "fail", "error": 信息}) - 必含注释:函数说明、参数/返回值说明、示例调用(if __name__ == "__main__": 块) - 禁止空函数、语法错误,确保复制后可直接运行 ### 五、YAML语法死规定(100%无解析错误) 1. 缩进:统一2个空格(禁止Tab,禁止1/3/4空格,嵌套层级严格对齐) - package 下子字段缩进2空格 - structure 下目录/文件节点缩进4空格(package→structure→children,每层+2空格) 2. 路径:全部遵循path只写父目录规则,Unix风格 /,禁止 \\ 或 ./ 3. 字符串:特殊字符(:、#、空格)无需转义,直接书写 4. 数组:tags格式严格为 tags: [标签1, 标签2](逗号后加空格,无多余逗号) 5. content:多行内容用 | 开头,内容行首顶格,内部遵循对应格式缩进(Python用4空格) ### 六、错误规避红线(绝对不能触碰) 1. 禁止顶层出现除 package 外的任何节点 2. 禁止在YAML前后加任何多余文字 3. 禁止生成非Python脚本,禁止生成无后缀的脚本文件 4. 禁止字段缺失 5. 禁止输出YAML片段,必须输出完整可解析文件 6. structure禁止描述根目录/,直接从一级子节点开始 7. 严禁path中携带文件/目录名称,必须只写父级路径 8. 严禁将scripts目录错误添加后缀名,严禁修改Python脚本名为非main.py 最终输出仅完整纯YAML,直接可复制存储、解析使用,无任何冗余或格式问题! """; String userContent = """ 基于以下信息生成技能包YAML,严格遵守system指令(仅Python脚本,无其他语言): 1. Skill名称:%s 2. Skill描述:%s 3. Skill标签:%s(中文,直接使用,不修改) 4. Skill摘要/需求:%s(用于完善skills.md的“使用说明”章节) """.formatted( request.getName(), request.getDescription(), tagsList.toString(), request.getRequirement() ); SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),systemContent,userContent,deepSeekConfig.getChat().getTemperature(), 8192,"text"); String deepseekResponse = ""; try { // 发送HTTP请求到deepseek API deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); log.info("Deepseek API响应: {}", deepseekResponse); JSONObject responseJson = JSON.parseObject(deepseekResponse); String content = responseJson.getJSONArray("choices").toJavaList(JSONObject.class).get(0).getJSONObject("message").getString("content"); content = EscapeCharacterUtils.removeEscapeCharacters( content);//去除转义字符 CmsContent cmsContent = getCmsContent(request, content, StpUtil.getLoginIdAsLong(),defaultIcon); List list = tags.stream().filter(tag -> tag.getTagId() == Long.parseLong(cmsContent.getTags().split(",")[0])).toList(); if (CollectionUtil.isNotEmpty( list)){ cmsContent.setIcon(list.get(0).getIcon()); } // 保存到数据库 cmsContentMapper.insert(cmsContent); return cmsContent; } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse); } } private CmsContent getCmsContent(SkillGenRequest request, String CmsContent,Long userId,String defaultIcon){ CmsContent cmsContent = new CmsContent(); cmsContent.setTitle(request.getName()); cmsContent.setDescription(request.getDescription()); cmsContent.setContent(CmsContent); cmsContent.setTags(request.getTags().stream().map(String::valueOf).collect(Collectors.joining(","))); cmsContent.setIsPaid(0);//免费 cmsContent.setIsOfficial(false); cmsContent.setPublishStatus(1); cmsContent.setAuthorId(userId); cmsContent.setAuthorName(null); cmsContent.setAuditStatus(1); cmsContent.setViewCount(0); cmsContent.setCommentCount(0); cmsContent.setRequiredPoints(0); cmsContent.setSupportPointsPay(0); cmsContent.setPrice(new BigDecimal(0)); cmsContent.setLikeCount(0); cmsContent.setShareCount(0); cmsContent.setCreateTime(new Date()); cmsContent.setUpdateTime(new Date()); cmsContent.setContentType(1); cmsContent.setCreateBy(userId+""); cmsContent.setUpdateBy(userId+""); cmsContent.setDeleteFlag(0); cmsContent.setIcon(defaultIcon); cmsContent.setRequirement(request.getRequirement()); cmsContent.setIntroduce(request.getIntroduce()); return cmsContent; } /** * 分析技能 * * @param request 分析请求 * @return 分析结果 */ @Override public String analyzeSkill(SkillAnalyzeRequest request) { log.info("分析技能请求: {}", request); // 这里可以实现技能分析逻辑 // 不需要数据库操作,直接返回结果 return "技能分析成功: " + request.getSkillId(); } @Override public String genIntroduce(String content) { log.info("生成技能介绍请求: {}", content); String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; String systemContent = "你是一个专业的AI技能设计助手。我会给你提供一个完整的skill的内容,请你帮我总结出skill的作用,能够解决的问题,输出一段描述文本"; SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, content, 0.3, 500, "text"); String deepseekResponse = ""; try { // 发送HTTP请求到deepseek API deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); log.info("Deepseek API响应: {}", deepseekResponse); // 解析返回结果 JSONObject responseJson = JSON.parseObject(deepseekResponse); List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); if (choices != null && !choices.isEmpty()) { JSONObject latestChoice = choices.get(0); JSONObject message = latestChoice.getJSONObject("message"); return EscapeCharacterUtils.removeEscapeCharacters( message.getString("content"));//去除转义字符 } } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse); } return null; } @Override public CmsContent uploadSkill(String skillUrl) { log.info("上传技能压缩包请求: {}", skillUrl); String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; // 从数据库中读取cms_tag表的标签信息 CmsTagDto tagDto = new CmsTagDto(); tagDto.setDeleteFlag(0); tagDto.setStatus(1); List tags = cmsTagService.getList(tagDto); // 将标签名称拼接成逗号分隔的字符串 StringBuilder tagsList = new StringBuilder(); for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); tagsList.append(tag.getTagId()+"."+tag.getTagName()); if (i < tags.size() - 1) { tagsList.append(","); } } // 构建系统消息内容 String systemContent = """ 你是一位专业的AI技能包解析助手,需完成以下任务: 1. 我会提供一个技能包压缩包(格式包含zip/rar)的URL,你需解析该压缩包根目录下的SKILL.md文件; 或者提供一个在线的SKILL.md文件的url; 2. 基于SKILL.md内容,提炼出: - skill的核心标题(name); - skill的核心描述(description); - 详细功能介绍(introduce); - 从指定标签列表中选取至少3个适配标签(tagList),标签范围:TAG_LIST; - 选择标签的时候只需要输出对应的标签编号 3. 生成content字段:基于技能包的名称、描述、标签,按照skills目录结构输出完整的技能包YAML内容(包含skills.md本体、scripts目录脚本等),需遵循以下严格规范: - 包含技能包必需的文件和目录; - 多行内容使用「|」字面块表示; - 所有内容从行首开始,无前置空格; - 空目录标注为「children: []」; - 文件内容需具备实际使用价值; - YAML文档需完整,核心概要包含name、version、description、author、created、tags等属性; - 核心节点为structure,用于描述技能包文件目录结构;structure下每个节点需包含基础属性:name、type、path、format、description;content和children为互选属性(type为file时,content字段填写文件内容;type为directory时,children数组填写子目录/文件节点)。 请将最终结果以JSON格式输出,JSON结构必须包含:name(技能名称),description(技能描述)、introduce(功能介绍)、tagList(标签列表)、content(完整YAML格式技能包内容),无需额外说明,仅输出符合要求的JSON内容。 """; systemContent = systemContent.replace("TAG_LIST", tagsList.toString()); // 构建用户消息内容 String userContent = skillUrl; // 创建技能请求 SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, userContent, 0.5, 8192, "json_object"); String deepseekResponse = ""; try { // 发送HTTP请求到DeepSeek API deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); log.info("Deepseek API响应: {}", deepseekResponse); // 解析返回结果 JSONObject responseJson = JSON.parseObject(deepseekResponse); List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); if (choices != null && !choices.isEmpty()) { // 获取最新的choice JSONObject latestChoice = choices.get(0); JSONObject message = latestChoice.getJSONObject("message"); String content = message.getString("content"); // 解析content中的JSON JSONObject skillJson = JSON.parseObject(content); // 构建技能生成请求 SkillGenRequest request = new SkillGenRequest(); request.setName(skillJson.getString("name")); request.setDescription(skillJson.getString("description")); request.setIntroduce(skillJson.getString("introduce")); request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class)); // 生成CmsContent对象 CmsContent cmsContent = getCmsContent(request, skillJson.getString("content"), StpUtil.getLoginIdAsLong(), ""); List list = tags.stream().filter(tag -> tag.getTagId() == Long.parseLong(cmsContent.getTags().split(",")[0])).toList(); if (CollectionUtil.isNotEmpty( list)){ cmsContent.setIcon(list.get(0).getIcon()); } // 保存到数据库 // cmsContentMapper.insert(cmsContent); return cmsContent; } } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse); } return null; } @Override public CmsContent uploadSkillV2(byte[] fileBytes, String fileName) { log.info("上传本地技能压缩包请求: {}", fileName); try { // 创建临时文件 String fileExtension = ""; if (fileName.contains(".")) { int dotIndex = fileName.lastIndexOf("."); if (dotIndex != -1) { fileExtension = fileName.substring(dotIndex); } } File tempFile = File.createTempFile("skill", fileExtension); try (FileOutputStream fos = new FileOutputStream(tempFile)) { fos.write(fileBytes); } // 生成默认参数 String author = StpUtil.getLoginIdAsString(); // 从文件名中提取技能名称 String defaultSkillName = fileName; if (fileName.contains(".")) { int dotIndex = fileName.lastIndexOf("."); if (dotIndex != -1) { defaultSkillName = fileName.substring(0, dotIndex); } } // 1. 提取压缩包中的skillMdText Map skillInfo = com.kexue.skills.common.util.SkillZipParser.extractSkillMdText(tempFile.getAbsolutePath(), defaultSkillName); String skillMdText = (String) skillInfo.get("skillMdText"); // 从数据库中读取cms_tag表的标签信息 CmsTagDto tagDto = new CmsTagDto(); tagDto.setDeleteFlag(0); tagDto.setStatus(1); List tags = cmsTagService.getList(tagDto); // 2. 解析skillMdText获取SkillGenRequest SkillGenRequest request = null; if (skillMdText != null && !skillMdText.isEmpty()) { request = parseSkillMdText(skillMdText, tags); } else { // 如果没有找到md文件,使用默认值 request = new SkillGenRequest(); request.setName(defaultSkillName); request.setDescription("未知"); request.setIntroduce("未知"); request.setTags(Arrays.asList("1001", "1000")); } // 3. 使用SkillGenRequest中的信息生成yaml String yamlContent = com.kexue.skills.common.util.SkillZipParser.generateYamlFromSkillInfo( tempFile.getAbsolutePath(), author, request.getName(), request.getDescription(), request.getTags() ); log.info("解析出skill 压缩包内容:{}", yamlContent); // 4. 生成CmsContent对象 CmsContent cmsContent = getCmsContent(request, yamlContent, StpUtil.getLoginIdAsLong(), ""); if (Objects.nonNull(cmsContent.getTags())) { String s = cmsContent.getTags().split(",")[0]; long l = 1000;// 默认值 try { l = Long.parseLong(s); } catch (NumberFormatException e) { // 异常是因为大模型返回的tag带中文了,忽略 } long finalL = l; List list = tags.stream().filter(tag -> tag.getTagId() == finalL).toList(); if (CollectionUtil.isNotEmpty(list)){ cmsContent.setIcon(list.get(0).getIcon()); } } // 保存到数据库 cmsContentMapper.insert(cmsContent); // 删除临时文件 tempFile.delete(); return cmsContent; } catch (Exception e) { log.error("上传本地技能压缩包失败: {}", e.getMessage(), e); throw new BizException("上传本地技能压缩包失败:" + e.getMessage()); } } public SkillGenRequest parseSkillMdText(String skillMdText,List tags) { String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; // 将标签名称拼接成逗号分隔的字符串 StringBuilder tagsList = new StringBuilder(); for (int i = 0; i < tags.size(); i++) { CmsTag tag = tags.get(i); tagsList.append(tag.getTagId()+"."+tag.getTagName()); if (i < tags.size() - 1) { tagsList.append(","); } } // 构建系统消息内容 String systemContent = """ 你是一位专业的AI技能包解析助手,需完成以下任务: 1. 我会提供一个技能包压缩包SKILL.md文件的具体内容; 2. 基于SKILL.md内容,提炼出: - skill的核心标题(name); - skill的核心描述(description); - 详细功能介绍(introduce); - 从指定标签列表中选取至少3个适配标签(tagList),标签范围:TAG_LIST; - 选择标签的时候只需要输出对应的标签编号,特别注意:只需要标签编号 请将最终结果以JSON格式输出,JSON结构必须包含:name(技能名称),description(技能描述)、introduce(功能介绍)、tagList(标签列表),无需额外说明,仅输出符合要求的JSON内容。 """; systemContent = systemContent.replace("TAG_LIST", tagsList.toString()); // 创建技能请求 SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, skillMdText, 0.5, 8192, "json_object"); String deepseekResponse = ""; try { // 发送HTTP请求到DeepSeek API deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null); log.info("Deepseek API响应: {}", deepseekResponse); // 解析返回结果 JSONObject responseJson = JSON.parseObject(deepseekResponse); List choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class); if (choices != null && !choices.isEmpty()) { // 获取最新的choice JSONObject latestChoice = choices.get(0); JSONObject message = latestChoice.getJSONObject("message"); String content = message.getString("content"); // 解析content中的JSON JSONObject skillJson = JSON.parseObject(content); // 构建技能生成请求 SkillGenRequest request = new SkillGenRequest(); request.setName(skillJson.getString("name")); request.setDescription(skillJson.getString("description")); request.setIntroduce(skillJson.getString("introduce")); // 处理tagList可能不存在的情况 if (skillJson.containsKey("tagList")) { request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class)); } else { request.setTags(Arrays.asList("1001", "1002")); } return request; } } catch (Exception e) { log.error("调用Deepseek API失败: {}", e.getMessage(), e); throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse); } return null; } }