开发笔记 | 快速上手基于Dify等第三方大模型平台接口实现AI智能聊天

前置:

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.控制层

  1. import jakarta.annotation.Resource;
  2. import jakarta.servlet.http.HttpServletResponse;
  3. import jakarta.validation.Valid;
  4. import lombok.RequiredArgsConstructor;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.web.bind.annotation.*;
  8. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
  9. @RequiredArgsConstructor
  10. @RestController
  11. @Slf4j
  12. @RequestMapping("/ai")
  13. public class PsyAiChatController {
  14. //流式模式
  15. private final static String STREAMING_MODE = "streaming";
  16. //阻塞模式
  17. private final static String BLOCKING_MODE = "blocking";
  18. @Resource
  19. private IPsyAiChatService difyChatService;
  20. /**
  21. * AI陪聊
  22. * @param bo
  23. * @param response
  24. * @return
  25. */
  26. @PostMapping("/chat")
  27. @ResponseBody
  28. public ResponseBodyEmitter chatSeeMessage(@RequestBody @Valid AiChatBo bo, HttpServletResponse response) {
  29. response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
  30. DifyRequest difyRequest = new DifyRequest();
  31. difyRequest.setResponseMode(STREAMING_MODE);
  32. difyRequest.setQuery(bo.getQuery());
  33. difyRequest.setUser(bo.getUserId().toString());
  34. if(StringUtils.isNotEmpty(bo.getConversationId())){
  35. difyRequest.setConversationId(bo.getConversationId());
  36. }
  37. return difyChatService.sseChatPrompt(difyRequest);
  38. }
  39. }

4.前端请求实体

  1. @Data
  2. public class AiChatBo implements Serializable {
  3. /**
  4. * 用户id
  5. */
  6. private Long userId;
  7. /**
  8. * 聊天内容
  9. */
  10. private String query;
  11. /**
  12. * (选填)会话id,若基于之前的聊天记录继续对话,必传之前消息的 conversation_id
  13. */
  14. private String conversationId;
  15. }

5.请求Dify请求体

  1. package com.xmzs.common.edu.domain.dify.bo;
  2. import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  3. import com.fasterxml.jackson.annotation.JsonProperty;
  4. import lombok.Data;
  5. import java.util.List;
  6. import java.util.Map;
  7. /**
  8. * dify聊天请求体
  9. * @Author: zyt
  10. * @CreateTime: 2024-11-05
  11. */
  12. @Data
  13. @JsonIgnoreProperties(ignoreUnknown = true)
  14. public class DifyRequest {
  15. /**
  16. * 输入提问内容
  17. */
  18. private String query;
  19. /**
  20. * (选填)允许传入 App 定义的各变量值
  21. */
  22. private Map<String,String> inputs;
  23. /**
  24. * 回复模式:streaming流式模式,blocking阻塞模式
  25. */
  26. @JsonProperty("response_mode")
  27. private String responseMode;
  28. /**
  29. * (选填)会话id,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id
  30. * */
  31. @JsonProperty("conversation_id")
  32. private String conversationId;
  33. /**
  34. * 用户标识,用于定义终端用户的身份,方便检索、统计。 由开发者定义规则,需保证用户标识在应用内唯一。
  35. * */
  36. private String user="";
  37. /**
  38. * (选填)自动生成标题,默认 false。 可通过调用会话重命名接口并设置 auto_generate 为 true 实现异步生成标题
  39. * */
  40. @JsonProperty("autoGenerate_name")
  41. private boolean autoGenerateName=false;
  42. private List<UploadFile> files;
  43. @Data
  44. public class UploadFile{
  45. /**
  46. * 支持类型:图片 image(目前仅支持图片格式)
  47. * */
  48. private String type="image";
  49. /**
  50. * remote_url: 图片地址
  51. * local_file: 上传文件
  52. * */
  53. @JsonProperty("transfer_method")
  54. private String transferMethod;
  55. /**
  56. *
  57. * */
  58. private String url;
  59. /**
  60. * 上传文件 ID。(仅当传递方式为 local_file 时)
  61. * */
  62. @JsonProperty("upload_file_id")
  63. private String uploadFileId;
  64. }
  65. }

6.service

