dify历史记录
This commit is contained in:
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.tik.dify.client;
|
||||
|
||||
import cn.iocoder.yudao.module.tik.dify.config.DifyProperties;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -38,9 +40,10 @@ public class DifyClient {
|
||||
* @param content 用户输入
|
||||
* @param systemPrompt 系统提示词
|
||||
* @param conversationId 会话ID(可选)
|
||||
* @param userId 用户标识(固定)
|
||||
* @return 流式响应
|
||||
*/
|
||||
public Flux<DifyChatRespVO> chatStream(String apiKey, String content, String systemPrompt, String conversationId) {
|
||||
public Flux<DifyChatRespVO> chatStream(String apiKey, String content, String systemPrompt, String conversationId, String userId) {
|
||||
String apiUrl = difyProperties.getApiUrl() + "/v1/chat-messages";
|
||||
|
||||
// 构建请求体 - 按照 Dify API 格式
|
||||
@@ -52,7 +55,7 @@ public class DifyClient {
|
||||
requestBody.put("query", content);
|
||||
requestBody.put("response_mode", "streaming");
|
||||
requestBody.put("conversation_id", conversationId != null ? conversationId : "");
|
||||
requestBody.put("user", "user-" + System.currentTimeMillis());
|
||||
requestBody.put("user", userId);
|
||||
|
||||
AtomicReference<String> responseConversationId = new AtomicReference<>(conversationId);
|
||||
StringBuilder fullContent = new StringBuilder();
|
||||
@@ -91,9 +94,10 @@ public class DifyClient {
|
||||
* @param inputs 自定义输入参数
|
||||
* @param content 用户输入
|
||||
* @param conversationId 会话ID(可选)
|
||||
* @param userId 用户标识(固定)
|
||||
* @return 流式响应
|
||||
*/
|
||||
public Flux<DifyChatRespVO> chatStreamWithInputs(String apiKey, Map<String, Object> inputs, String content, String conversationId) {
|
||||
public Flux<DifyChatRespVO> chatStreamWithInputs(String apiKey, Map<String, Object> inputs, String content, String conversationId, String userId) {
|
||||
String apiUrl = difyProperties.getApiUrl() + "/v1/chat-messages";
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
@@ -101,7 +105,7 @@ public class DifyClient {
|
||||
requestBody.put("query", content);
|
||||
requestBody.put("response_mode", "streaming");
|
||||
requestBody.put("conversation_id", conversationId != null ? conversationId : "");
|
||||
requestBody.put("user", "user-" + System.currentTimeMillis());
|
||||
requestBody.put("user", userId);
|
||||
|
||||
// 调试日志:打印完整请求体
|
||||
try {
|
||||
@@ -195,4 +199,77 @@ public class DifyClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
*
|
||||
* @param apiKey Dify API Key
|
||||
* @param user 用户标识
|
||||
* @param lastId 上一页最后一条记录ID(用于分页)
|
||||
* @param limit 返回条数
|
||||
* @return 会话列表
|
||||
*/
|
||||
public DifyConversationListRespVO getConversations(String apiKey, String user, String lastId, Integer limit) {
|
||||
StringBuilder params = new StringBuilder("?user=").append(user);
|
||||
if (lastId != null && !lastId.isEmpty()) {
|
||||
params.append("&last_id=").append(lastId);
|
||||
}
|
||||
if (limit != null) {
|
||||
params.append("&limit=").append(limit);
|
||||
}
|
||||
|
||||
String url = difyProperties.getApiUrl() + "/v1/conversations" + params;
|
||||
try {
|
||||
String response = executeGetRequest(url, apiKey);
|
||||
log.info("[getConversations] 获取会话列表成功,user: {}", user);
|
||||
return objectMapper.readValue(response, DifyConversationListRespVO.class);
|
||||
} catch (Exception e) {
|
||||
log.error("[getConversations] 获取会话列表失败,user: {}", user, e);
|
||||
throw new RuntimeException("获取会话列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话历史消息
|
||||
*
|
||||
* @param apiKey Dify API Key
|
||||
* @param conversationId 会话ID
|
||||
* @param user 用户标识
|
||||
* @param firstId 当前页第一条记录ID(用于分页)
|
||||
* @param limit 返回条数
|
||||
* @return 消息列表
|
||||
*/
|
||||
public DifyMessageListRespVO getMessages(String apiKey, String conversationId, String user, String firstId, Integer limit) {
|
||||
StringBuilder params = new StringBuilder("?conversation_id=").append(conversationId).append("&user=").append(user);
|
||||
if (firstId != null && !firstId.isEmpty()) {
|
||||
params.append("&first_id=").append(firstId);
|
||||
}
|
||||
if (limit != null) {
|
||||
params.append("&limit=").append(limit);
|
||||
}
|
||||
|
||||
String url = difyProperties.getApiUrl() + "/v1/messages" + params;
|
||||
try {
|
||||
String response = executeGetRequest(url, apiKey);
|
||||
log.info("[getMessages] 获取会话消息成功,conversationId: {}, user: {}", conversationId, user);
|
||||
return objectMapper.readValue(response, DifyMessageListRespVO.class);
|
||||
} catch (Exception e) {
|
||||
log.error("[getMessages] 获取会话消息失败,conversationId: {}, user: {}", conversationId, user, e);
|
||||
throw new RuntimeException("获取会话消息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 GET 请求
|
||||
*/
|
||||
private String executeGetRequest(String url, String apiKey) {
|
||||
return WebClient.builder()
|
||||
.baseUrl(url)
|
||||
.defaultHeader("Authorization", "Bearer " + apiKey)
|
||||
.build()
|
||||
.get()
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,15 +5,20 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.tik.dify.service.DifyService;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -27,27 +32,54 @@ import reactor.core.publisher.Flux;
|
||||
@RequestMapping("/api/tik/dify")
|
||||
public class AppDifyController {
|
||||
|
||||
private static final String DEFAULT_USER_ID = "1";
|
||||
|
||||
@Resource
|
||||
private DifyService difyService;
|
||||
|
||||
@PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@Operation(summary = "流式聊天")
|
||||
public Flux<CommonResult<DifyChatRespVO>> chatStream(@Valid @RequestBody DifyChatReqVO reqVO) {
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
String userId = loginUserId != null ? loginUserId.toString() : "1"; // 默认用户ID
|
||||
|
||||
return difyService.chatStream(reqVO, userId)
|
||||
return difyService.chatStream(reqVO, getCurrentUserId())
|
||||
.map(CommonResult::success);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/forecast/rewrite", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@Operation(summary = "Forecast 文案改写(流式)")
|
||||
public Flux<CommonResult<DifyChatRespVO>> rewriteStream(@Valid @RequestBody ForecastRewriteReqVO reqVO) {
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
String userId = loginUserId != null ? loginUserId.toString() : "1"; // 默认用户ID
|
||||
|
||||
return difyService.rewriteStream(reqVO, userId)
|
||||
return difyService.rewriteStream(reqVO, getCurrentUserId())
|
||||
.map(CommonResult::success);
|
||||
}
|
||||
|
||||
@GetMapping("/conversations")
|
||||
@Operation(summary = "获取会话列表")
|
||||
@Parameter(name = "agentId", description = "智能体ID", required = true)
|
||||
@Parameter(name = "lastId", description = "上一页最后一条记录ID")
|
||||
@Parameter(name = "limit", description = "返回条数,默认20")
|
||||
public CommonResult<DifyConversationListRespVO> getConversations(
|
||||
@RequestParam("agentId") Long agentId,
|
||||
@RequestParam(value = "lastId", required = false) String lastId,
|
||||
@RequestParam(value = "limit", required = false) Integer limit) {
|
||||
return CommonResult.success(difyService.getConversations(agentId, getCurrentUserId(), lastId, limit));
|
||||
}
|
||||
|
||||
@GetMapping("/messages")
|
||||
@Operation(summary = "获取会话历史消息")
|
||||
@Parameter(name = "agentId", description = "智能体ID", required = true)
|
||||
@Parameter(name = "conversationId", description = "会话ID", required = true)
|
||||
@Parameter(name = "firstId", description = "当前页第一条记录ID")
|
||||
@Parameter(name = "limit", description = "返回条数,默认20")
|
||||
public CommonResult<DifyMessageListRespVO> getMessages(
|
||||
@RequestParam("agentId") Long agentId,
|
||||
@RequestParam("conversationId") String conversationId,
|
||||
@RequestParam(value = "firstId", required = false) String firstId,
|
||||
@RequestParam(value = "limit", required = false) Integer limit) {
|
||||
return CommonResult.success(difyService.getMessages(agentId, conversationId, getCurrentUserId(), firstId, limit));
|
||||
}
|
||||
|
||||
private String getCurrentUserId() {
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
return loginUserId != null ? loginUserId.toString() : DEFAULT_USER_ID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.tik.dify.service;
|
||||
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
@@ -30,4 +32,27 @@ public interface DifyService {
|
||||
*/
|
||||
Flux<DifyChatRespVO> rewriteStream(ForecastRewriteReqVO reqVO, String userId);
|
||||
|
||||
/**
|
||||
* 获取会话列表
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
* @param userId 用户ID
|
||||
* @param lastId 上一页最后一条记录ID
|
||||
* @param limit 返回条数
|
||||
* @return 会话列表
|
||||
*/
|
||||
DifyConversationListRespVO getConversations(Long agentId, String userId, String lastId, Integer limit);
|
||||
|
||||
/**
|
||||
* 获取会话历史消息
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
* @param conversationId 会话ID
|
||||
* @param userId 用户ID
|
||||
* @param firstId 当前页第一条记录ID
|
||||
* @param limit 返回条数
|
||||
* @return 消息列表
|
||||
*/
|
||||
DifyMessageListRespVO getMessages(Long agentId, String conversationId, String userId, String firstId, Integer limit);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.tik.dify.service;
|
||||
import cn.iocoder.yudao.module.tik.dify.client.DifyClient;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
|
||||
import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO;
|
||||
import cn.iocoder.yudao.module.tik.enums.AiModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.tik.enums.AiPlatformEnum;
|
||||
@@ -47,6 +49,8 @@ public class DifyServiceImpl implements DifyService {
|
||||
AtomicLong pendingRecordId = new AtomicLong();
|
||||
// 用于存储会话ID
|
||||
AtomicReference<String> conversationIdRef = new AtomicReference<>(reqVO.getConversationId());
|
||||
// Dify 用户标识(固定格式)
|
||||
String difyUserId = "user-" + userId;
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
// 1. 获取智能体配置
|
||||
@@ -84,7 +88,8 @@ public class DifyServiceImpl implements DifyService {
|
||||
context.apiKey(),
|
||||
reqVO.getContent(),
|
||||
context.systemPrompt(),
|
||||
reqVO.getConversationId()
|
||||
reqVO.getConversationId(),
|
||||
difyUserId
|
||||
)
|
||||
.doOnNext(resp -> {
|
||||
if (resp.getConversationId() != null) {
|
||||
@@ -142,6 +147,8 @@ public class DifyServiceImpl implements DifyService {
|
||||
AtomicLong pendingRecordId = new AtomicLong();
|
||||
// 用于存储会话ID
|
||||
AtomicReference<String> conversationIdRef = new AtomicReference<>("");
|
||||
// Dify 用户标识(固定格式)
|
||||
String difyUserId = "user-" + userId;
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
// 1. 获取智能体配置(通过 agentId 获取 systemPrompt)
|
||||
@@ -199,7 +206,8 @@ public class DifyServiceImpl implements DifyService {
|
||||
context.apiKey(),
|
||||
context.inputs(),
|
||||
reqVO.getUserText(),
|
||||
null
|
||||
null,
|
||||
difyUserId
|
||||
)
|
||||
.doOnNext(resp -> {
|
||||
if (resp.getConversationId() != null) {
|
||||
@@ -260,4 +268,42 @@ public class DifyServiceImpl implements DifyService {
|
||||
*/
|
||||
private record ForecastRewriteContext(Map<String, Object> inputs, String apiKey, Integer consumePoints) {}
|
||||
|
||||
@Override
|
||||
public DifyConversationListRespVO getConversations(Long agentId, String userId, String lastId, Integer limit) {
|
||||
// 获取智能体配置
|
||||
AiAgentDO agent = aiAgentService.getAiAgent(agentId);
|
||||
if (agent == null) {
|
||||
throw new RuntimeException("智能体不存在");
|
||||
}
|
||||
|
||||
// 获取积分配置(使用标准模式的 API Key)
|
||||
AiModelConfigDO config = pointsService.getConfig(
|
||||
AiPlatformEnum.DIFY.getPlatform(),
|
||||
AiModelTypeEnum.DIFY_WRITING_STANDARD.getModelCode());
|
||||
|
||||
// Dify 用户标识
|
||||
String difyUserId = "user-" + userId;
|
||||
|
||||
return difyClient.getConversations(config.getApiKey(), difyUserId, lastId, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DifyMessageListRespVO getMessages(Long agentId, String conversationId, String userId, String firstId, Integer limit) {
|
||||
// 获取智能体配置
|
||||
AiAgentDO agent = aiAgentService.getAiAgent(agentId);
|
||||
if (agent == null) {
|
||||
throw new RuntimeException("智能体不存在");
|
||||
}
|
||||
|
||||
// 获取积分配置(使用标准模式的 API Key)
|
||||
AiModelConfigDO config = pointsService.getConfig(
|
||||
AiPlatformEnum.DIFY.getPlatform(),
|
||||
AiModelTypeEnum.DIFY_WRITING_STANDARD.getModelCode());
|
||||
|
||||
// Dify 用户标识
|
||||
String difyUserId = "user-" + userId;
|
||||
|
||||
return difyClient.getMessages(config.getApiKey(), conversationId, difyUserId, firstId, limit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.tik.dify.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Dify 会话列表响应 VO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Schema(description = "Dify 会话列表响应")
|
||||
@Data
|
||||
public class DifyConversationListRespVO {
|
||||
|
||||
@Schema(description = "返回条数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Integer limit;
|
||||
|
||||
@Schema(description = "是否有更多数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Boolean hasMore;
|
||||
|
||||
@Schema(description = "会话列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<DifyConversationRespVO> data;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.iocoder.yudao.module.tik.dify.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Dify 会话响应 VO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Schema(description = "Dify 会话响应")
|
||||
@Data
|
||||
public class DifyConversationRespVO {
|
||||
|
||||
@Schema(description = "会话ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String id;
|
||||
|
||||
@Schema(description = "会话名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "输入参数")
|
||||
private Map<String, Object> inputs;
|
||||
|
||||
@Schema(description = "会话状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "开场白")
|
||||
private String introduction;
|
||||
|
||||
@Schema(description = "创建时间(时间戳)")
|
||||
@JsonProperty("created_at")
|
||||
private Long createdAt;
|
||||
|
||||
@Schema(description = "更新时间(时间戳)")
|
||||
@JsonProperty("updated_at")
|
||||
private Long updatedAt;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.tik.dify.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Dify 消息列表响应 VO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Schema(description = "Dify 消息列表响应")
|
||||
@Data
|
||||
public class DifyMessageListRespVO {
|
||||
|
||||
@Schema(description = "返回条数", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Integer limit;
|
||||
|
||||
@Schema(description = "是否有更多数据", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Boolean hasMore;
|
||||
|
||||
@Schema(description = "消息列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<DifyMessageRespVO> data;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.iocoder.yudao.module.tik.dify.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Dify 消息响应 VO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Schema(description = "Dify 消息响应")
|
||||
@Data
|
||||
public class DifyMessageRespVO {
|
||||
|
||||
@Schema(description = "消息ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String id;
|
||||
|
||||
@Schema(description = "会话ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@JsonProperty("conversation_id")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "输入参数")
|
||||
private Map<String, Object> inputs;
|
||||
|
||||
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String query;
|
||||
|
||||
@Schema(description = "AI回答", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String answer;
|
||||
|
||||
@Schema(description = "消息文件列表")
|
||||
@JsonProperty("message_files")
|
||||
private List<MessageFile> messageFiles;
|
||||
|
||||
@Schema(description = "用户反馈")
|
||||
private Feedback feedback;
|
||||
|
||||
@Schema(description = "创建时间(时间戳)")
|
||||
@JsonProperty("created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* 消息文件
|
||||
*/
|
||||
@Data
|
||||
public static class MessageFile {
|
||||
private String id;
|
||||
private String type;
|
||||
private String url;
|
||||
|
||||
@JsonProperty("belongs_to")
|
||||
private String belongsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户反馈
|
||||
*/
|
||||
@Data
|
||||
public static class Feedback {
|
||||
private String rating;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user