sxwz2.0/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java

677 lines
34 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<CmsTag> 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<JSONObject> 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<CmsTag> 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<String> 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<JSONObject> 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<CmsTag> tags = cmsTagService.getList(tagDto);
List<String> 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: /scriptsname: 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<CmsTag> 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<JSONObject> 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<CmsTag> 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、descriptioncontent和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<JSONObject> 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<CmsTag> 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<String, Object> 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<CmsTag> 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<CmsTag> 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<CmsTag> 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<JSONObject> 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;
}
}