接口

  1. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
  2. public interface IPsyAiChatService {
  3. /**
  4. * AI陪聊
  5. * @param chatRequest
  6. * @return
  7. */
  8. ResponseBodyEmitter sseChatPrompt(DifyRequest chatRequest);
  9. }

实现类

  1. import cn.hutool.core.date.DateUnit;
  2. import jakarta.annotation.Resource;
  3. import jodd.cache.TimedCache;
  4. import lombok.RequiredArgsConstructor;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.stereotype.Service;
  7. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
  8. /**
  9. * AI相关service
  10. * @CreateTime: 2024-11-05
  11. */
  12. @Service
  13. @Slf4j
  14. @RequiredArgsConstructor
  15. public class PsyAiChatServiceImpl implements IPsyAiChatService {
  16. /**
  17. * 设置sse链接时长缓存
  18. */
  19. public static final long TIMEOUT = 30 * DateUnit.MINUTE.getMillis();
  20. public static final TimedCache<String, Object> LOCAL_CACHE = new TimedCache<>(TIMEOUT);
  21. @Resource
  22. private DifyApiClient difyApiClient;
  23. /**
  24. * AI陪聊
  25. * @param chatRequest
  26. * @return
  27. */
  28. @Override
  29. public ResponseBodyEmitter sseChatPrompt(DifyRequest chatRequest) {
  30. ResponseBodyEmitter sseEmitter = this.getResponseBodyEmitter(chatRequest);
  31. DifySseEventSourceListener listener = new DifySseEventSourceListener(sseEmitter);
  32. difyApiClient.streamChatCompletion(chatRequest,listener);
  33. return sseEmitter;
  34. }
  35. /**
  36. * 创建sse连接
  37. * @param chatRequest
  38. * @return
  39. */
  40. private ResponseBodyEmitter getResponseBodyEmitter(DifyRequest chatRequest) {
  41. //0L设置允许超时
  42. ResponseBodyEmitter sseEmitter = new ResponseBodyEmitter(0L);
  43. sseEmitter.onCompletion(() -> {
  44. log.info("会话[{}]sse结束连接......", chatRequest.getConversationId());
  45. LOCAL_CACHE.remove(chatRequest.getConversationId());
  46. });
  47. //超时回调
  48. sseEmitter.onTimeout(() -> {
  49. log.error("会话[{}]sse连接超时......", chatRequest.getConversationId());
  50. });
  51. //异常回调
  52. sseEmitter.onError(
  53. throwable -> {
  54. log.error("会话[{}]sse连接失败......", chatRequest.getConversationId());
  55. }
  56. );
  57. LOCAL_CACHE.put(chatRequest.getConversationId(), sseEmitter);
  58. log.info("会话[{}]创建sse连接成功!", chatRequest.getConversationId());
  59. return sseEmitter;
  60. }
  61. }

事件监听器

用于流式模式,监听dify返回的多个结果,模拟ai问答一个字一个字回答打印出来的效果,如果是阻塞模式则需要dify处理完回复把完整结果返回,则用不到监听器

  1. import com.fasterxml.jackson.databind.ObjectMapper;
  2. import lombok.AllArgsConstructor;
  3. import lombok.SneakyThrows;
  4. import lombok.extern.slf4j.Slf4j;
  5. import okhttp3.Response;
  6. import okhttp3.ResponseBody;
  7. import okhttp3.sse.EventSource;
  8. import okhttp3.sse.EventSourceListener;
  9. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
  10. import java.util.Objects;
  11. /**
  12. * dify sse事件监听器
  13. * @CreateTime: 2024-11-05
  14. */
  15. @Slf4j
  16. @AllArgsConstructor
  17. public class DifySseEventSourceListener extends EventSourceListener {
  18. private static final String DONE_SIGNAL = "[DONE]";
  19. private final ResponseBodyEmitter emitter;
  20. /**
  21. * {@inheritDoc}
  22. */
  23. @Override
  24. public void onOpen(EventSource eventSource, Response response) {
  25. log.info("Dify建立sse连接...");
  26. }
  27. /**
  28. * {@inheritDoc}
  29. */
  30. @SneakyThrows
  31. @Override
  32. public void onEvent(EventSource eventSource, String id, String type, String data) {
  33. log.debug("DifyEventSourceListener data : {}",data);
  34. if (data.equals(DONE_SIGNAL)) {
  35. // 成功响应
  36. emitter.complete();
  37. return;
  38. }
  39. ObjectMapper mapper = new ObjectMapper();
  40. ChunkChatCompletionResponse completionResponse = mapper.readValue(data, ChunkChatCompletionResponse.class);
  41. if(completionResponse == null){
  42. return;
  43. }
  44. String content = completionResponse.getAnswer();
  45. if(StringUtils.isEmpty(content)){
  46. return;
  47. }
  48. try {
  49. emitter.send(content);
  50. } catch (Exception e) {
  51. log.error("sse信息推送失败!",e);
  52. eventSource.cancel();
  53. }
  54. }
  55. @Override
  56. public void onClosed(EventSource eventSource) {
  57. log.info("Dify关闭sse连接...");
  58. }
  59. @SneakyThrows
  60. @Override
  61. public void onFailure(EventSource eventSource, Throwable t, Response response) {
  62. if (Objects.isNull(response)) {
  63. return;
  64. }
  65. ResponseBody body = response.body();
  66. if (Objects.nonNull(body)) {
  67. log.error("Dify sse连接异常data:{},异常:{}", body.string(), t);
  68. } else {
  69. log.error("Dify sse连接异常data:{},异常:{}", response, t);
  70. }
  71. eventSource.cancel();
  72. }
  73. }

其他实体

  1. @Data
  2. public class Usage {
  3. @JsonProperty("prompt_tokens")
  4. private int promptTokens;
  5. @JsonProperty("prompt_unit_price")
  6. private double promptNnitPrice;
  7. @JsonProperty("prompt_price_unit")
  8. private double promptPriceUnit;
  9. @JsonProperty("prompt_price")
  10. private double promptPrice;
  11. @JsonProperty("completion_tokens")
  12. private int completionTokens;
  13. @JsonProperty("completion_unit_price")
  14. private double completionUnitPrice;//": "0.002",
  15. @JsonProperty("completion_price_unit")
  16. private double completionPriceUnit;//": "0.001",
  17. @JsonProperty("completion_price")
  18. private double completionPrice;//": "0.0002560",
  19. @JsonProperty("total_tokens")
  20. private int totalTokens;//": 1161,
  21. @JsonProperty("total_price")
  22. private double totalPrice;//": "0.0012890",
  23. @JsonProperty("currency")
  24. private String currency= "USD";
  25. @JsonProperty("latency")
  26. private double latency;
  27. }
  28. @Data
  29. public class RetrieverResources {
  30. //": 1
  31. @JsonProperty("position")
  32. private int position;
  33. //": "101b4c97-fc2e-463c-90b1-5261a4cdcafb",
  34. @JsonProperty("dataset_id")
  35. private String datasetId;
  36. //": "iPhone",
  37. @JsonProperty("dataset_name")
  38. private String datasetName;
  39. //": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00",
  40. @JsonProperty("document_id")
  41. private String documentId;
  42. //": "iPhone List",
  43. @JsonProperty("document_name")
  44. private String documentName;
  45. //": "ed599c7f-2766-4294-9d1d-e5235a61270a",
  46. @JsonProperty("segment_id")
  47. private String segmentId;
  48. //": 0.98457545,
  49. @JsonProperty("score")
  50. private String score;
  51. //": "\"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\""
  52. @JsonProperty("content")
  53. private String content;
  54. }
  55. @Data
  56. public class Metadata {
  57. @JsonProperty("usage")
  58. private Usage usage;
  59. @JsonProperty("retriever_resources")
  60. private List<RetrieverResources> retrieverResources;
  61. }
  62. /**
  63. * dify聊天请求体
  64. * @CreateTime: 2024-11-05
  65. */
  66. @Data
  67. @JsonIgnoreProperties(ignoreUnknown = true)
  68. public class DifyResponse {
  69. /**
  70. * 消息id
  71. */
  72. @JsonProperty("message_id")
  73. private String messageId;
  74. /**
  75. * 事件
  76. */
  77. private String event;
  78. /**
  79. * 会话id
  80. */
  81. @JsonProperty("conversation_id")
  82. private String conversationId;
  83. /**
  84. * 创建时间戳
  85. */
  86. @JsonProperty("created_at")
  87. private int createdAt;
  88. }
  89. /**
  90. *
  91. *流式消息时返回对象
  92. */
  93. @Data
  94. @JsonIgnoreProperties(ignoreUnknown = true)
  95. public class ChunkChatCompletionResponse extends DifyResponse{
  96. //每一轮Agent迭代都会有一个唯一的id
  97. private String id;
  98. //任务 ID,用于请求跟踪和下方的停止响应接口
  99. @JsonProperty("task_id")
  100. private String taskId;
  101. //LLM 返回文本块内容
  102. private String answer;
  103. //agent_thought在消息中的位置,如第一轮迭代position为1
  104. private int position;
  105. //agent的思考内容
  106. private String thought;
  107. //工具调用的返回结果
  108. private String observation;
  109. //使用的工具列表,以 ; 分割多个工具
  110. private String tool;
  111. //工具的输入,JSON格式的字符串(object)。如:{"dalle3": {"prompt": "a cute cat"}}
  112. @JsonProperty("tool_input")
  113. private String toolInput;
  114. //当前 agent_thought 关联的文件ID
  115. @JsonProperty("message_files")
  116. private List<String> messageFiles;
  117. //event: message_file时,文件类型,目前仅为image
  118. private String type;
  119. // (string) 文件归属,user或assistant,该接口返回仅为 assistant
  120. @JsonProperty("belongs_to")
  121. private String belongsTo;
  122. //文件访问地址
  123. private String url;
  124. //元数据
  125. private Metadata metadata;
  126. //HTTP 状态码
  127. private int status;
  128. //错误码
  129. private String code;
  130. //错误消息
  131. private String message;
  132. }
  133. /**
  134. * 阻塞式消息时返回对象
  135. *
  136. */
  137. @Data
  138. @JsonIgnoreProperties(ignoreUnknown = true)
  139. public class ChatCompletionResponse extends DifyResponse{
  140. /**App 模式,固定为 chat*/
  141. private String mode="chat";
  142. /**完整回复内容*/
  143. /** LLM 返回文本全部内容*/
  144. private String answer;
  145. @JsonProperty("task_id")
  146. private String taskId;
  147. /**元数据*/
  148. private Metadata metadata;
  149. }

