feat(ai): 集成DeepSeek AI服务实现技能生成与分析功能
- 添加DeepSeek API配置类和application.yml配置 - 实现HTTP工具类用于发送AI API请求 - 创建技能生成和分析的请求响应实体类 - 开发技能生成控制器提供generate和analyze接口 - 实现SkillGenService服务完成AI交互逻辑 - 设计SkillRequest封装聊天完成API调用参数 - 添加跨域支持和Swagger API文档注解
This commit is contained in:
parent
5bb2bfe0b9
commit
18787b68a0
|
|
@ -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<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
return response.body();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<SkillResponse> 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<String> analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest request) {
|
||||
return CommonResult.success(skillGenService.analyzeSkill(request));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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<Message> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> tags;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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<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);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 分析技能
|
||||
*
|
||||
* @param request 分析请求
|
||||
* @return 分析结果
|
||||
*/
|
||||
@Override
|
||||
public String analyzeSkill(SkillAnalyzeRequest request) {
|
||||
log.info("分析技能请求: {}", request);
|
||||
// 这里可以实现技能分析逻辑
|
||||
// 不需要数据库操作,直接返回结果
|
||||
return "技能分析成功: " + request.getSkillId();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue