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技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,概要含核心属性:name、version、description、author、created、tags 等,其中 structure 为核心节点,用于描述 skill 包的文件目录结构;structure 下的每个节点均含基础属性:name、type、path、format、description,content 和 children 为互选属性,由 type 决定:type 为 file 时,展示文件具体内容在 content 字段,必须保证content内容的缩进为2个字符;type 为 directory 时,展示子目录 / 文件节点在 children [] 数组中,无需其他额外说明。"; String userContent = "请根据以下Skill信息生成skills.md文档内容:Skill名称:SKILL_NAME,Skill描述:DESCRIPTION,Skill标签:TAGS ,摘要:SUMMARY。"; userContent = userContent.replace("SKILL_NAME", request.getName()).replace("DESCRIPTION", request.getDescription()).replace("TAGS", tagsList.toString()).replace("SUMMARY", 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()); } } // 删除临时文件 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; } }