Commit f6dd6d63 authored by 何处是我家's avatar 何处是我家
Browse files

提交

parent fe85dec5
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.yuanbaobaoo</groupId>
<artifactId>dify-java-client</artifactId>
<version>0.15.x.2</version>
<description>Dify Java 客户端</description>
<url>https://github.com/yuanbaobaoo/dify-java-client</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>yuanbaobaoo</name>
<email>hodge.yuan@hotmail.com</email>
<timezone>+8</timezone>
</developer>
</developers>
<scm>
<url>https://github.com/yuanbaobaoo/dify-java-client</url>
</scm>
<properties>
<serverId>central</serverId>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.53</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension</artifactId>
<version>2.0.53</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.8.28</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.11.2</version>
<configuration>
<doclint>none</doclint>
<charset>${project.build.sourceEncoding}</charset>
<docencoding>${project.build.sourceEncoding}</docencoding>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.7</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.7.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>${serverId}</publishingServerId>
</configuration>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>${serverId}</id>
<url>https://central.sonatype.com/</url>
</snapshotRepository>
</distributionManagement>
</project>
\ No newline at end of file
package io.github.yuanbaobaoo.dify.client;
import io.github.yuanbaobaoo.dify.client.impl.DifyBaseClientImpl;
import io.github.yuanbaobaoo.dify.client.impl.DifyChatClientImpl;
import io.github.yuanbaobaoo.dify.client.impl.DifyWorkFlowClientImpl;
/**
* Dify Client builder
*/
public class DifyClientBuilder {
private String baseUrl = "http://localhost:5001";
private String apiKey;
/**
* create
* @return DifyClientBuilder
*/
public static DifyClientBuilder create() {
return new DifyClientBuilder();
}
/**
* dify server base url
* @param baseUrl String
*/
public DifyClientBuilder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
/**
* dify app api key
* @param apiKey String
*/
public DifyClientBuilder apiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
/**
* build DifyBaseClient
* @return IDifyClient
*/
public IDifyClient build() {
if (baseUrl == null || apiKey == null) {
throw new RuntimeException("Dify Client Build Error: params is not defined");
}
return new DifyBaseClientImpl(baseUrl, apiKey);
}
/**
* build DifyChatClient
* @return IDifyChatClient
*/
public IDifyChatClient buildChat() {
if (baseUrl == null || apiKey == null) {
throw new RuntimeException("Dify Client Build Error: params is not defined");
}
return new DifyChatClientImpl(baseUrl, apiKey);
}
/**
* build DifyWorkFlowClient
* @return IDifyWorkFlowClient
*/
public IDifyWorkFlowClient buildWorkFlow() {
if (baseUrl == null || apiKey == null) {
throw new RuntimeException("Dify Client Build Error: params is not defined");
}
return new DifyWorkFlowClientImpl(baseUrl, apiKey);
}
}
package io.github.yuanbaobaoo.dify.client;
import com.alibaba.fastjson2.JSONObject;
import io.github.yuanbaobaoo.dify.client.params.ParamMessage;
import io.github.yuanbaobaoo.dify.client.types.DifyChatResult;
import io.github.yuanbaobaoo.dify.types.DifyException;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* 适用于对话应用的客户端
* ChatBot、Agent、ChatFlow
*/
public interface IDifyChatClient extends IDifyClient {
/**
* 发送对话消息(同步接收)
* @param message ParamMessage
*/
DifyChatResult sendMessages(ParamMessage message);
/**
* 发送对话消息(流式接收)
* @param message ParamMessage
* @param consumer Consumer<DifyChatResult>
*/
CompletableFuture<Void> sendMessagesAsync(ParamMessage message, Consumer<DifyChatResult> consumer);
/**
* 停止响应(仅支持流式模式)
* @param taskId 任务 ID,可在流式返回 Chunk 中获取
* @param user 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。
*/
Boolean stopResponse(String taskId, String user);
/**
* 获取下一轮建议问题列表
* @param messageId 消息ID
* @param user 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
*/
List<String> suggestedList(String messageId, String user);
/**
* 获取会话历史消息
* @param conversationId 会话ID
* @param user 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
* @param limit 一次请求返回多少条聊天记录,默认 20 条。
* @param firstId 当前页第一条聊天记录的 ID,默认为空,表示获取第一页数据
*/
JSONObject history(String conversationId, String user, Integer limit, String firstId);
/**
* 获取会话列表
* @param user 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
* @param lastId (选填)当前页最后面一条记录的 ID,默认 null
* @param sortBy (选填)排序字段,默认 -updated_at(按更新时间倒序排列)
* 可选值:created_at, -created_at, updated_at, -updated_at
* 字段前面的符号代表顺序或倒序,-代表倒序
* @param limit (选填)一次请求返回多少条记录,默认 20 条,最大 100 条,最小 1 条。
*/
JSONObject conversations(String user, Integer limit, String sortBy, String lastId);
/**
* 删除会话
* @param conversationId 会话 ID
* @param user 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
*/
Boolean deleteConversation(String conversationId, String user);
/**
* 会话重命名
* @param conversationId 会话ID
* @param user 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
* @param name 会话名称
*/
JSONObject renameConversation(String conversationId, String user, String name);
/**
* 语音转文字
* @param file 语音文件。 支持格式:['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm'] 文件
* 大小限制:15MB
* @param user 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
*/
String audioToText(File file, String user) throws DifyException, IOException, InterruptedException;
// 文字转语音
//void textToAudio();
}
package io.github.yuanbaobaoo.dify.client;
import io.github.yuanbaobaoo.dify.types.DifyException;
import io.github.yuanbaobaoo.dify.types.DifyRoute;
import io.github.yuanbaobaoo.dify.client.types.DifyFileResult;
import io.github.yuanbaobaoo.dify.routes.HttpMethod;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* 基础Dify客户端
*/
public interface IDifyClient {
/**
* 获取应用基础信息
* @return String
*/
String getAppInfo() throws IOException, InterruptedException;
/**
* 获取应用参数信息
* @return String
*/
String getAppParameters() throws IOException, InterruptedException;
/**
* 获取应用元数据
* @return String
*/
String getAppMetaInfo() throws IOException, InterruptedException;
/**
* 上传文件
*
* @param file 文件对象
* @param user 用户标识
* @return DifyFileResult
*/
DifyFileResult uploadFile(File file, String user) throws DifyException, IOException, InterruptedException;
/**
* 发送同步接口请求
*
* @param route DifyRoute
* @param params Body params
* @return String
*/
String sendBlocking(DifyRoute route, Map<String, Object> params) throws DifyException, IOException, InterruptedException;
/**
* 发送同步接口请求
*
* @param route DifyRoute
* @param query Query params
* @param params Body params
* @return String
*/
String sendBlocking(DifyRoute route, Map<String, Object> query, Map<String, Object> params)
throws DifyException, IOException, InterruptedException
;
/**
* 发送流式接口请求
*
* @param route DifyRoute
* @param params Body params
* @return CompletableFuture<Void>
*/
CompletableFuture<Void> sendStreaming(DifyRoute route, Map<String, Object> params, Consumer<String> consumer);
/**
* 发送流式接口请求
*
* @param route DifyRoute
* @param query Query params
* @param params Body params
* @return CompletableFuture<Void>
*/
CompletableFuture<Void> sendStreaming(
DifyRoute route,
Map<String, Object> query,
Map<String, Object> params,
Consumer<String> consumer
);
/**
* request by application/json content-type
*
* @param route DifyRoute
* @return String
*/
String requestJson(DifyRoute route)
throws DifyException, IOException, InterruptedException
;
/**
* request by application/json content-type
*
* @param route DifyRoute
* @param query Query 查询参数
* @return String
*/
String requestJson(DifyRoute route, Map<String, Object> query)
throws DifyException, IOException, InterruptedException
;
/**
* request by application/json content-type
*
* @param route DifyRoute
* @param query Query 查询参数
* @param params Body 参数
* @return String
*/
String requestJson(DifyRoute route, Map<String, Object> query, Object params)
throws DifyException, IOException, InterruptedException
;
/**
* request by application/json content-type
*
* @param url API URL
* @param method HTTP请求方法
* @param query Query 查询参数
* @param params Body 参数
* @return String
*/
String requestJson(String url, HttpMethod method, Map<String, Object> query, Object params)
throws DifyException, IOException, InterruptedException
;
/**
* request by multipart/form-data content-type
*
* @param route DifyRoute
* @param query Query 查询参数
* @param params Body 参数,文件流需自行在params中传入
* @return String
*/
String requestMultipart(DifyRoute route, Map<String, Object> query, Map<String, Object> params)
throws DifyException, IOException, InterruptedException
;
/**
* request by multipart/form-data content-type
*
* @param url API URL
* @param method HTTP请求方法
* @param query Query 查询参数
* @param params Body 参数,文件流需自行在params中传入
* @return String
*/
String requestMultipart(String url, HttpMethod method, Map<String, Object> query, Map<String, Object> params)
throws DifyException, IOException, InterruptedException
;
}
package io.github.yuanbaobaoo.dify.client;
import com.alibaba.fastjson2.JSONObject;
import io.github.yuanbaobaoo.dify.client.params.ParamMessage;
import io.github.yuanbaobaoo.dify.client.types.DifyWorkFlowResult;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* 适用于工作流应用的客户端
* @example workflow
*/
public interface IDifyWorkFlowClient extends IDifyClient {
/**
* 执行工作流(流式模式)
* @param message ParamMessage
* @param consumer Consumer<DifyWorkFlowResult>
*/
CompletableFuture<Void> runStreaming(ParamMessage message, Consumer<DifyWorkFlowResult> consumer);
/**
* 执行工作流(阻塞模式)
* @param message ParamMessage
*/
JSONObject runBlocking(ParamMessage message);
/**
* 获取工作流执行情况
* @param workFlowId 工作流ID
*/
JSONObject getWorkFlowStatus(String workFlowId);
/**
* 停止工作流(仅限流式模式)
* @param taskId 任务ID(流式返回chunk中获取)
*/
Boolean stopWorkFlow(String taskId);
// /**
// * 上传文件
// */
// JSONObject uploadFile(String filePath, String user);
//
// /**
// * 获取工作流日志
// * @param keyword 关键字
// * @param status 执行状态(succeeded/failed/stopped)
// * @param page 页码
// * @param limit 每页数量
// */
// JSONObject getWorkFlowLog(String keyword, String status, Integer page, Integer limit);
//
// /**
// * 获取应用信息
// */
// JSONObject getWorkFlowInfo();
//
// /**
// * 获取应用参数
// */
// JSONObject getWorkFlowParameters();
}
package io.github.yuanbaobaoo.dify.client.impl;
import cn.hutool.core.net.url.UrlBuilder;
import com.alibaba.fastjson2.JSON;
import io.github.yuanbaobaoo.dify.types.DifyException;
import io.github.yuanbaobaoo.dify.types.DifyRoute;
import lombok.extern.slf4j.Slf4j;
import io.github.yuanbaobaoo.dify.client.IDifyClient;
import io.github.yuanbaobaoo.dify.client.types.DifyFileResult;
import io.github.yuanbaobaoo.dify.routes.DifyRoutes;
import io.github.yuanbaobaoo.dify.routes.HttpMethod;
import io.github.yuanbaobaoo.dify.types.ResponseMode;
import java.io.File;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@Slf4j
public class DifyBaseClientImpl implements IDifyClient {
private final String server;
private final String apiKey;
private final HttpClient httpClient;
/**
* constructor
*
* @param server Dify Server URL
* @param apiKey The App Api Key
*/
public DifyBaseClientImpl(String server, String apiKey) {
this.apiKey = apiKey;
this.server = server;
this.httpClient = HttpClient.newHttpClient();
}
@Override
public String getAppInfo() throws DifyException, IOException, InterruptedException {
return requestJson(DifyRoutes.INFO, null, null);
}
@Override
public String getAppParameters() throws DifyException, IOException, InterruptedException {
return requestJson(DifyRoutes.PARAMETERS, null, null);
}
@Override
public String getAppMetaInfo() throws DifyException, IOException, InterruptedException {
return requestJson(DifyRoutes.META_INFO, null, null);
}
@Override
public DifyFileResult uploadFile(File file, String user) throws DifyException, IOException, InterruptedException {
Map<String, Object> data = new HashMap<>();
data.put("file", file);
data.put("user", user);
String result = requestMultipart(DifyRoutes.FILE_UPLOAD, null, data);
return JSON.parseObject(result, DifyFileResult.class);
}
@Override
public String sendBlocking(DifyRoute route, Map<String, Object> params) throws DifyException, IOException, InterruptedException {
return sendBlocking(route, null, params);
}
@Override
public String sendBlocking(DifyRoute route, Map<String, Object> query, Map<String, Object> params)
throws DifyException, IOException, InterruptedException {
// body请求对象中,强制覆盖 response_mode
params.put("response_mode", ResponseMode.blocking);
return requestJson(route, query, params);
}
@Override
public CompletableFuture<Void> sendStreaming(DifyRoute route, Map<String, Object> params, Consumer<String> consumer) {
return sendStreaming(route, null, params, consumer);
}
@Override
public CompletableFuture<Void> sendStreaming(DifyRoute route, Map<String, Object> query, Map<String, Object> params, Consumer<String> consumer) {
// body请求对象中,强制覆盖 response_mode
params.put("response_mode", ResponseMode.streaming);
HttpRequest.Builder builder = buildRequest(route.getUrl(), query);
if (route.getMethod() == HttpMethod.GET) {
builder.method(route.getMethod().name(), BodyPublishers.noBody());
} else {
builder.header("Content-Type", "application/json");
builder.method(route.getMethod().name(), BodyPublishers.ofString(JSON.toJSONString(params)));
}
// 异步发送请求并处理响应
return httpClient.sendAsync(builder.build(), BodyHandlers.ofLines()).thenAccept(response -> {
response.body().forEach(line -> {
if (response.statusCode() >= 400) {
throw new DifyException(line, response.statusCode());
}
final String FLAG = "data:";
if (line.startsWith(FLAG)) {
consumer.accept(line.substring(FLAG.length()).trim());
}
});
});
}
@Override
public String requestJson(DifyRoute route)
throws DifyException, IOException, InterruptedException {
return requestJson(route.getUrl(), route.getMethod(), null, null);
}
@Override
public String requestJson(DifyRoute route, Map<String, Object> query)
throws DifyException, IOException, InterruptedException {
return requestJson(route.getUrl(), route.getMethod(), query, null);
}
@Override
public String requestJson(DifyRoute route, Map<String, Object> query, Object params)
throws DifyException, IOException, InterruptedException {
return requestJson(route.getUrl(), route.getMethod(), query, params);
}
@Override
public String requestJson(String url, HttpMethod method, Map<String, Object> query, Object params)
throws DifyException, IOException, InterruptedException {
HttpRequest.Builder builder = buildRequest(url, query);
if (params == null) {
params = new HashMap<>();
}
if (method == HttpMethod.GET) {
builder.method(method.name(), BodyPublishers.noBody());
} else {
builder.header("Content-Type", "application/json");
builder.method(method.name(), BodyPublishers.ofString(JSON.toJSONString(params)));
}
HttpResponse<String> response = httpClient.send(builder.build(), BodyHandlers.ofString());
// 所有非200响应都被视为dify平台的错误响应,统一返回异常对象
if (response.statusCode() >= 400) {
throw new DifyException(response.body(), response.statusCode());
}
return response.body();
}
@Override
public String requestMultipart(DifyRoute route, Map<String, Object> query, Map<String, Object> params)
throws DifyException, IOException, InterruptedException {
return requestMultipart(route.getUrl(), route.getMethod(), query, params);
}
@Override
public String requestMultipart(String url, HttpMethod method, Map<String, Object> query, Map<String, Object> params)
throws DifyException, IOException, InterruptedException {
HttpRequest.Builder builder = buildRequest(url, query);
if (params == null) {
params = new HashMap<>();
}
String boundary = "-----------" + UUID.randomUUID().toString().replaceAll("-", "");
builder.header("Content-Type", "multipart/form-data; boundary=" + boundary);
builder.method(method.name(), convertMapToMultipart(boundary, params));
HttpResponse<String> response = httpClient.send(builder.build(), BodyHandlers.ofString());
// 所有非200响应都被视为dify平台的错误响应,统一返回异常对象
if (response.statusCode() >= 400) {
throw new DifyException(response.body(), response.statusCode());
}
return response.body();
}
/**
* build request
*
* @param url api url
* @param query Query params
*/
private HttpRequest.Builder buildRequest(String url, Map<String, Object> query) {
HttpRequest.Builder builder = HttpRequest.newBuilder();
UrlBuilder urlBuilder = UrlBuilder.ofHttp(this.server);
urlBuilder.addPath(url);
if (query != null) {
query.forEach((key, value) -> {
if (value != null && key != null) {
urlBuilder.addQuery(key, value.toString());
}
});
}
builder.uri(urlBuilder.toURI());
builder.header("Authorization", "Bearer " + this.apiKey);
return builder;
}
/**
* 将 Map 转换为 multipart/form-data 格式的 BodyPublisher
* 支持类型:String(文本)、Path/File(文件)
*/
private HttpRequest.BodyPublisher convertMapToMultipart(String boundary, Map<String, Object> data) throws IOException {
var parts = new ArrayList<HttpRequest.BodyPublisher>();
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Path || value instanceof File) {
// 文件字段(支持 Path 或 File 对象)
Path filePath = (value instanceof Path) ? (Path) value : ((File) value).toPath();
String fileName = filePath.getFileName().toString();
byte[] fileBytes = Files.readAllBytes(filePath);
String mimeType = Files.probeContentType(filePath);
parts.add(BodyPublishers.ofString(
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n" +
"Content-Type: " + (mimeType != null ? mimeType : "application/octet-stream") + "\r\n\r\n"
));
parts.add(BodyPublishers.ofByteArray(fileBytes));
parts.add(BodyPublishers.ofString("\r\n"));
} else {
// 其他字符都当作字符串
parts.add(BodyPublishers.ofString(
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"" + key + "\"\r\n" +
"Content-Type: text/plain\r\n\r\n" +
value + "\r\n"
));
}
}
// 添加结束边界
parts.add(BodyPublishers.ofString("--" + boundary + "--\r\n"));
// 合并所有部分
return BodyPublishers.concat(parts.toArray(new HttpRequest.BodyPublisher[0]));
}
}
package io.github.yuanbaobaoo.dify.client.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.github.yuanbaobaoo.dify.client.types.DifyFileResult;
import io.github.yuanbaobaoo.dify.types.DifyException;
import lombok.extern.slf4j.Slf4j;
import io.github.yuanbaobaoo.dify.client.IDifyChatClient;
import io.github.yuanbaobaoo.dify.client.params.ParamMessage;
import io.github.yuanbaobaoo.dify.client.types.DifyChatEvent;
import io.github.yuanbaobaoo.dify.client.types.DifyChatResult;
import io.github.yuanbaobaoo.dify.routes.DifyRoutes;
import io.github.yuanbaobaoo.dify.routes.HttpMethod;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@Slf4j
public class DifyChatClientImpl extends DifyBaseClientImpl implements IDifyChatClient {
/**
* constructor
*
* @param server Dify Server URL
* @param apiKey The App Api Key
*/
public DifyChatClientImpl(String server, String apiKey) {
super(server, apiKey);
}
@Override
public DifyChatResult sendMessages(ParamMessage message) {
try {
String result = sendBlocking(DifyRoutes.CHAT_CHAT_MESSAGES, message.toMap());
return DifyChatResult.builder().event(DifyChatEvent.message).payload(JSON.parseObject(result)).build();
} catch (DifyException e) {
throw e;
} catch (Exception e) {
log.error("sendMessages", e);
throw new DifyException("消息发送异常", 500);
}
}
@Override
public CompletableFuture<Void> sendMessagesAsync(ParamMessage message, Consumer<DifyChatResult> consumer) {
return sendStreaming(DifyRoutes.CHAT_CHAT_MESSAGES, message.toMap(), (line) -> {
JSONObject json = JSON.parseObject(line);
consumer.accept(DifyChatResult.builder().event(json.getString("event")).payload(json).build());
});
}
@Override
public Boolean stopResponse(String taskId, String user) {
try {
String result = requestJson(
String.format("%s/%s/stop", DifyRoutes.CHAT_CHAT_MESSAGES.getUrl(), taskId),
HttpMethod.POST,
null,
new HashMap<>() {{
put("user", user);
}}
);
JSONObject json = JSON.parseObject(result);
return "success".equals(json.getString("result"));
} catch (DifyException e) {
log.error("stopResponse: {}", e.getOriginal());
} catch (Exception e) {
log.error("stopResponse", e);
}
return false;
}
@Override
public List<String> suggestedList(String messageId, String user) {
try {
String result = requestJson(
String.format("%s/%s/suggested", DifyRoutes.CHAT_MESSAGES.getUrl(), messageId),
HttpMethod.GET,
new HashMap<>() {{
put("user", user);
}},
null
);
JSONObject json = JSON.parseObject(result);
if ("success".equals(json.getString("result"))) {
return json.getJSONArray("data").toList(String.class);
}
return new ArrayList<>();
} catch (DifyException e) {
log.error("suggestedList: {}", e.getOriginal());
} catch (Exception e) {
log.error("suggestedList", e);
}
return null;
}
@Override
public JSONObject history(String conversationId, String user, Integer limit, String firstId) {
try {
String result = requestJson(DifyRoutes.CHAT_MESSAGES, new HashMap<>() {{
put("conversation_id", conversationId);
put("user", user);
put("limit", limit);
put("first_id", firstId);
}}, null);
if (result == null) {
return null;
}
return JSON.parseObject(result);
} catch (DifyException e) {
throw e;
} catch (Exception e) {
log.error("history", e);
throw new DifyException("获取会话历史消息异常", 500);
}
}
@Override
public JSONObject conversations(String user, Integer limit, String sortBy, String lastId) {
try {
String result = requestJson(DifyRoutes.CHAT_CONVERSATIONS, new HashMap<>() {{
put("user", user);
put("last_id", lastId);
put("sort_by", sortBy);
put("limit", limit);
}}, null);
if (result == null) {
return null;
}
return JSON.parseObject(result);
} catch (DifyException e) {
throw e;
} catch (Exception e) {
log.error("conversations", e);
throw new DifyException("获取会话列表异常", 500);
}
}
@Override
public Boolean deleteConversation(String conversationId, String user) {
try {
String result = requestJson(
DifyRoutes.CHAT_CONVERSATIONS.getUrl() + "/" + conversationId,
HttpMethod.DELETE,
null,
new HashMap<>() {{
put("user", user);
}}
);
JSONObject json = JSON.parseObject(result);
return "success".equals(json.getString("result"));
} catch (DifyException e) {
log.error("deleteConversation: {}", e.getOriginal());
} catch (Exception e) {
log.error("deleteConversation", e);
}
return false;
}
@Override
public JSONObject renameConversation(String conversationId, String user, String name) {
try {
String result = requestJson(
String.format("%s/%s/name", DifyRoutes.CHAT_CONVERSATIONS.getUrl(), conversationId),
HttpMethod.POST,
null,
new HashMap<>() {{
put("user", user);
put("name", name);
put("auto_generate", false);
}}
);
return JSON.parseObject(result);
} catch (DifyException e) {
log.error("renameConversation: {}", e.getOriginal());
} catch (Exception e) {
log.error("renameConversation", e);
}
return null;
}
@Override
public String audioToText(File file, String user) throws DifyException, IOException, InterruptedException {
Map<String, Object> data = new HashMap<>();
data.put("file", file);
data.put("user", user);
JSONObject result = JSON.parseObject(requestMultipart(DifyRoutes.AUDIO_TO_TEXT, null, data));
return result.getString("text");
}
}
package io.github.yuanbaobaoo.dify.client.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.github.yuanbaobaoo.dify.client.params.ParamMessage;
import io.github.yuanbaobaoo.dify.client.types.DifyWorkFlowResult;
import io.github.yuanbaobaoo.dify.routes.DifyRoutes;
import io.github.yuanbaobaoo.dify.types.DifyException;
import lombok.extern.slf4j.Slf4j;
import io.github.yuanbaobaoo.dify.client.IDifyWorkFlowClient;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@Slf4j
public class DifyWorkFlowClientImpl extends DifyBaseClientImpl implements IDifyWorkFlowClient {
/**
* constructor
*
* @param server Dify Server URL
* @param apiKey The App Api Key
*/
public DifyWorkFlowClientImpl(String server, String apiKey) {
super(server, apiKey);
}
@Override
public CompletableFuture<Void> runStreaming(ParamMessage message, Consumer<DifyWorkFlowResult> consumer) {
return sendStreaming(DifyRoutes.WORKFLOW_RUN, message.toMap(), (line) -> {
JSONObject json = JSON.parseObject(line);
consumer.accept(DifyWorkFlowResult.builder().event(json.getString("event")).payload(json).build());
});
}
@Override
public JSONObject runBlocking(ParamMessage message) {
try {
String result = sendBlocking(DifyRoutes.WORKFLOW_RUN, message.toMap());
return JSON.parseObject(result);
} catch (DifyException e) {
throw e;
} catch (Exception e) {
log.error("sendMessages", e);
throw new DifyException("消息发送异常", 500);
}
}
@Override
public JSONObject getWorkFlowStatus(String workFlowId) {
return null;
}
@Override
public Boolean stopWorkFlow(String taskId) {
return null;
}
}
package io.github.yuanbaobaoo.dify.client.params;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Getter
@Setter
@Builder
public class ParamMessage {
/**
* 用户输入/提问内容。
*/
private String query;
/**
* 允许传入 App 定义的各变量值
* 如果变量是文件类型,请指定一个包含以下 files 中所述键的对象
*/
private Map<String, Object> inputs;
/**
* 用户标识
*/
private String user;
/**
* 会话 ID
*/
@JSONField(name = "conversation_id", alternateNames = {"conversationId"})
private String conversationId;
/**
* 文件列表,适用于传入文件结合文本理解并回答问题,仅当模型支持 Vision 能力时可用
* 具体请看官方文档
*/
private List<Map<String, Object>> files;
/**
* ToMap
* @return Map<String, Object>
*/
public Map<String, Object> toMap() {
return new HashMap<>(){{
put("query", query);
put("inputs", inputs);
put("user", user);
put("conversation_id", conversationId);
put("files", files);
}};
}
}
package io.github.yuanbaobaoo.dify.client.types;
/**
* dify chat-message 消息事件
* 来源:https://docs.dify.ai/
*/
public class DifyChatEvent {
public static final String message = "message";
public static final String nodeRetry = "node_retry";
public static final String messageFile = "message_file";
public static final String messageEnd = "message_end";
public static final String ttsMessage = "tts_message";
public static final String ttsMessageEnd = "tts_message_end";
public static final String messageReplace = "message_replace";
public static final String workflowStarted = "workflow_started";
public static final String nodeStarted = "node_started";
public static final String nodeFinished = "node_finished";
public static final String workflowFinished = "workflow_finished";
public static final String error = "error";
}
package io.github.yuanbaobaoo.dify.client.types;
import com.alibaba.fastjson2.JSONObject;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class DifyChatResult {
/**
* 事件类型
*/
private String event;
/**
* 返回的消息内容
*/
private JSONObject payload;
}
package io.github.yuanbaobaoo.dify.client.types;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DifyFileResult {
private String id;
private String name;
private Long size;
private String extension;
private String mimeType;
private String createdBy;
private Long createdAt;
}
package io.github.yuanbaobaoo.dify.client.types;
import com.alibaba.fastjson2.JSONObject;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Builder
public class DifyWorkFlowResult {
/**
* 事件类型
*/
private String event;
/**
* 返回的消息内容
*/
private JSONObject payload;
}
package io.github.yuanbaobaoo.dify.routes;
import io.github.yuanbaobaoo.dify.types.DifyRoute;
public class DifyRoutes {
/**
* DESC: Get Application Basic Information
* Type: Public
*/
public static final DifyRoute INFO = new DifyRoute("/info", HttpMethod.GET);
/**
* DESC: Get Application Parameters Information
* TYPE: Public
*/
public static final DifyRoute PARAMETERS = new DifyRoute("/parameters", HttpMethod.GET);
/**
* DESC: Get Application Meta Information
*/
public static final DifyRoute META_INFO = new DifyRoute("/meta", HttpMethod.GET);
/**
* DESC: File Upload
* Type: Public
*/
public static final DifyRoute FILE_UPLOAD = new DifyRoute("/files/upload", HttpMethod.POST);
/**
* Desc: Get Conversation History Messages
* Type: ChatFlow、Chat、Agent
*/
public static final DifyRoute CHAT_MESSAGES = new DifyRoute("/messages", HttpMethod.GET);
/**
* Get | Delete Conversations
* Type: ChatFlow、Chat、Agent
*/
public static final DifyRoute CHAT_CONVERSATIONS = new DifyRoute("/conversations", HttpMethod.GET);
/**
* Desc: Get Conversation History Messages
* Type: ChatFlow、Chat、Agent
*/
public static final DifyRoute CHAT_CHAT_MESSAGES = new DifyRoute("/chat-messages", HttpMethod.POST);
/**
* Desc: Audio to Text
* Type: ChatFlow、Chat、Agent
*/
public static final DifyRoute AUDIO_TO_TEXT = new DifyRoute("/audio-to-text", HttpMethod.POST);
/**
* Desc: run workflow
* Type: workflow
*/
public static final DifyRoute WORKFLOW_RUN = new DifyRoute("/workflows/run", HttpMethod.POST);
}
package io.github.yuanbaobaoo.dify.routes;
public enum HttpMethod {
POST,
GET,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
TRACE,
CONNECT
}
package io.github.yuanbaobaoo.dify.service;
import io.github.yuanbaobaoo.dify.types.KnowledgeResult;
import io.github.yuanbaobaoo.dify.types.KnowledgeArgs;
public interface IDifyKonwledgeService {
/**
* 知识检索
* @param apiKey Dify传递的API KEY
* @param args KownledgeArgs
*/
KnowledgeResult retrieval(String apiKey, KnowledgeArgs args);
}
package io.github.yuanbaobaoo.dify.types;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class DifyException extends RuntimeException {
// error message {code, message, params, status}
private String original;
// error code | http status
private Integer status;
/**
* constructor
*/
public DifyException() {
super();
}
/**
* constructor
* @param response String
* @param status http status
*/
public DifyException(String response, Integer status) {
this.status = status;
this.original = response;
}
@Override
public String getMessage() {
return original;
}
@Override
public String toString() {
return String.format("{original='%s', status=%s}", original, status);
}
}
package io.github.yuanbaobaoo.dify.types;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import io.github.yuanbaobaoo.dify.routes.HttpMethod;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DifyRoute {
/**
* API URL
*/
private String url;
/**
* HTTP METHOD
*/
private HttpMethod method;
/**
* build get route info
* @param url API URL
*/
public static DifyRoute buildGet(String url) {
return DifyRoute.builder().url(url).method(HttpMethod.GET).build();
}
/**
* build post route info
* @param url API URL
*/
public static DifyRoute buildPost(String url) {
return DifyRoute.builder().url(url).method(HttpMethod.POST).build();
}
/**
* build delete route info
* @param url API URL
*/
public static DifyRoute buildDelete(String url) {
return DifyRoute.builder().url(url).method(HttpMethod.DELETE).build();
}
}
package io.github.yuanbaobaoo.dify.types;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class KnowledgeArgs {
/**
* 知识库唯一 ID
*/
private String knowledge_id;
/**
* 用户的查询
*/
private String query;
/**
* 知识检索参数
*/
private Setting retrieval_setting;
@Setter
@Getter
public static class Setting {
/**
* 检索结果的最大数量
*/
private String top_k;
/**
* 结果与查询相关性的分数限制,范围:0~1
*/
private Double score_threshold;
}
}
package io.github.yuanbaobaoo.dify.types;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class KnowledgeException extends RuntimeException {
/**
* 错误代码
*/
private Integer error_code;
/**
* 错误信息
*/
private String error_message;
/**
* toObject
*/
public Object toObject() {
Map<String, Object> obj = new HashMap<>();
obj.put("error_code", error_code);
obj.put("error_message", error_message);
return obj;
}
@Override
public String toString() {
return "{" +
"error_code: " + error_code +
", error_message: '" + error_message + '\'' +
'}';
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment