开发笔记 | 快速上手基于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.控制层

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
 * @Author: zyt
 * @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事件监听器
 * @Author: zyt
 * @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聊天请求体
 * @Author: zyt
 * @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);

}

测试

 

over 后续有再补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/910764.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据分析:转录组差异fgsea富集分析

文章目录 介绍加载R包数据链接导入数据数据预处理DE testing: 2BP vs no-BP比较limma-voomLoad steroid dataIn No-BP patientsIn 2BP patientsCompare gene expression vs bacterial mass其他系统信息介绍 转录组差异fgsea富集分析是一种基于基因集的富集分析方法,它关注的是…

查看网路信息-ifconfig命令

1.ifconfig缺点&#xff1a; 可以查看接口的网络类型&#xff1b;部分IP和掩码以及状态是否插线&#xff0c;看不到接口下的网关&#xff0c;DNS, 要想看到接口下多个IP,使用 ip addr show 命令 要想看网关&#xff0c;使用 ip route show 命令、route -n 命令 显示路由表内…

基于Spring Boot的中小型制造企业质量管理系统设计与实现,LW+源码+讲解

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自…

Java:多态的调用

1.什么是多态 允许不同类的对象对同一消息做不同的响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。&#xff08;发送消息就是函数调用&#xff09;。多态使用了一种动态绑定&#xff08;dynamic binding&#xff09;技术&#xff0c;指在执行期间判断所引用…

并查集算法详解

文章目录 并查集概念并查集的常见操作构建并查集合并并查集和查找 关于find函数 并查集概念 并查集&#xff08;Union-Find&#xff09;是一种树型的数据结构&#xff0c;用于处理一些不交集的合并及查询问题。其主要应用是判断两个元素是否在同一个集合中&#xff0c;以及合并…

Redis持久化机制——针对实习面试

目录 Redis持久化机制Redis为什么要有持久化机制&#xff1f;Redis持久化方式有哪些&#xff1f;AOF持久化工作原理是什么&#xff1f;有什么优缺点&#xff1f;AOF持久化工作原理AOF的优点AOF的缺点 RDB持久化工作原理是什么&#xff1f;有什么优缺点&#xff1f;RDB持久化工作…

【系统架构设计师(第2版)】七、系统架构设计基础知识

有效的软件体系结构及其明确的描述和设计&#xff0c;已成为软件工程领域中重要的主题。 *注&#xff1a;由于历史原因&#xff0c;研究者和工程人员对**Software Architecture&#xff08;简称SA&#xff09;*的翻译不尽相同&#xff0c;本文中软件“体系结构”和“架构”具有…

【NLP】使用 SpaCy、ollama 创建用于命名实体识别的合成数据集

命名实体识别 (NER) 是自然语言处理 (NLP) 中的一项重要任务&#xff0c;用于自动识别和分类文本中的实体&#xff0c;例如人物、位置、组织等。尽管它很重要&#xff0c;但手动注释大型数据集以进行 NER 既耗时又费钱。受本文 ( https://huggingface.co/blog/synthetic-data-s…

Google推出了AI驱动的学习工具“Learn About”

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Vue3中使用LogicFlow实现简单流程图

实现结果 实现功能&#xff1a; 拖拽创建节点自定义节点/边自定义快捷键人员选择弹窗右侧动态配置组件配置项获取/回显必填项验证 自定义节点与拖拽创建节点 拖拽节点面板node-panel.vue <template><div class"node-panel"><divv-for"(item, k…

本地部署运行 HuggingFace Diffuser 大模型

最近需要篡改大模型验证篡改定位水印的泛化性&#xff0c;但是由于网络连接原因无法直接使用&#x1f917;s Diffusers library &#xff0c;在网上找到了以下本地部署的方法。 目录 下载模型&#xff0c;部署至服务器上 1&#xff09;huggingface官网下载 2&#xff09;gi…

Bert框架详解(下)

一、Bert模型网络结构 1、Add与Normalize Add&#xff1a;将前面的数据传到后面层&#xff0c;残差网络同理。 Normalize &#xff1a;归一化&#xff0c;与batch normalize同理。 2、outputs(shifted right) outputs&#xff08;shifted right&#xff09;&#xff1a;指在…

操作系统学习笔记-3.2虚拟内存

文章目录 虚拟内存请求分页管理方式页面置换算法最佳置换算法工作原理OPT 算法的示例最佳置换算法的优点和缺点 先进先出置换算法最近最久未使用时钟置换算法时钟置换算法的工作原理&#xff1a;算法的步骤&#xff1a; 改进型时钟置换算法改进型时钟置换算法的特点&#xff1a…

【数学】通用三阶矩阵特征向量的快速求法 超简单!!!

目录 三个定理1、3个特征值&#xff08;即根互不相等&#xff09;例题实践2、2个特征值&#xff08;即有一个双重根&#xff09;3、1个特征值&#xff08;即有一个三重根&#xff09;定理证明 三个定理 本定理适用于 所有三阶矩阵 的特征向量求法&#xff01; 1、3个特征值&…

16通道AD采集方案,基于复旦微ARM + FPGA国产SoC处理器平台

测试数据汇总 表 1 本文带来的是基于复旦微FMQL20S400M四核ARM Cortex-A7(PS端) + FPGA可编程逻辑资源(PL端)异构多核SoC处理器设计的全国产工业评估板的AD采集案例。本次案例演示的开发环境如下: Windows开发环境:Windows 7 64bit、Windows 10 64bit PL端开发环境:P…

文件系统和日志管理

文件系统和日志管理 文件系统&#xff1a;文件系统提供了一个接口&#xff0c;用户用来访问硬件设备&#xff08;硬盘、光驱&#xff09;------------- 在硬件设备上对文件的管理 1、文件存储在硬盘上&#xff08;机械硬盘&#xff1a;一个扇区 2、文件中硬盘上的最小存储单位…

数据结构---排序总结

1.排序的时间复杂度&#xff08;均为平均值&#xff09; O(n^2) &#xff1a;冒泡排序&#xff0c;选择排序&#xff0c;插入排序。 O(n * log(n))&#xff1a;堆排序&#xff0c;快速排序&#xff0c;归并排序。 O(n^1.3)&#xff1a;希尔排序 2.空间复杂度&#xff1a; O(n) …

数据结构:七种排序及总结

文章目录 排序一插入排序1直接插入排序2希尔排序二选择排序3直接选择排序4堆排序三 交换排序5冒泡排序6快速排序四 归并排序7归并排序源码 排序 我们数据结构常见的排序有四大种&#xff0c;四大种又分为七小种&#xff0c;如图所示 排序&#xff1a;所谓排序&#xff0c;就是…

【操作系统】基于环形队列的生产消费模型

目录 一、单生产单消费 1.环形队列的实现 (1) void P(sem_t &sem); (2) void V(sem_t &sem); (3) RingQueue() (4) ~RingQueue() (5) void Push(const T &in); (6) void Pop(T *out); 2.上层调用逻辑 二、多生产多消费 1.环形队列的实现 (1) RingQueue…

Linux下的WatchDog

看门狗&#x1f415; 看门狗简介 看门狗定时器&#xff08;Watchdog Timer&#xff09;是一种定时器&#xff0c;用于检测系统是否正常运行。如果系统在规定时间内没有向看门狗定时器发送复位信号&#xff0c;看门狗定时器就会产生复位信号&#xff0c;使系统复位。看门狗定时…