Dify客户端请求类及配置文件

  1. import cn.hutool.core.collection.CollectionUtil;
  2. import cn.hutool.core.util.StrUtil;
  3. import cn.hutool.http.ContentType;
  4. import cn.hutool.http.Header;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import jakarta.annotation.Resource;
  7. import lombok.Getter;
  8. import lombok.extern.slf4j.Slf4j;
  9. import okhttp3.MediaType;
  10. import okhttp3.OkHttpClient;
  11. import okhttp3.Request;
  12. import okhttp3.RequestBody;
  13. import okhttp3.sse.EventSource;
  14. import okhttp3.sse.EventSourceListener;
  15. import okhttp3.sse.EventSources;
  16. import org.jetbrains.annotations.NotNull;
  17. import retrofit2.Retrofit;
  18. import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
  19. import retrofit2.converter.jackson.JacksonConverterFactory;
  20. import java.util.ArrayList;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Objects;
  24. import java.util.concurrent.TimeUnit;
  25. /**
  26. * @Author: linjinde
  27. * @CreateTime: 2024-11-05
  28. */
  29. @Slf4j
  30. public class DifyApiClient {
  31. //自定义api host使用builder的方式构造client
  32. @Getter
  33. private String apiHost;
  34. @Getter
  35. private List<String> apiKey;
  36. @Getter
  37. private DifyApi difyApi;
  38. // 自定义okHttpClient,非自定义为sdk默认OkHttpClient实例
  39. @Getter
  40. private OkHttpClient okHttpClient;
  41. // api key的获取策略
  42. @Getter
  43. private KeyStrategyFunction<List<String>, String> keyStrategy;
  44. /**
  45. * 构造器
  46. * @return OpenAiClient.Builder
  47. */
  48. public static Builder builder() {
  49. return new Builder();
  50. }
  51. /**
  52. * 构造
  53. * @param builder
  54. */
  55. private DifyApiClient(Builder builder) {
  56. if (StrUtil.isBlank(builder.apiHost)) {
  57. builder.apiHost = DifyConst.OPENAI_HOST;
  58. }
  59. apiHost = builder.apiHost;
  60. apiKey = builder.apiKey;
  61. if (Objects.isNull(builder.okHttpClient)) {
  62. builder.okHttpClient = this.okHttpClient();
  63. } else {
  64. //自定义的okhttpClient 需要增加api keys
  65. builder.okHttpClient = builder.okHttpClient
  66. .newBuilder()
  67. .build();
  68. }
  69. okHttpClient = builder.okHttpClient;
  70. this.difyApi = new Retrofit.Builder()
  71. .baseUrl(apiHost)
  72. .client(okHttpClient)
  73. .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
  74. .addConverterFactory(JacksonConverterFactory.create())
  75. .build().create(DifyApi.class);
  76. }
  77. /**
  78. * 创建默认OkHttpClient
  79. * @return
  80. */
  81. private OkHttpClient okHttpClient() {
  82. return new OkHttpClient
  83. .Builder()
  84. .connectTimeout(30, TimeUnit.SECONDS)
  85. .writeTimeout(30, TimeUnit.SECONDS)
  86. .readTimeout(30, TimeUnit.SECONDS).build();
  87. }
  88. public static final class Builder {
  89. //api keys
  90. private @NotNull List<String> apiKey;
  91. //api请求地址,结尾处有斜杠
  92. private String apiHost;
  93. //自定义OkhttpClient
  94. private OkHttpClient okHttpClient;
  95. // api key的获取策略
  96. private KeyStrategyFunction keyStrategy;
  97. public Builder() {
  98. }
  99. /**
  100. * @param val api请求地址,结尾处有斜杠
  101. * @return Builder对象
  102. */
  103. public Builder apiHost(String val) {
  104. apiHost = val;
  105. return this;
  106. }
  107. public Builder apiKey(@NotNull List<String> val) {
  108. apiKey = val;
  109. return this;
  110. }
  111. public Builder keyStrategy(KeyStrategyFunction val) {
  112. keyStrategy = val;
  113. return this;
  114. }
  115. public Builder okHttpClient(OkHttpClient val) {
  116. okHttpClient = val;
  117. return this;
  118. }
  119. public DifyApiClient build() {
  120. return new DifyApiClient(this);
  121. }
  122. }
  123. /**
  124. * 流式输出
  125. * @param difyRequest
  126. * @param eventSourceListener
  127. * @param <T>
  128. */
  129. public <T extends DifyResponse> void streamChatCompletion(DifyRequest difyRequest, EventSourceListener eventSourceListener) {
  130. if (Objects.isNull(eventSourceListener)) {
  131. log.info("EventSourceListener为空");
  132. throw new EduException("300001");
  133. }
  134. try {
  135. if(CollectionUtil.isNotEmpty(difyRequest.getInputs())){
  136. difyRequest.setInputs(new HashMap<>());
  137. }
  138. if(CollectionUtil.isNotEmpty(difyRequest.getFiles())){
  139. difyRequest.setFiles(new ArrayList<>());
  140. }
  141. //构建请求参数json数据
  142. ObjectMapper mapper = new ObjectMapper();
  143. String requestBody = mapper.writeValueAsString(difyRequest);
  144. log.debug("请求参数:{}",requestBody);
  145. //创建事件工厂
  146. EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
  147. Request request = new Request.Builder()
  148. .url(this.apiHost + "chat-messages")
  149. .addHeader(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey.get(0))
  150. .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
  151. .build();
  152. factory.newEventSource(request, eventSourceListener);
  153. } catch (Exception e) {
  154. log.error("请求参数解析异常:{}", e.getMessage());
  155. }
  156. }
  157. /**
  158. * 阻塞式问答
  159. * @param difyRequest chat completion
  160. * @return 返回答案
  161. */
  162. public ChatCompletionResponse chatMessages(@Body DifyRequest difyRequest, String serverKey){
  163. if(difyRequest.getInputs()==null){
  164. difyRequest.setInputs(new HashMap<>());
  165. }
  166. if(difyRequest.getFiles() ==null){
  167. difyRequest.setFiles(new ArrayList<>());
  168. }
  169. log.debug(JsonUtils.toJsonString(difyRequest));
  170. // 序列化请求体
  171. ObjectMapper mapper = new ObjectMapper();
  172. String requestBodyJson = "";
  173. try {
  174. requestBodyJson = mapper.writeValueAsString(difyRequest);
  175. } catch (Exception e) {
  176. log.error("请求体序列化失败:{}", e.getMessage());
  177. throw new EduException("300001");
  178. }
  179. // 创建请求体
  180. RequestBody requestBody = RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBodyJson);
  181. // 创建请求对象,这里动态地将API Key设置到请求头中
  182. Request request = new Request.Builder()
  183. .url(this.apiHost + "chat-messages") // 此处路径根据实际需要进行调整
  184. .addHeader("Authorization", "Bearer " + serverKey) // 设置动态API Key
  185. .post(requestBody)
  186. .build();
  187. ChatCompletionResponse response;
  188. try {
  189. // 执行同步请求并获取响应
  190. okhttp3.Response okHttpResponse = okHttpClient.newCall(request).execute();
  191. if (!okHttpResponse.isSuccessful() || okHttpResponse.body() == null) {
  192. log.error("请求失败:HTTP {},message: {}", okHttpResponse.code(),okHttpResponse.message());
  193. throw new BaseException("请求失败:HTTP " + okHttpResponse.code()+" "+okHttpResponse.message());
  194. }
  195. // 反序列化响应体
  196. String responseBody = okHttpResponse.body().string();
  197. response = mapper.readValue(responseBody, ChatCompletionResponse.class);
  198. } catch (Exception e) {
  199. log.error("请求异常:{}", e.getMessage());
  200. throw new EduException("300001");
  201. }
  202. // 返回结果
  203. return response;
  204. }
  205. }
  1. import io.reactivex.Single;
  2. import retrofit2.http.Body;
  3. import retrofit2.http.POST;
  4. /**
  5. * @Author: zyt
  6. * @CreateTime: 2024-11-05
  7. */
  8. public interface DifyApi {
  9. /**
  10. * 发送对话消息
  11. *
  12. * @param difyRequest chat completion
  13. * @return 返回答案
  14. */
  15. @POST("chat-messages")
  16. Single<ChatCompletionResponse> chatMessages(@Body DifyRequest difyRequest);
  17. }

  1. import com.xmzs.edu.client.dify.DifyApiClient;
  2. import okhttp3.OkHttpClient;
  3. import okhttp3.logging.HttpLoggingInterceptor;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import java.util.List;
  8. import java.util.concurrent.TimeUnit;
  9. /**
  10. * dify配置类
  11. * @Author: linjinde
  12. * @CreateTime: 2024-11-05
  13. */
  14. @Configuration
  15. public class DifyConfig {
  16. @Value("${dify.apiKey}")
  17. private List<String> apiKey;
  18. @Value("${dify.apiHost}")
  19. private String apiHost;
  20. // @Bean(name = "openAiStreamClient")
  21. // public DifyStreamClient DifyStreamClient() {
  22. //
  23. // OkHttpClient okHttpClient;
  24. // HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
  25. // httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
  26. // okHttpClient = new OkHttpClient
  27. // .Builder()
  28. // .addInterceptor(httpLoggingInterceptor)
  29. // .connectTimeout(30, TimeUnit.SECONDS)
  30. // .writeTimeout(600, TimeUnit.SECONDS)
  31. // .readTimeout(600, TimeUnit.SECONDS)
  32. // .build();
  33. //
  34. // return OpenAiStreamClient
  35. // .builder()
  36. // .apiHost(apiHost)
  37. // .apiKey(apiKey)
  38. // //自定义key使用策略 默认随机策略
  39. // .keyStrategy(new KeyRandomStrategy())
  40. // .okHttpClient(okHttpClient)
  41. // .build();
  42. // }
  43. @Bean(name = "difyApiClient")
  44. public DifyApiClient difyApiClient() {
  45. OkHttpClient okHttpClient;
  46. HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new DifyLogger());
  47. httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
  48. okHttpClient = new OkHttpClient
  49. .Builder()
  50. .addInterceptor(httpLoggingInterceptor)
  51. .connectTimeout(30, TimeUnit.SECONDS)
  52. .writeTimeout(600, TimeUnit.SECONDS)
  53. .readTimeout(600, TimeUnit.SECONDS)
  54. .build();
  55. return DifyApiClient
  56. .builder()
  57. .apiHost(apiHost)
  58. .apiKey(apiKey)
  59. //自定义key使用策略 默认随机策略
  60. .keyStrategy(new KeyRandomStrategy())
  61. .okHttpClient(okHttpClient)
  62. .build();
  63. }
  64. }
  65. public class DifyConst {
  66. public final static String OPENAI_HOST = "https://api.openai.com/";
  67. public final static int SUCCEED_CODE = 200;
  68. /** DALL3绘图费用 */
  69. public final static double DALL3_COST = 0.5;
  70. public final static double GPT3_IN = 0.01;
  71. public final static double GPT3_OUT = 0.02;
  72. /**
  73. * 以毫厘为单位,0.1元=1000
  74. * 毫厘(0.0001)
  75. * **/
  76. public final static long GPT4_IN = 1000;
  77. /**
  78. * 以毫厘为单位,0.3元=3000
  79. * 毫厘(0.0001)
  80. * **/
  81. public final static long GPT4_OUT = 3000;
  82. /** 默认账户余额 */
  83. public final static double USER_BALANCE = 5;
  84. }
  85. /**
  86. * * @Author: linjinde
  87. * * @CreateTime: 2024-11-05
  88. * dify请求日志打印
  89. */
  90. @Slf4j
  91. public class DifyLogger implements HttpLoggingInterceptor.Logger {
  92. @Override
  93. public void log(String message) {
  94. log.info("Dify数据请求中:{}", message);
  95. }
  96. }
  97. /**
  98. * 随机策略
  99. * @Author: linjinde
  100. * @CreateTime: 2024-11-05
  101. */
  102. public class KeyRandomStrategy implements KeyStrategyFunction<List<String>, String> {
  103. @Override
  104. public String apply(List<String> apiKeys) {
  105. return RandomUtil.randomEle(apiKeys);
  106. }
  107. }
  108. @FunctionalInterface
  109. public interface KeyStrategyFunction<T, R> {
  110. /**
  111. * Applies this function to the given argument.
  112. *
  113. * @param t the function argument
  114. * @return the function result
  115. */
  116. R apply(T t);
  117. }

