diff --git a/src/main/java/com/kexue/skills/common/util/HttpUtil.java b/src/main/java/com/kexue/skills/common/util/HttpUtil.java new file mode 100644 index 0000000..dd1f246 --- /dev/null +++ b/src/main/java/com/kexue/skills/common/util/HttpUtil.java @@ -0,0 +1,58 @@ +package com.kexue.skills.common.util; + +import com.alibaba.fastjson.JSON; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; + +/** + * HTTP工具类 + * + * @author 维哥 + * @since 2026-01-28 + */ +public class HttpUtil { + + /** + * 发送POST请求到指定URL(通用HTTP客户端方法,使用JDK自带HttpClient) + * + * @param url 请求URL + * @param requestBody 请求体对象 + * @param apiKey API密钥 + * @param authHeader 认证头 + * @return 响应结果 + * @throws Exception 异常信息 + */ + public static String sendPostRequest(String url, Object requestBody, String apiKey, String authHeader) throws Exception { + // 创建HttpClient实例 + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(30)) + .build(); + + // 构建请求体 + String jsonInputString = JSON.toJSONString(requestBody); + + // 构建HttpRequest + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(jsonInputString, StandardCharsets.UTF_8)); + + // 设置认证头 + if (authHeader != null) { + requestBuilder.header("Authorization", authHeader); + } else if (apiKey != null) { + requestBuilder.header("Authorization", "Bearer " + apiKey); + } + + // 发送请求并获取响应 + HttpResponse response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + + return response.body(); + } +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/config/DeepSeekConfig.java b/src/main/java/com/kexue/skills/config/DeepSeekConfig.java new file mode 100644 index 0000000..2ab024b --- /dev/null +++ b/src/main/java/com/kexue/skills/config/DeepSeekConfig.java @@ -0,0 +1,73 @@ +package com.kexue.skills.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * DeepSeek API配置类 + * + * @author 维哥 + * @since 2026-01-28 + */ +@Component +@ConfigurationProperties(prefix = "spring.ai.deepseek") +public class DeepSeekConfig { + + private String baseUrl; + private String apiKey; + private ChatOptions chat; + + public static class ChatOptions { + private String model; + private Double temperature; + private Integer maxTokens; + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public Double getTemperature() { + return temperature; + } + + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Integer getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; + } + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public ChatOptions getChat() { + return chat; + } + + public void setChat(ChatOptions chat) { + this.chat = chat; + } +} diff --git a/src/main/java/com/kexue/skills/controller/SkillGenController.java b/src/main/java/com/kexue/skills/controller/SkillGenController.java new file mode 100644 index 0000000..d0cbd48 --- /dev/null +++ b/src/main/java/com/kexue/skills/controller/SkillGenController.java @@ -0,0 +1,48 @@ +package com.kexue.skills.controller; + +import com.kexue.skills.common.CommonResult; +import com.kexue.skills.entity.response.SkillResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; + +/** + * 技能生成控制器 + * + * @author 维哥 + * @since 2026-01-28 + */ +@RestController +@RequestMapping("api/skillGen") +@Tag(name = "技能生成 Api") +@CrossOrigin(origins = "*") +public class SkillGenController { + + @Resource + private com.kexue.skills.service.SkillGenService skillGenService; + + /** + * 生成技能 + * + * @param request 生成请求 + * @return 生成结果 + */ + @PostMapping("/generate") + @Operation(summary = "生成技能", description = "生成技能") + public CommonResult generate(@RequestBody com.kexue.skills.entity.request.SkillGenRequest request) { + return CommonResult.success(skillGenService.generateSkill(request)); + } + + /** + * 分析技能 + * + * @param request 分析请求 + * @return 分析结果 + */ + @PostMapping("/analyze") + @Operation(summary = "分析技能", description = "分析技能") + public CommonResult analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest request) { + return CommonResult.success(skillGenService.analyzeSkill(request)); + } +} diff --git a/src/main/java/com/kexue/skills/entity/request/SkillAnalyzeRequest.java b/src/main/java/com/kexue/skills/entity/request/SkillAnalyzeRequest.java new file mode 100644 index 0000000..ad0bb4f --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/SkillAnalyzeRequest.java @@ -0,0 +1,24 @@ +package com.kexue.skills.entity.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 技能分析请求参数 + * + * @author 维哥 + * @since 2026-01-28 + */ +@Data +@ApiModel(value = "技能分析请求参数") +public class SkillAnalyzeRequest implements Serializable { + + @Schema(description = "技能ID") + private Long skillId; + + @Schema(description = "分析类型") + private String analyzeType; +} diff --git a/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java b/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java new file mode 100644 index 0000000..9b36a57 --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/SkillGenRequest.java @@ -0,0 +1,22 @@ +package com.kexue.skills.entity.request; + +import io.swagger.annotations.ApiModel; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 技能生成请求参数 + * + * @author 维哥 + * @since 2026-01-28 + */ +@Data +@ApiModel(value = "技能生成请求参数") +public class SkillGenRequest implements Serializable { + + @Schema(description = "用户提示词") + private String prompt; + +} diff --git a/src/main/java/com/kexue/skills/entity/request/SkillRequest.java b/src/main/java/com/kexue/skills/entity/request/SkillRequest.java new file mode 100644 index 0000000..587bd8f --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/request/SkillRequest.java @@ -0,0 +1,91 @@ +package com.kexue.skills.entity.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SkillRequest implements Serializable { + private static final long serialVersionUID = 1L; + + private String model; + private List messages; + private double temperature; + private int max_tokens; + private ResponseFormat response_format; + + public SkillRequest(boolean useDefaultSettings) { + if (useDefaultSettings) { + this.model = "deepseek-chat"; + this.messages = new ArrayList<>(); + + Message systemMessage = new Message(); + systemMessage.setRole("system"); + systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。"); + this.messages.add(systemMessage); + + Message userMessage = new Message(); + userMessage.setRole("user"); + userMessage.setContent("主题:我想要做一个将数据库设计表转换成sql schema语言并迁移到数据库服务器中的skill。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"软件开发,系统集成,网络工程,云计算,大数据,人工智能,物联网,区块链,信息安全,运维服务,测试认证,IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。"); + this.messages.add(userMessage); + + this.temperature = 0.3; + this.max_tokens = 500; + + this.response_format = new ResponseFormat(); + this.response_format.setType("json_object"); + } + } + + public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens,String prompt) { + if (useDefaultSettings) { + this.model = model; + this.messages = new ArrayList<>(); + + Message systemMessage = new Message(); + systemMessage.setRole("system"); + systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。"); + this.messages.add(systemMessage); + + Message userMessage = new Message(); + userMessage.setRole("user"); + userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"软件开发,系统集成,网络工程,云计算,大数据,人工智能,物联网,区块链,信息安全,运维服务,测试认证,IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。"); + this.messages.add(userMessage); + + this.temperature = temperature; + this.max_tokens = maxTokens; + + this.response_format = new ResponseFormat(); + this.response_format.setType("json_object"); + } + } + + public static SkillRequest createDefault() { + return new SkillRequest(true); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Message implements Serializable { + private static final long serialVersionUID = 1L; + + private String role; + private String content; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ResponseFormat implements Serializable { + private static final long serialVersionUID = 1L; + + private String type; + } +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/entity/response/SkillResponse.java b/src/main/java/com/kexue/skills/entity/response/SkillResponse.java new file mode 100644 index 0000000..d9f55dc --- /dev/null +++ b/src/main/java/com/kexue/skills/entity/response/SkillResponse.java @@ -0,0 +1,15 @@ +package com.kexue.skills.entity.response; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class SkillResponse implements Serializable { + private static final long serialVersionUID = 1L; + + private String name; + private String description; + private List tags; +} \ No newline at end of file diff --git a/src/main/java/com/kexue/skills/service/SkillGenService.java b/src/main/java/com/kexue/skills/service/SkillGenService.java new file mode 100644 index 0000000..7b3c205 --- /dev/null +++ b/src/main/java/com/kexue/skills/service/SkillGenService.java @@ -0,0 +1,30 @@ +package com.kexue.skills.service; + +import com.kexue.skills.entity.request.SkillGenRequest; +import com.kexue.skills.entity.request.SkillAnalyzeRequest; +import com.kexue.skills.entity.response.SkillResponse; + +/** + * 技能生成服务接口 + * + * @author 维哥 + * @since 2026-01-28 + */ +public interface SkillGenService { + + /** + * 生成技能 + * + * @param request 生成请求 + * @return 生成结果 + */ + SkillResponse generateSkill(SkillGenRequest request); + + /** + * 分析技能 + * + * @param request 分析请求 + * @return 分析结果 + */ + String analyzeSkill(SkillAnalyzeRequest request); +} diff --git a/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java new file mode 100644 index 0000000..aa24312 --- /dev/null +++ b/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java @@ -0,0 +1,91 @@ +package com.kexue.skills.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.kexue.skills.common.util.HttpUtil; +import com.kexue.skills.config.DeepSeekConfig; +import com.kexue.skills.entity.request.SkillAnalyzeRequest; +import com.kexue.skills.entity.request.SkillGenRequest; +import com.kexue.skills.entity.request.SkillRequest; +import com.kexue.skills.entity.response.SkillResponse; +import com.kexue.skills.service.SkillGenService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 技能生成服务实现 + * + * @author 维哥 + * @since 2026-01-28 + */ +@Slf4j +@Service +public class SkillGenServiceImpl implements SkillGenService { + + @Autowired + private DeepSeekConfig deepSeekConfig; + + /** + * 生成技能 + * + * @param request 生成请求 + * @return 生成结果 + */ + @Override + public SkillResponse generateSkill(SkillGenRequest request) { + log.info("生成技能请求: {}", request); + String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions"; + SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), + deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),request.getPrompt()); + + try { + // 发送HTTP请求到deepseek API + String 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); + } + + return null; + } + + + + /** + * 分析技能 + * + * @param request 分析请求 + * @return 分析结果 + */ + @Override + public String analyzeSkill(SkillAnalyzeRequest request) { + log.info("分析技能请求: {}", request); + // 这里可以实现技能分析逻辑 + // 不需要数据库操作,直接返回结果 + return "技能分析成功: " + request.getSkillId(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8bf77c7..c2b93a0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,6 +27,15 @@ spring: max-request-size: 20MB max-file-size: 20MB + ai: + deepseek: + base-url: https://api.deepseek.com + api-key: ${DEEPSEEK_API_KEY:sk-7334f88754a84a86abbd98fc907d3354} + chat: + model: deepseek-chat + temperature: 0.3 + max-tokens: 500 + # 包含公共配置文件 spring.config.import: - classpath:application-common.yml @@ -72,3 +81,4 @@ sms: # 短信模板变量名,对应验证码的变量 template-param-name: code template-cache: true +