前置:
1.部署Dify,见官方教程及介绍https://docs.dify.ai/zh-hans,本文主要讲基于部署完之后的java实现的调用它的接口实现AI智能聊天,其他AI功能后续有用到再补充,没有就看缘分
2.什么是Dify?可以简单理解为集成了各类AI大模型的一个中转平台,你的服务请求它的接口,再通过在Dify上配置好的应用尽情请求获取到回复内容
3.用到了Dify,但其他相关的提供大模型平台的用法可能都差不多,可做参考
4.用到springboot/java
AI机器人创建
1.先在Dify上创建一个聊天应用
选择聊天助手
创建完毕
获取请求地址跟API密钥,写入配置文件
开始开发
1.pom文件新增依赖
<dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>retrofit</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>converter-jackson</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>adapter-rxjava2</artifactId> <version>2.9.0</version> </dependency>
2.yml配置文件
dify:
#复制的apiKey,以下为假数据
apiKey: 'app-ugR39FF8DDDAOaY4yBZDq4ba'
#apiHost: 'http://localhost/v1/' 记得需要/结尾
apiHost: 'http://api-test.xxxxxx.cn/v1/'
流式请求与阻塞式请求
区别:简单来说流式就是一个字一个字给我们返回打印出来,代码实现相对复杂,阻塞式则是等机器人把问题想清楚了,全部内容返回给我们,先对来说实现比较容易,但是反应可能没那么快,具体需要看业务需要
流式请求实现
3.控制层
- import jakarta.annotation.Resource;
- import jakarta.servlet.http.HttpServletResponse;
- import jakarta.validation.Valid;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.http.MediaType;
- import org.springframework.web.bind.annotation.*;
- import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
-
-
- @RequiredArgsConstructor
- @RestController
- @Slf4j
- @RequestMapping("/ai")
- public class PsyAiChatController {
-
- //流式模式
- private final static String STREAMING_MODE = "streaming";
- //阻塞模式
- private final static String BLOCKING_MODE = "blocking";
-
- @Resource
- private IPsyAiChatService difyChatService;
-
- /**
- * AI陪聊
- * @param bo
- * @param response
- * @return
- */
- @PostMapping("/chat")
- @ResponseBody
- public ResponseBodyEmitter chatSeeMessage(@RequestBody @Valid AiChatBo bo, HttpServletResponse response) {
- response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
- DifyRequest difyRequest = new DifyRequest();
- difyRequest.setResponseMode(STREAMING_MODE);
- difyRequest.setQuery(bo.getQuery());
- difyRequest.setUser(bo.getUserId().toString());
- if(StringUtils.isNotEmpty(bo.getConversationId())){
- difyRequest.setConversationId(bo.getConversationId());
- }
- return difyChatService.sseChatPrompt(difyRequest);
- }
- }
4.前端请求实体
- @Data
- public class AiChatBo implements Serializable {
-
- /**
- * 用户id
- */
- private Long userId;
-
- /**
- * 聊天内容
- */
- private String query;
-
- /**
- * (选填)会话id,若基于之前的聊天记录继续对话,必传之前消息的 conversation_id
- */
- private String conversationId;
-
- }
5.请求Dify请求体
- package com.xmzs.common.edu.domain.dify.bo;
-
- import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
- import com.fasterxml.jackson.annotation.JsonProperty;
- import lombok.Data;
-
- import java.util.List;
- import java.util.Map;
-
- /**
- * dify聊天请求体
- * @Author: zyt
- * @CreateTime: 2024-11-05
- */
- @Data
- @JsonIgnoreProperties(ignoreUnknown = true)
- public class DifyRequest {
-
- /**
- * 输入提问内容
- */
- private String query;
-
- /**
- * (选填)允许传入 App 定义的各变量值
- */
- private Map<String,String> inputs;
-
- /**
- * 回复模式:streaming流式模式,blocking阻塞模式
- */
- @JsonProperty("response_mode")
- private String responseMode;
-
- /**
- * (选填)会话id,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id
- * */
- @JsonProperty("conversation_id")
- private String conversationId;
- /**
- * 用户标识,用于定义终端用户的身份,方便检索、统计。 由开发者定义规则,需保证用户标识在应用内唯一。
- * */
- private String user="";
-
- /**
- * (选填)自动生成标题,默认 false。 可通过调用会话重命名接口并设置 auto_generate 为 true 实现异步生成标题
- * */
- @JsonProperty("autoGenerate_name")
- private boolean autoGenerateName=false;
-
- private List<UploadFile> files;
- @Data
- public class UploadFile{
- /**
- * 支持类型:图片 image(目前仅支持图片格式)
- * */
- private String type="image";
- /**
- * remote_url: 图片地址
- * local_file: 上传文件
- * */
- @JsonProperty("transfer_method")
- private String transferMethod;
-
- /**
- *
- * */
- private String url;
- /**
- * 上传文件 ID。(仅当传递方式为 local_file 时)
- * */
- @JsonProperty("upload_file_id")
- private String uploadFileId;
- }
-
- }
-
6.service
接口
- import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
-
- public interface IPsyAiChatService {
-
- /**
- * AI陪聊
- * @param chatRequest
- * @return
- */
- ResponseBodyEmitter sseChatPrompt(DifyRequest chatRequest);
- }
实现类
- import cn.hutool.core.date.DateUnit;
- import jakarta.annotation.Resource;
- import jodd.cache.TimedCache;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
-
- /**
- * AI相关service
- * @CreateTime: 2024-11-05
- */
- @Service
- @Slf4j
- @RequiredArgsConstructor
- public class PsyAiChatServiceImpl implements IPsyAiChatService {
-
- /**
- * 设置sse链接时长缓存
- */
- public static final long TIMEOUT = 30 * DateUnit.MINUTE.getMillis();
-
- public static final TimedCache<String, Object> LOCAL_CACHE = new TimedCache<>(TIMEOUT);
-
- @Resource
- private DifyApiClient difyApiClient;
- /**
- * AI陪聊
- * @param chatRequest
- * @return
- */
- @Override
- public ResponseBodyEmitter sseChatPrompt(DifyRequest chatRequest) {
- ResponseBodyEmitter sseEmitter = this.getResponseBodyEmitter(chatRequest);
- DifySseEventSourceListener listener = new DifySseEventSourceListener(sseEmitter);
- difyApiClient.streamChatCompletion(chatRequest,listener);
- return sseEmitter;
- }
-
- /**
- * 创建sse连接
- * @param chatRequest
- * @return
- */
- private ResponseBodyEmitter getResponseBodyEmitter(DifyRequest chatRequest) {
- //0L设置允许超时
- ResponseBodyEmitter sseEmitter = new ResponseBodyEmitter(0L);
- sseEmitter.onCompletion(() -> {
- log.info("会话[{}]sse结束连接......", chatRequest.getConversationId());
- LOCAL_CACHE.remove(chatRequest.getConversationId());
- });
- //超时回调
- sseEmitter.onTimeout(() -> {
- log.error("会话[{}]sse连接超时......", chatRequest.getConversationId());
- });
- //异常回调
- sseEmitter.onError(
- throwable -> {
- log.error("会话[{}]sse连接失败......", chatRequest.getConversationId());
- }
- );
- LOCAL_CACHE.put(chatRequest.getConversationId(), sseEmitter);
- log.info("会话[{}]创建sse连接成功!", chatRequest.getConversationId());
- return sseEmitter;
- }
- }
事件监听器
用于流式模式,监听dify返回的多个结果,模拟ai问答一个字一个字回答打印出来的效果,如果是阻塞模式则需要dify处理完回复把完整结果返回,则用不到监听器
-
- import com.fasterxml.jackson.databind.ObjectMapper;
- import lombok.AllArgsConstructor;
- import lombok.SneakyThrows;
- import lombok.extern.slf4j.Slf4j;
- import okhttp3.Response;
- import okhttp3.ResponseBody;
- import okhttp3.sse.EventSource;
- import okhttp3.sse.EventSourceListener;
- import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
-
- import java.util.Objects;
-
- /**
- * dify sse事件监听器
- * @CreateTime: 2024-11-05
- */
- @Slf4j
- @AllArgsConstructor
- public class DifySseEventSourceListener extends EventSourceListener {
-
- private static final String DONE_SIGNAL = "[DONE]";
-
- private final ResponseBodyEmitter emitter;
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onOpen(EventSource eventSource, Response response) {
- log.info("Dify建立sse连接...");
- }
-
- /**
- * {@inheritDoc}
- */
- @SneakyThrows
- @Override
- public void onEvent(EventSource eventSource, String id, String type, String data) {
- log.debug("DifyEventSourceListener data : {}",data);
- if (data.equals(DONE_SIGNAL)) {
- // 成功响应
- emitter.complete();
- return;
- }
- ObjectMapper mapper = new ObjectMapper();
- ChunkChatCompletionResponse completionResponse = mapper.readValue(data, ChunkChatCompletionResponse.class);
- if(completionResponse == null){
- return;
- }
- String content = completionResponse.getAnswer();
-
- if(StringUtils.isEmpty(content)){
- return;
- }
- try {
- emitter.send(content);
- } catch (Exception e) {
- log.error("sse信息推送失败!",e);
- eventSource.cancel();
- }
- }
-
- @Override
- public void onClosed(EventSource eventSource) {
- log.info("Dify关闭sse连接...");
- }
-
- @SneakyThrows
- @Override
- public void onFailure(EventSource eventSource, Throwable t, Response response) {
- if (Objects.isNull(response)) {
- return;
- }
- ResponseBody body = response.body();
- if (Objects.nonNull(body)) {
- log.error("Dify sse连接异常data:{},异常:{}", body.string(), t);
- } else {
- log.error("Dify sse连接异常data:{},异常:{}", response, t);
- }
- eventSource.cancel();
- }
- }
其他实体
- @Data
- public class Usage {
- @JsonProperty("prompt_tokens")
- private int promptTokens;
- @JsonProperty("prompt_unit_price")
- private double promptNnitPrice;
- @JsonProperty("prompt_price_unit")
- private double promptPriceUnit;
- @JsonProperty("prompt_price")
- private double promptPrice;
- @JsonProperty("completion_tokens")
- private int completionTokens;
- @JsonProperty("completion_unit_price")
- private double completionUnitPrice;//": "0.002",
- @JsonProperty("completion_price_unit")
- private double completionPriceUnit;//": "0.001",
- @JsonProperty("completion_price")
- private double completionPrice;//": "0.0002560",
- @JsonProperty("total_tokens")
- private int totalTokens;//": 1161,
- @JsonProperty("total_price")
- private double totalPrice;//": "0.0012890",
- @JsonProperty("currency")
- private String currency= "USD";
- @JsonProperty("latency")
- private double latency;
-
- }
-
-
-
- @Data
- public class RetrieverResources {
-
- //": 1
- @JsonProperty("position")
- private int position;
-
- //": "101b4c97-fc2e-463c-90b1-5261a4cdcafb",
- @JsonProperty("dataset_id")
- private String datasetId;
-
- //": "iPhone",
- @JsonProperty("dataset_name")
- private String datasetName;
-
- //": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00",
- @JsonProperty("document_id")
- private String documentId;
-
- //": "iPhone List",
- @JsonProperty("document_name")
- private String documentName;
-
- //": "ed599c7f-2766-4294-9d1d-e5235a61270a",
- @JsonProperty("segment_id")
- private String segmentId;
-
- //": 0.98457545,
- @JsonProperty("score")
- private String score;
-
- //": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""
- @JsonProperty("content")
- private String content;
- }
-
-
-
- @Data
- public class Metadata {
- @JsonProperty("usage")
- private Usage usage;
- @JsonProperty("retriever_resources")
- private List<RetrieverResources> retrieverResources;
- }
-
-
- /**
- * dify聊天请求体
- * @CreateTime: 2024-11-05
- */
- @Data
- @JsonIgnoreProperties(ignoreUnknown = true)
- public class DifyResponse {
-
- /**
- * 消息id
- */
- @JsonProperty("message_id")
- private String messageId;
-
- /**
- * 事件
- */
- private String event;
-
- /**
- * 会话id
- */
- @JsonProperty("conversation_id")
- private String conversationId;
-
- /**
- * 创建时间戳
- */
- @JsonProperty("created_at")
- private int createdAt;
- }
-
- /**
- *
- *流式消息时返回对象
- */
- @Data
- @JsonIgnoreProperties(ignoreUnknown = true)
- public class ChunkChatCompletionResponse extends DifyResponse{
- //每一轮Agent迭代都会有一个唯一的id
- private String id;
-
- //任务 ID,用于请求跟踪和下方的停止响应接口
- @JsonProperty("task_id")
- private String taskId;
-
- //LLM 返回文本块内容
- private String answer;
-
- //agent_thought在消息中的位置,如第一轮迭代position为1
- private int position;
-
- //agent的思考内容
- private String thought;
-
- //工具调用的返回结果
- private String observation;
-
- //使用的工具列表,以 ; 分割多个工具
- private String tool;
-
- //工具的输入,JSON格式的字符串(object)。如:{"dalle3": {"prompt": "a cute cat"}}
- @JsonProperty("tool_input")
- private String toolInput;
-
- //当前 agent_thought 关联的文件ID
- @JsonProperty("message_files")
- private List<String> messageFiles;
-
- //event: message_file时,文件类型,目前仅为image
- private String type;
-
- // (string) 文件归属,user或assistant,该接口返回仅为 assistant
- @JsonProperty("belongs_to")
- private String belongsTo;
-
- //文件访问地址
- private String url;
-
- //元数据
- private Metadata metadata;
-
- //HTTP 状态码
- private int status;
-
- //错误码
- private String code;
-
- //错误消息
- private String message;
-
- }
-
- /**
- * 阻塞式消息时返回对象
- *
- */
- @Data
- @JsonIgnoreProperties(ignoreUnknown = true)
- public class ChatCompletionResponse extends DifyResponse{
- /**App 模式,固定为 chat*/
- private String mode="chat";
- /**完整回复内容*/
- /** LLM 返回文本全部内容*/
- private String answer;
- @JsonProperty("task_id")
- private String taskId;
- /**元数据*/
- private Metadata metadata;
- }
Dify客户端请求类及配置文件
-
- import cn.hutool.core.collection.CollectionUtil;
- import cn.hutool.core.util.StrUtil;
- import cn.hutool.http.ContentType;
- import cn.hutool.http.Header;
- import com.fasterxml.jackson.databind.ObjectMapper;
- import jakarta.annotation.Resource;
- import lombok.Getter;
- import lombok.extern.slf4j.Slf4j;
- import okhttp3.MediaType;
- import okhttp3.OkHttpClient;
- import okhttp3.Request;
- import okhttp3.RequestBody;
- import okhttp3.sse.EventSource;
- import okhttp3.sse.EventSourceListener;
- import okhttp3.sse.EventSources;
- import org.jetbrains.annotations.NotNull;
- import retrofit2.Retrofit;
- import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
- import retrofit2.converter.jackson.JacksonConverterFactory;
-
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Objects;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @Author: linjinde
- * @CreateTime: 2024-11-05
- */
- @Slf4j
- public class DifyApiClient {
-
- //自定义api host使用builder的方式构造client
- @Getter
- private String apiHost;
- @Getter
- private List<String> apiKey;
- @Getter
- private DifyApi difyApi;
-
- // 自定义okHttpClient,非自定义为sdk默认OkHttpClient实例
- @Getter
- private OkHttpClient okHttpClient;
-
- // api key的获取策略
- @Getter
- private KeyStrategyFunction<List<String>, String> keyStrategy;
-
- /**
- * 构造器
- * @return OpenAiClient.Builder
- */
- public static Builder builder() {
- return new Builder();
- }
-
- /**
- * 构造
- * @param builder
- */
- private DifyApiClient(Builder builder) {
- if (StrUtil.isBlank(builder.apiHost)) {
- builder.apiHost = DifyConst.OPENAI_HOST;
- }
- apiHost = builder.apiHost;
- apiKey = builder.apiKey;
- if (Objects.isNull(builder.okHttpClient)) {
- builder.okHttpClient = this.okHttpClient();
- } else {
- //自定义的okhttpClient 需要增加api keys
- builder.okHttpClient = builder.okHttpClient
- .newBuilder()
- .build();
- }
- okHttpClient = builder.okHttpClient;
- this.difyApi = new Retrofit.Builder()
- .baseUrl(apiHost)
- .client(okHttpClient)
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .addConverterFactory(JacksonConverterFactory.create())
- .build().create(DifyApi.class);
- }
-
- /**
- * 创建默认OkHttpClient
- * @return
- */
- private OkHttpClient okHttpClient() {
- return new OkHttpClient
- .Builder()
- .connectTimeout(30, TimeUnit.SECONDS)
- .writeTimeout(30, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS).build();
- }
-
- public static final class Builder {
- //api keys
- private @NotNull List<String> apiKey;
- //api请求地址,结尾处有斜杠
- private String apiHost;
- //自定义OkhttpClient
- private OkHttpClient okHttpClient;
- // api key的获取策略
- private KeyStrategyFunction keyStrategy;
-
- public Builder() {
- }
-
- /**
- * @param val api请求地址,结尾处有斜杠
- * @return Builder对象
- */
- public Builder apiHost(String val) {
- apiHost = val;
- return this;
- }
-
- public Builder apiKey(@NotNull List<String> val) {
- apiKey = val;
- return this;
- }
-
- public Builder keyStrategy(KeyStrategyFunction val) {
- keyStrategy = val;
- return this;
- }
-
- public Builder okHttpClient(OkHttpClient val) {
- okHttpClient = val;
- return this;
- }
- public DifyApiClient build() {
- return new DifyApiClient(this);
- }
- }
-
-
- /**
- * 流式输出
- * @param difyRequest
- * @param eventSourceListener
- * @param <T>
- */
- public <T extends DifyResponse> void streamChatCompletion(DifyRequest difyRequest, EventSourceListener eventSourceListener) {
- if (Objects.isNull(eventSourceListener)) {
- log.info("EventSourceListener为空");
- throw new EduException("300001");
- }
- try {
- if(CollectionUtil.isNotEmpty(difyRequest.getInputs())){
- difyRequest.setInputs(new HashMap<>());
- }
- if(CollectionUtil.isNotEmpty(difyRequest.getFiles())){
- difyRequest.setFiles(new ArrayList<>());
- }
- //构建请求参数json数据
- ObjectMapper mapper = new ObjectMapper();
- String requestBody = mapper.writeValueAsString(difyRequest);
- log.debug("请求参数:{}",requestBody);
- //创建事件工厂
- EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
- Request request = new Request.Builder()
- .url(this.apiHost + "chat-messages")
- .addHeader(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey.get(0))
- .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
- .build();
- factory.newEventSource(request, eventSourceListener);
- } catch (Exception e) {
- log.error("请求参数解析异常:{}", e.getMessage());
- }
- }
-
- /**
- * 阻塞式问答
- * @param difyRequest chat completion
- * @return 返回答案
- */
- public ChatCompletionResponse chatMessages(@Body DifyRequest difyRequest, String serverKey){
- if(difyRequest.getInputs()==null){
- difyRequest.setInputs(new HashMap<>());
- }
- if(difyRequest.getFiles() ==null){
- difyRequest.setFiles(new ArrayList<>());
- }
- log.debug(JsonUtils.toJsonString(difyRequest));
-
- // 序列化请求体
- ObjectMapper mapper = new ObjectMapper();
- String requestBodyJson = "";
- try {
- requestBodyJson = mapper.writeValueAsString(difyRequest);
- } catch (Exception e) {
- log.error("请求体序列化失败:{}", e.getMessage());
- throw new EduException("300001");
- }
-
- // 创建请求体
- RequestBody requestBody = RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBodyJson);
-
- // 创建请求对象,这里动态地将API Key设置到请求头中
- Request request = new Request.Builder()
- .url(this.apiHost + "chat-messages") // 此处路径根据实际需要进行调整
- .addHeader("Authorization", "Bearer " + serverKey) // 设置动态API Key
- .post(requestBody)
- .build();
-
- ChatCompletionResponse response;
- try {
- // 执行同步请求并获取响应
- okhttp3.Response okHttpResponse = okHttpClient.newCall(request).execute();
- if (!okHttpResponse.isSuccessful() || okHttpResponse.body() == null) {
- log.error("请求失败:HTTP {},message: {}", okHttpResponse.code(),okHttpResponse.message());
- throw new BaseException("请求失败:HTTP " + okHttpResponse.code()+" "+okHttpResponse.message());
- }
-
- // 反序列化响应体
- String responseBody = okHttpResponse.body().string();
- response = mapper.readValue(responseBody, ChatCompletionResponse.class);
- } catch (Exception e) {
- log.error("请求异常:{}", e.getMessage());
- throw new EduException("300001");
- }
-
- // 返回结果
- return response;
- }
-
- }
- import io.reactivex.Single;
- import retrofit2.http.Body;
- import retrofit2.http.POST;
-
- /**
- * @Author: zyt
- * @CreateTime: 2024-11-05
- */
- public interface DifyApi {
- /**
- * 发送对话消息
- *
- * @param difyRequest chat completion
- * @return 返回答案
- */
- @POST("chat-messages")
- Single<ChatCompletionResponse> chatMessages(@Body DifyRequest difyRequest);
-
- }
- import com.xmzs.edu.client.dify.DifyApiClient;
- import okhttp3.OkHttpClient;
- import okhttp3.logging.HttpLoggingInterceptor;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- import java.util.List;
- import java.util.concurrent.TimeUnit;
-
- /**
- * dify配置类
- * @Author: linjinde
- * @CreateTime: 2024-11-05
- */
- @Configuration
- public class DifyConfig {
-
- @Value("${dify.apiKey}")
- private List<String> apiKey;
-
- @Value("${dify.apiHost}")
- private String apiHost;
-
- // @Bean(name = "openAiStreamClient")
- // public DifyStreamClient DifyStreamClient() {
- //
- // OkHttpClient okHttpClient;
- // HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
- // httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
- // okHttpClient = new OkHttpClient
- // .Builder()
- // .addInterceptor(httpLoggingInterceptor)
- // .connectTimeout(30, TimeUnit.SECONDS)
- // .writeTimeout(600, TimeUnit.SECONDS)
- // .readTimeout(600, TimeUnit.SECONDS)
- // .build();
- //
- // return OpenAiStreamClient
- // .builder()
- // .apiHost(apiHost)
- // .apiKey(apiKey)
- // //自定义key使用策略 默认随机策略
- // .keyStrategy(new KeyRandomStrategy())
- // .okHttpClient(okHttpClient)
- // .build();
- // }
-
- @Bean(name = "difyApiClient")
- public DifyApiClient difyApiClient() {
-
- OkHttpClient okHttpClient;
- HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new DifyLogger());
- httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
- okHttpClient = new OkHttpClient
- .Builder()
- .addInterceptor(httpLoggingInterceptor)
- .connectTimeout(30, TimeUnit.SECONDS)
- .writeTimeout(600, TimeUnit.SECONDS)
- .readTimeout(600, TimeUnit.SECONDS)
- .build();
-
- return DifyApiClient
- .builder()
- .apiHost(apiHost)
- .apiKey(apiKey)
- //自定义key使用策略 默认随机策略
- .keyStrategy(new KeyRandomStrategy())
- .okHttpClient(okHttpClient)
- .build();
- }
- }
-
-
- public class DifyConst {
-
- public final static String OPENAI_HOST = "https://api.openai.com/";
-
- public final static int SUCCEED_CODE = 200;
-
- /** DALL3绘图费用 */
- public final static double DALL3_COST = 0.5;
-
- public final static double GPT3_IN = 0.01;
-
- public final static double GPT3_OUT = 0.02;
-
- /**
- * 以毫厘为单位,0.1元=1000
- * 毫厘(0.0001)
- * **/
- public final static long GPT4_IN = 1000;
-
- /**
- * 以毫厘为单位,0.3元=3000
- * 毫厘(0.0001)
- * **/
- public final static long GPT4_OUT = 3000;
-
- /** 默认账户余额 */
- public final static double USER_BALANCE = 5;
-
- }
-
-
- /**
- * * @Author: linjinde
- * * @CreateTime: 2024-11-05
- * dify请求日志打印
- */
- @Slf4j
- public class DifyLogger implements HttpLoggingInterceptor.Logger {
-
- @Override
- public void log(String message) {
- log.info("Dify数据请求中:{}", message);
- }
- }
-
-
- /**
- * 随机策略
- * @Author: linjinde
- * @CreateTime: 2024-11-05
- */
- public class KeyRandomStrategy implements KeyStrategyFunction<List<String>, String> {
-
- @Override
- public String apply(List<String> apiKeys) {
- return RandomUtil.randomEle(apiKeys);
- }
- }
-
-
- @FunctionalInterface
- public interface KeyStrategyFunction<T, R> {
-
- /**
- * Applies this function to the given argument.
- *
- * @param t the function argument
- * @return the function result
- */
- R apply(T t);
-
- }
测试
阻塞式请求实现
比较简单,直接调用方法chatMessages,详细如上,下面只是部分代码
- * 阻塞式问答
- * @param difyRequest chat completion
- * @return 返回答案
- */
- public ChatCompletionResponse chatMessages(@Body DifyRequest difyRequest, String serverKey){
- if(difyRequest.getInputs()==null){
- difyRequest.setInputs(new HashMap<>());
- }
- if(difyRequest.getFiles() ==null){
- difyRequest.setFiles(new ArrayList<>());
- }
- log.debug(JsonUtils.toJsonString(difyRequest));
-
- // 序列化请求体
- ObjectMapper mapper = new ObjectMapper();
- String requestBodyJson = "";
- try {
- requestBodyJson = mapper.writeValueAsString(difyRequest);
- } catch (Exception e) {
- log.error("请求体序列化失败:{}", e.getMessage());
- throw new EduException("300001");
- }
-
- // 创建请求体
- RequestBody requestBody = RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBodyJson);
-
- // 创建请求对象,这里动态地将API Key设置到请求头中
- Request request = new Request.Builder()
- .url(this.apiHost + "chat-messages") // 此处路径根据实际需要进行调整
- .addHeader("Authorization", "Bearer " + serverKey) // 设置动态API Key
- .post(requestBody)
- .build();
-
- ChatCompletionResponse response;
- try {
- // 执行同步请求并获取响应
- okhttp3.Response okHttpResponse = okHttpClient.newCall(request).execute();
- if (!okHttpResponse.isSuccessful() || okHttpResponse.body() == null) {
- log.error("请求失败:HTTP {},message: {}", okHttpResponse.code(),okHttpResponse.message());
- throw new BaseException("请求失败:HTTP " + okHttpResponse.code()+" "+okHttpResponse.message());
- }
-
- // 反序列化响应体
- String responseBody = okHttpResponse.body().string();
- response = mapper.readValue(responseBody, ChatCompletionResponse.class);
- } catch (Exception e) {
- log.error("请求异常:{}", e.getMessage());
- throw new EduException("300001");
- }
-
- // 返回结果
- return response;
- }
- .....
over 后续有再补充