测试

 

阻塞式请求实现

 比较简单,直接调用方法chatMessages,详细如上,下面只是部分代码

  1. * 阻塞式问答
  2. * @param difyRequest chat completion
  3. * @return 返回答案
  4. */
  5. public ChatCompletionResponse chatMessages(@Body DifyRequest difyRequest, String serverKey){
  6. if(difyRequest.getInputs()==null){
  7. difyRequest.setInputs(new HashMap<>());
  8. }
  9. if(difyRequest.getFiles() ==null){
  10. difyRequest.setFiles(new ArrayList<>());
  11. }
  12. log.debug(JsonUtils.toJsonString(difyRequest));
  13. // 序列化请求体
  14. ObjectMapper mapper = new ObjectMapper();
  15. String requestBodyJson = "";
  16. try {
  17. requestBodyJson = mapper.writeValueAsString(difyRequest);
  18. } catch (Exception e) {
  19. log.error("请求体序列化失败:{}", e.getMessage());
  20. throw new EduException("300001");
  21. }
  22. // 创建请求体
  23. RequestBody requestBody = RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBodyJson);
  24. // 创建请求对象,这里动态地将API Key设置到请求头中
  25. Request request = new Request.Builder()
  26. .url(this.apiHost + "chat-messages") // 此处路径根据实际需要进行调整
  27. .addHeader("Authorization", "Bearer " + serverKey) // 设置动态API Key
  28. .post(requestBody)
  29. .build();
  30. ChatCompletionResponse response;
  31. try {
  32. // 执行同步请求并获取响应
  33. okhttp3.Response okHttpResponse = okHttpClient.newCall(request).execute();
  34. if (!okHttpResponse.isSuccessful() || okHttpResponse.body() == null) {
  35. log.error("请求失败:HTTP {},message: {}", okHttpResponse.code(),okHttpResponse.message());
  36. throw new BaseException("请求失败:HTTP " + okHttpResponse.code()+" "+okHttpResponse.message());
  37. }
  38. // 反序列化响应体
  39. String responseBody = okHttpResponse.body().string();
  40. response = mapper.readValue(responseBody, ChatCompletionResponse.class);
  41. } catch (Exception e) {
  42. log.error("请求异常:{}", e.getMessage());
  43. throw new EduException("300001");
  44. }
  45. // 返回结果
  46. return response;
  47. }
  48. .....

over 后续有再补充