From 6a1b544545ba2a005a1d6263f3b42aaeeef78bcd Mon Sep 17 00:00:00 2001
From: ageerle <ageerle@163.com>
Date: 星期二, 11 三月 2025 17:32:47 +0800
Subject: [PATCH] feat: 支持插件功能

---
 ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserPasswordBo.java                   |   32 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdResp.java                       |   12 
 ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java                                      |   14 
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeAttachServiceImpl.java |    8 
 ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/plugin/WebSearchPlugin.java                        |  212 ++++
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeInfoService.java           |    4 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdReq.java                        |   13 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlPlugin.java                     |   88 +
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlResp.java                       |   12 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java    |   73 +
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java           |    1 
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/domain/KnowledgeInfo.java                    |    2 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java      |    2 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java                   |   24 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlReq.java                        |   13 
 ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java        |   12 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java                      |   13 
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/EmbeddingServiceImpl.java       |    3 
 script/sql/update/update20250307.sql                                                                         |   12 
 ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java                   |  172 +-
 ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java               |    7 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginAbstract.java         |   88 +
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeInfoServiceImpl.java   |   54 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/AllToolsTest.java              |  122 ++
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java                     |   15 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java            |  187 +++
 ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java               |    2 
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStore.java           |   13 
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreWrapper.java    |    9 
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeAttachService.java         |    4 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java                   |  126 ++
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreFactory.java    |   25 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java    |   92 +
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java         |    5 
 ruoyi-common/ruoyi-common-chat/pom.xml                                                                       |   26 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/V4Test.java                    |  646 ++++++++++++
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginParam.java            |    7 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java                     |   36 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java                      |  417 ++++++++
 ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/MilvusVectorStore.java     |    4 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java                  |   91 +
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java       |   56 +
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/WebSearchToolsTest.java        |  246 ++++
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/DefaultPluginListener.java            |   22 
 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java                  |   63 
 ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java                  |    7 
 ruoyi-modules/ruoyi-knowledge/pom.xml                                                                        |    3 
 47 files changed, 2,865 insertions(+), 230 deletions(-)

diff --git a/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java b/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java
index 1768a69..6f9f06b 100644
--- a/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java
+++ b/ruoyi-admin/src/main/java/org/ruoyi/controller/KnowledgeController.java
@@ -73,11 +73,9 @@
      */
     @PostMapping("/send")
     public SseEmitter send(@RequestBody @Valid ChatRequest chatRequest) {
-
         openAiStreamClient = chatConfig.getOpenAiStreamClient();
         SseEmitter sseEmitter = new SseEmitter(0L);
         SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
-
         List<Message> messages = chatRequest.getMessages();
         String content = messages.get(messages.size() - 1).getContent().toString();
         List<String> nearestList;
@@ -89,8 +87,6 @@
         }
         Message userMessage = Message.builder().content(content + (nearestList.size() > 0 ? "\n\n娉ㄦ剰锛氬洖绛旈棶棰樻椂锛岄』涓ユ牸鏍规嵁鎴戠粰浣犵殑绯荤粺涓婁笅鏂囧唴瀹瑰師鏂囪繘琛屽洖绛旓紝璇蜂笉瑕佽嚜宸卞彂鎸�,鍥炵瓟鏃朵繚鎸佸師鏉ユ枃鏈殑娈佃惤灞傜骇" : "") ).role(Message.Role.USER).build();
         messages.add(userMessage);
-
-
         ChatCompletion completion = ChatCompletion
             .builder()
             .messages(messages)
@@ -104,7 +100,6 @@
         return sseEmitter;
     }
 
-
     /**
      * 鏍规嵁鐢ㄦ埛淇℃伅鏌ヨ鏈湴鐭ヨ瘑搴�
      */
@@ -116,8 +111,6 @@
         bo.setUid(LoginHelper.getUserId());
         return knowledgeInfoService.queryPageList(bo, pageQuery);
     }
-
-
 
     /**
      * 鏂板鐭ヨ瘑搴�
@@ -190,10 +183,9 @@
      * 鍒犻櫎鐭ヨ瘑搴撻檮浠�
      *
      */
-    @PostMapping("attach/remove/{kid}")
-    public R<Void> removeAttach(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖")
-                          @PathVariable String kid) {
-        attachService.removeKnowledgeAttach(kid);
+    @PostMapping("attach/remove/{docId}")
+    public R<Void> removeAttach(@NotEmpty(message = "涓婚敭涓嶈兘涓虹┖") @PathVariable String docId) {
+        attachService.removeKnowledgeAttach(docId);
         return R.ok();
     }
 
diff --git a/ruoyi-common/ruoyi-common-chat/pom.xml b/ruoyi-common/ruoyi-common-chat/pom.xml
index 8b5ac81..aba4a0f 100644
--- a/ruoyi-common/ruoyi-common-chat/pom.xml
+++ b/ruoyi-common/ruoyi-common-chat/pom.xml
@@ -27,6 +27,12 @@
         </dependency>
 
         <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.33</version>
+        </dependency>
+
+        <dependency>
             <groupId>com.azure</groupId>
             <artifactId>azure-ai-openai</artifactId>
             <version>1.0.0-beta.12</version>
@@ -92,5 +98,25 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.bigmodel.openapi</groupId>
+            <artifactId>oapi-java-sdk</artifactId>
+            <version>release-V4-2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>2.7.5</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java
new file mode 100644
index 0000000..21bb3d5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java
@@ -0,0 +1,73 @@
+package org.ruoyi.common.chat.demo;
+
+import cn.hutool.json.JSONUtil;
+
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okhttp3.sse.EventSource;
+import okhttp3.sse.EventSourceListener;
+import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
+
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * 鎻忚堪锛� sse
+ *
+ * @author https:www.unfbx.com
+ * 2023-06-15
+ */
+@Slf4j
+public class ConsoleEventSourceListenerV2 extends EventSourceListener {
+    @Getter
+    String args = "";
+    final CountDownLatch countDownLatch;
+
+    public ConsoleEventSourceListenerV2(CountDownLatch countDownLatch) {
+        this.countDownLatch = countDownLatch;
+    }
+
+    @Override
+    public void onOpen(EventSource eventSource, Response response) {
+        log.info("OpenAI寤虹珛sse杩炴帴...");
+    }
+
+    @Override
+    public void onEvent(EventSource eventSource, String id, String type, String data) {
+        log.info("OpenAI杩斿洖鏁版嵁锛歿}", data);
+        if (data.equals("[DONE]")) {
+            log.info("OpenAI杩斿洖鏁版嵁缁撴潫浜�");
+            countDownLatch.countDown();
+            return;
+        }
+        ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
+        if(Objects.nonNull(chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall())){
+            args += chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall().getArguments();
+        }
+    }
+
+    @Override
+    public void onClosed(EventSource eventSource) {
+        log.info("OpenAI鍏抽棴sse杩炴帴...");
+    }
+
+    @SneakyThrows
+    @Override
+    public void onFailure(EventSource eventSource, Throwable t, Response response) {
+        if(Objects.isNull(response)){
+            log.error("OpenAI  sse杩炴帴寮傚父:{}", t);
+            eventSource.cancel();
+            return;
+        }
+        ResponseBody body = response.body();
+        if (Objects.nonNull(body)) {
+            log.error("OpenAI  sse杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", body.string(), t);
+        } else {
+            log.error("OpenAI  sse杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", response, t);
+        }
+        eventSource.cancel();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java
new file mode 100644
index 0000000..22661ef
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java
@@ -0,0 +1,92 @@
+package org.ruoyi.common.chat.demo;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.json.JSONUtil;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okhttp3.sse.EventSource;
+import okhttp3.sse.EventSourceListener;
+import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
+import org.ruoyi.common.chat.entity.chat.Message;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * 鎻忚堪锛� demo娴嬭瘯瀹炵幇绫伙紝浠呬緵鎬濊矾鍙傝��
+ *
+ * @author https:www.unfbx.com
+ * 2023-11-12
+ */
+@Slf4j
+public class ConsoleEventSourceListenerV3 extends EventSourceListener {
+    @Getter
+    List<ToolCalls> choices = new ArrayList<>();
+    @Getter
+    ToolCalls toolCalls = new ToolCalls();
+    @Getter
+    ToolCallFunction toolCallFunction = ToolCallFunction.builder().name("").arguments("").build();
+    final CountDownLatch countDownLatch;
+
+    public ConsoleEventSourceListenerV3(CountDownLatch countDownLatch) {
+        this.countDownLatch = countDownLatch;
+    }
+
+    @Override
+    public void onOpen(EventSource eventSource, Response response) {
+        log.info("OpenAI寤虹珛sse杩炴帴...");
+    }
+
+    @Override
+    public void onEvent(EventSource eventSource, String id, String type, String data) {
+        log.info("OpenAI杩斿洖鏁版嵁锛歿}", data);
+        if (data.equals("[DONE]")) {
+            log.info("OpenAI杩斿洖鏁版嵁缁撴潫浜�");
+            return;
+        }
+        ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
+        Message delta = chatCompletionResponse.getChoices().get(0).getDelta();
+        if (CollectionUtil.isNotEmpty(delta.getToolCalls())) {
+            choices.addAll(delta.getToolCalls());
+        }
+    }
+
+    @Override
+    public void onClosed(EventSource eventSource) {
+        if(CollectionUtil.isNotEmpty(choices)){
+            toolCalls.setId(choices.get(0).getId());
+            toolCalls.setType(choices.get(0).getType());
+            choices.forEach(e -> {
+                toolCallFunction.setName(e.getFunction().getName());
+                toolCallFunction.setArguments(toolCallFunction.getArguments() + e.getFunction().getArguments());
+                toolCalls.setFunction(toolCallFunction);
+            });
+        }
+        log.info("OpenAI鍏抽棴sse杩炴帴...");
+        countDownLatch.countDown();
+    }
+
+    @SneakyThrows
+    @Override
+    public void onFailure(EventSource eventSource, Throwable t, Response response) {
+        if(Objects.isNull(response)){
+            log.error("OpenAI  sse杩炴帴寮傚父:{}", t);
+            eventSource.cancel();
+            return;
+        }
+        ResponseBody body = response.body();
+        if (Objects.nonNull(body)) {
+            log.error("OpenAI  sse杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", body.string(), t);
+        } else {
+            log.error("OpenAI  sse杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", response, t);
+        }
+        eventSource.cancel();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java
new file mode 100644
index 0000000..eeda6d4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java
@@ -0,0 +1,417 @@
+package org.ruoyi.common.chat.demo;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.junit.Before;
+import org.junit.Test;
+import org.ruoyi.common.chat.entity.chat.*;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
+import org.ruoyi.common.chat.entity.chat.tool.Tools;
+import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
+import org.ruoyi.common.chat.openai.OpenAiClient;
+import org.ruoyi.common.chat.openai.OpenAiStreamClient;
+import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
+import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
+import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
+import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor;
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+import org.ruoyi.common.chat.plugin.CmdPlugin;
+import org.ruoyi.common.chat.plugin.CmdReq;
+import org.ruoyi.common.chat.sse.ConsoleEventSourceListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鎻忚堪锛�
+ *
+ * @author ageerle@163.com
+ * date 2025/3/8
+ */
+@Slf4j
+public class PluginTest {
+
+    private OpenAiClient openAiClient;
+    private OpenAiStreamClient openAiStreamClient;
+
+    @Before
+    public void before() {
+        //鍙互涓簄ull
+//        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
+        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
+        //锛侊紒锛侊紒鍗冧竾鍒啀鐢熶骇鎴栬�呮祴璇曠幆澧冩墦寮�BODY绾у埆鏃ュ織锛侊紒锛侊紒
+        //锛侊紒锛佺敓浜ф垨鑰呮祴璇曠幆澧冨缓璁缃负杩欎笁绉嶇骇鍒細NONE,BASIC,HEADERS,锛侊紒锛�
+        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
+        OkHttpClient okHttpClient = new OkHttpClient
+                .Builder()
+//                .proxy(proxy)
+                .addInterceptor(httpLoggingInterceptor)
+                .addInterceptor(new OpenAiResponseInterceptor())
+                .connectTimeout(10, TimeUnit.SECONDS)
+                .writeTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(30, TimeUnit.SECONDS)
+                .build();
+        openAiClient = OpenAiClient.builder()
+                //鏀寔澶歬ey浼犲叆锛岃姹傛椂鍊欓殢鏈洪�夋嫨
+                .apiKey(Arrays.asList("sk-xx"))
+                //鑷畾涔塳ey鐨勮幏鍙栫瓥鐣ワ細榛樿KeyRandomStrategy
+                //.keyStrategy(new KeyRandomStrategy())
+                .keyStrategy(new KeyRandomStrategy())
+                .okHttpClient(okHttpClient)
+                //鑷繁鍋氫簡浠g悊灏变紶浠g悊鍦板潃锛屾病鏈夊彲涓嶄笉浼�,(鍏虫敞鍏紬鍙峰洖澶嶏細openai 锛岃幏鍙栧厤璐圭殑娴嬭瘯浠g悊鍦板潃)
+                .apiHost("https://api.pandarobot.chat/")
+                .build();
+
+        openAiStreamClient = OpenAiStreamClient.builder()
+                //鏀寔澶歬ey浼犲叆锛岃姹傛椂鍊欓殢鏈洪�夋嫨
+                .apiKey(Arrays.asList("sk-xx"))
+                //鑷畾涔塳ey鐨勮幏鍙栫瓥鐣ワ細榛樿KeyRandomStrategy
+                .keyStrategy(new KeyRandomStrategy())
+                .authInterceptor(new DynamicKeyOpenAiAuthInterceptor())
+                .okHttpClient(okHttpClient)
+                //鑷繁鍋氫簡浠g悊灏变紶浠g悊鍦板潃锛屾病鏈夊彲涓嶄笉浼�,(鍏虫敞鍏紬鍙峰洖澶嶏細openai 锛岃幏鍙栧厤璐圭殑娴嬭瘯浠g悊鍦板潃)
+                .apiHost("https://api.pandarobot.chat/")
+                .build();
+    }
+
+
+    @Test
+    public void chatFunction() {
+        //妯″瀷锛欸PT_3_5_TURBO_16K_0613
+        Message message = Message.builder().role(Message.Role.USER).content("缁欐垜杈撳嚭涓�涓暱搴︿负2鐨勪腑鏂囪瘝璇紝骞惰В閲婁笅璇嶈瀵瑰簲鐗╁搧鐨勭敤閫�").build();
+        //灞炴�т竴
+        JSONObject wordLength = new JSONObject();
+        wordLength.put("type", "number");
+        wordLength.put("description", "璇嶈鐨勯暱搴�");
+        //灞炴�т簩
+        JSONObject language = new JSONObject();
+        language.put("type", "string");
+        language.put("enum", Arrays.asList("zh", "en"));
+        language.put("description", "璇█绫诲瀷锛屼緥濡傦細zh浠h〃涓枃銆乪n浠h〃鑻辫");
+        //鍙傛暟
+        JSONObject properties = new JSONObject();
+        properties.put("wordLength", wordLength);
+        properties.put("language", language);
+
+        Parameters parameters = Parameters.builder()
+                .type("object")
+                .properties(properties)
+                .required(Collections.singletonList("wordLength")).build();
+        Functions functions = Functions.builder()
+                .name("getOneWord")
+                .description("鑾峰彇涓�涓寚瀹氶暱搴﹀拰璇█绫诲瀷鐨勮瘝璇�")
+                .parameters(parameters)
+                .build();
+
+        ChatCompletion chatCompletion = ChatCompletion
+                .builder()
+                .messages(Collections.singletonList(message))
+                .functions(Collections.singletonList(functions))
+                .functionCall("auto")
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+
+        ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
+        log.info("鏋勯�犵殑鏂规硶鍊硷細{}", chatChoice.getMessage().getFunctionCall());
+        log.info("鏋勯�犵殑鏂规硶鍚嶇О锛歿}", chatChoice.getMessage().getFunctionCall().getName());
+        log.info("鏋勯�犵殑鏂规硶鍙傛暟锛歿}", chatChoice.getMessage().getFunctionCall().getArguments());
+        WordParam wordParam = JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), WordParam.class);
+        String oneWord = getOneWord(wordParam);
+
+        FunctionCall functionCall = FunctionCall.builder()
+                .arguments(chatChoice.getMessage().getFunctionCall().getArguments())
+                .name("getOneWord")
+                .build();
+        Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("鏂规硶鍙傛暟").functionCall(functionCall).build();
+        String content
+                = "{ " +
+                "\"wordLength\": \"3\", " +
+                "\"language\": \"zh\", " +
+                "\"word\": \"" + oneWord + "\"," +
+                "\"鐢ㄩ�擻": [\"鐩存帴鍚僜", \"鍋氭矙鎷塡", \"鍞崠\"]" +
+                "}";
+        Message message3 = Message.builder().role(Message.Role.FUNCTION).name("getOneWord").content(content).build();
+        List<Message> messageList = Arrays.asList(message, message2, message3);
+        ChatCompletion chatCompletionV2 = ChatCompletion
+                .builder()
+                .messages(messageList)
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
+        log.info("鑷畾涔夌殑鏂规硶杩斿洖鍊硷細{}",chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
+    }
+
+
+    @Test
+    public void plugin() {
+        CmdPlugin plugin = new CmdPlugin(CmdReq.class);
+        // 鎻掍欢鍚嶇О
+        plugin.setName("鍛戒护琛屽伐鍏�");
+        // 鏂规硶鍚嶇О
+        plugin.setFunction("openCmd");
+        // 鏂规硶璇存槑
+        plugin.setDescription("鎻愪緵涓�涓懡浠よ鎸囦护,姣斿<璁颁簨鏈�>,鎸囦护浣跨敤涓枃,浠unction杩斿洖缁撴灉涓哄噯");
+
+        PluginAbstract.Arg arg = new PluginAbstract.Arg();
+        // 鍙傛暟鍚嶇О
+        arg.setName("cmd");
+        // 鍙傛暟璇存槑
+        arg.setDescription("鍛戒护琛屾寚浠�");
+        // 鍙傛暟绫诲瀷
+        arg.setType("string");
+        arg.setRequired(true);
+        plugin.setArgs(Collections.singletonList(arg));
+
+        Message message2 = Message.builder().role(Message.Role.USER).content("甯垜鎵撳紑璁$畻鍣�,缁撳悎涓婁笅鏂囧垽鏂寚浠ゆ槸鍚︽墽琛屾垚鍔�,鍙敤鍥炲鎴愬姛鎴栬�呭け璐�").build();
+        List<Message> messages = new ArrayList<>();
+        messages.add(message2);
+        //鏈夊洓涓噸杞芥柟娉曪紝閮藉彲浠ヤ娇鐢�
+        ChatCompletionResponse response = openAiClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
+        log.info("鑷畾涔夌殑鏂规硶杩斿洖鍊硷細{}", response.getChoices().get(0).getMessage().getContent());
+    }
+
+    /**
+     * 鑷畾涔夎繑鍥炴暟鎹牸寮�
+     */
+    @Test
+    public void diyReturnDataModelChat() {
+        Message message = Message.builder().role(Message.Role.USER).content("闅忔満杈撳嚭10涓崟璇嶏紝浣跨敤json杈撳嚭").build();
+        ChatCompletion chatCompletion = ChatCompletion
+                .builder()
+                .messages(Collections.singletonList(message))
+                .responseFormat(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT.getName()).build())
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        chatCompletionResponse.getChoices().forEach(e -> System.out.println(e.getMessage()));
+    }
+
+    @Test
+    public void streamPlugin() {
+        WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class);
+        plugin.setName("鐭ュ績澶╂皵");
+        plugin.setFunction("getLocationWeather");
+        plugin.setDescription("鎻愪緵涓�涓湴鍧�锛屾柟娉曞皢浼氳幏鍙栬鍦板潃鐨勫ぉ姘旂殑瀹炴椂娓╁害淇℃伅銆�");
+        PluginAbstract.Arg arg = new PluginAbstract.Arg();
+        arg.setName("location");
+        arg.setDescription("鍦板悕");
+        arg.setType("string");
+        arg.setRequired(true);
+        plugin.setArgs(Collections.singletonList(arg));
+
+//        Message message1 = Message.builder().role(Message.Role.USER).content("绉﹀鐨囩粺涓�浜嗗摢鍏浗銆�").build();
+        Message message2 = Message.builder().role(Message.Role.USER).content("鑾峰彇涓婃捣甯傜殑澶╂皵鐜板湪澶氬皯搴︼紝鐒跺悗鍐嶇粰鍑�3涓帹鑽愮殑鎴峰杩愬姩銆�").build();
+        List<Message> messages = new ArrayList<>();
+//        messages.add(message1);
+        messages.add(message2);
+        //榛樿妯″瀷锛欸PT_3_5_TURBO_16K_0613
+        //鏈夊洓涓噸杞芥柟娉曪紝閮藉彲浠ヤ娇鐢�
+        openAiStreamClient.streamChatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_4_1106_PREVIEW.getName(), new ConsoleEventSourceListener(), plugin);
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        try {
+            countDownLatch.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * tools浣跨敤绀轰緥
+     */
+    @Test
+    public void toolsChat() {
+        Message message = Message.builder().role(Message.Role.USER).content("缁欐垜杈撳嚭涓�涓暱搴︿负2鐨勪腑鏂囪瘝璇紝骞惰В閲婁笅璇嶈瀵瑰簲鐗╁搧鐨勭敤閫�").build();
+        //灞炴�т竴
+        JSONObject wordLength = new JSONObject();
+        wordLength.put("type", "number");
+        wordLength.put("description", "璇嶈鐨勯暱搴�");
+        //灞炴�т簩
+        JSONObject language = new JSONObject();
+        language.put("type", "string");
+        language.put("enum", Arrays.asList("zh", "en"));
+        language.put("description", "璇█绫诲瀷锛屼緥濡傦細zh浠h〃涓枃銆乪n浠h〃鑻辫");
+        //鍙傛暟
+        JSONObject properties = new JSONObject();
+        properties.put("wordLength", wordLength);
+        properties.put("language", language);
+        Parameters parameters = Parameters.builder()
+                .type("object")
+                .properties(properties)
+                .required(Collections.singletonList("wordLength")).build();
+        Tools tools = Tools.builder()
+                .type(Tools.Type.FUNCTION.getName())
+                .function(ToolsFunction.builder().name("getOneWord").description("鑾峰彇涓�涓寚瀹氶暱搴﹀拰璇█绫诲瀷鐨勮瘝璇�").parameters(parameters).build())
+                .build();
+
+        ChatCompletion chatCompletion = ChatCompletion
+                .builder()
+                .messages(Collections.singletonList(message))
+                .tools(Collections.singletonList(tools))
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+
+        ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
+        log.info("鏋勯�犵殑鏂规硶鍊硷細{}", chatChoice.getMessage().getToolCalls());
+
+        ToolCalls openAiReturnToolCalls = chatChoice.getMessage().getToolCalls().get(0);
+        WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
+        String oneWord = getOneWord(wordParam);
+
+
+        ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
+        ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
+        //鏋勯�爐ool call
+        Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("鏂规硶鍙傛暟").toolCalls(Collections.singletonList(tc)).build();
+        String content
+                = "{ " +
+                "\"wordLength\": \"3\", " +
+                "\"language\": \"zh\", " +
+                "\"word\": \"" + oneWord + "\"," +
+                "\"鐢ㄩ�擻": [\"鐩存帴鍚僜", \"鍋氭矙鎷塡", \"鍞崠\"]" +
+                "}";
+        Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
+        List<Message> messageList = Arrays.asList(message, message2, message3);
+        ChatCompletion chatCompletionV2 = ChatCompletion
+                .builder()
+                .messages(messageList)
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
+        log.info("鑷畾涔夌殑鏂规硶杩斿洖鍊硷細{}", chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
+
+    }
+
+    /**
+     * tools娴佸紡杈撳嚭浣跨敤绀轰緥
+     */
+    @Test
+    public void streamToolsChat() {
+
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch);
+
+        Message message = Message.builder().role(Message.Role.USER).content("缁欐垜杈撳嚭涓�涓暱搴︿负2鐨勪腑鏂囪瘝璇紝骞惰В閲婁笅璇嶈瀵瑰簲鐗╁搧鐨勭敤閫�").build();
+        //灞炴�т竴
+        JSONObject wordLength = new JSONObject();
+        wordLength.put("type", "number");
+        wordLength.put("description", "璇嶈鐨勯暱搴�");
+        //灞炴�т簩
+        JSONObject language = new JSONObject();
+        language.put("type", "string");
+        language.put("enum", Arrays.asList("zh", "en"));
+        language.put("description", "璇█绫诲瀷锛屼緥濡傦細zh浠h〃涓枃銆乪n浠h〃鑻辫");
+        //鍙傛暟
+        JSONObject properties = new JSONObject();
+        properties.put("wordLength", wordLength);
+        properties.put("language", language);
+        Parameters parameters = Parameters.builder()
+                .type("object")
+                .properties(properties)
+                .required(Collections.singletonList("wordLength")).build();
+        Tools tools = Tools.builder()
+                .type(Tools.Type.FUNCTION.getName())
+                .function(ToolsFunction.builder().name("getOneWord").description("鑾峰彇涓�涓寚瀹氶暱搴﹀拰璇█绫诲瀷鐨勮瘝璇�").parameters(parameters).build())
+                .build();
+
+        ChatCompletion chatCompletion = ChatCompletion
+                .builder()
+                .messages(Collections.singletonList(message))
+                .tools(Collections.singletonList(tools))
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
+
+        try {
+            countDownLatch.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls();
+        WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
+        String oneWord = getOneWord(wordParam);
+
+
+        ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
+        ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
+        //鏋勯�爐ool call
+        Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("鏂规硶鍙傛暟").toolCalls(Collections.singletonList(tc)).build();
+        String content
+                = "{ " +
+                "\"wordLength\": \"3\", " +
+                "\"language\": \"zh\", " +
+                "\"word\": \"" + oneWord + "\"," +
+                "\"鐢ㄩ�擻": [\"鐩存帴鍚僜", \"鍋氭矙鎷塡", \"鍞崠\"]" +
+                "}";
+        Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
+        List<Message> messageList = Arrays.asList(message, message2, message3);
+        ChatCompletion chatCompletionV2 = ChatCompletion
+                .builder()
+                .messages(messageList)
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+
+
+        CountDownLatch countDownLatch1 = new CountDownLatch(1);
+        openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch));
+        try {
+            countDownLatch1.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        try {
+            countDownLatch1.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+    @Data
+    @Builder
+    static class WordParam {
+        private int wordLength;
+        @Builder.Default
+        private String language = "zh";
+    }
+
+
+    /**
+     * 鑾峰彇涓�涓瘝璇�(鏍规嵁璇█鍜屽瓧绗﹂暱搴︽煡璇�)
+     * @param wordParam
+     * @return
+     */
+    public String getOneWord(WordParam wordParam) {
+
+        List<String> zh = Arrays.asList("澶ч钑�", "鍝堝瘑鐡�", "鑻规灉");
+        List<String> en = Arrays.asList("apple", "banana", "cantaloupe");
+        if (wordParam.getLanguage().equals("zh")) {
+            for (String e : zh) {
+                if (e.length() == wordParam.getWordLength()) {
+                    return e;
+                }
+            }
+        }
+        if (wordParam.getLanguage().equals("en")) {
+            for (String e : en) {
+                if (e.length() == wordParam.getWordLength()) {
+                    return e;
+                }
+            }
+        }
+        return "瑗跨摐";
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java
new file mode 100644
index 0000000..787e598
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java
@@ -0,0 +1,24 @@
+package org.ruoyi.common.chat.demo;
+
+
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+
+public class WeatherPlugin extends PluginAbstract<WeatherReq, WeatherResp> {
+
+    public WeatherPlugin(Class<?> r) {
+        super(r);
+    }
+
+    @Override
+    public WeatherResp func(WeatherReq args) {
+        WeatherResp weatherResp = new WeatherResp();
+        weatherResp.setTemp("25鍒�28鎽勬皬搴�");
+        weatherResp.setLevel(3);
+        return weatherResp;
+    }
+
+    @Override
+    public String content(WeatherResp weatherResp) {
+        return "褰撳墠澶╂皵娓╁害锛�" + weatherResp.getTemp() + "锛岄鍔涚瓑绾э細" + weatherResp.getLevel();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java
new file mode 100644
index 0000000..b0670e5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java
@@ -0,0 +1,13 @@
+package org.ruoyi.common.chat.demo;
+
+
+import lombok.Data;
+import org.ruoyi.common.chat.openai.plugin.PluginParam;
+
+@Data
+public class WeatherReq extends PluginParam {
+    /**
+     * 鍩庡競
+     */
+    private String location;
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java
new file mode 100644
index 0000000..360c56c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java
@@ -0,0 +1,15 @@
+package org.ruoyi.common.chat.demo;
+
+import lombok.Data;
+
+@Data
+public class WeatherResp {
+    /**
+     * 娓╁害
+     */
+    private String temp;
+    /**
+     * 椋庡姏绛夌骇
+     */
+    private Integer level;
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/AllToolsTest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/AllToolsTest.java
new file mode 100644
index 0000000..805402c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/AllToolsTest.java
@@ -0,0 +1,122 @@
+package org.ruoyi.common.chat.demo.zhipu;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.zhipu.oapi.ClientV4;
+import com.zhipu.oapi.Constants;
+import com.zhipu.oapi.service.v4.deserialize.MessageDeserializeFactory;
+import com.zhipu.oapi.service.v4.model.*;
+import io.reactivex.Flowable;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class AllToolsTest {
+
+    private final static Logger logger = LoggerFactory.getLogger(AllToolsTest.class);
+    private static final String API_SECRET_KEY = "28550a39d4cfaabbbf38df04dd3931f5.IUvfTThUf0xBF5l0";
+
+    private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY)
+            .networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
+            .connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
+            .build();
+    private static final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper();
+    // 璇疯嚜瀹氫箟鑷繁鐨勪笟鍔d
+    private static final String requestIdTemplate = "mycompany-%d";
+
+
+    @Test
+    public void test1() throws JsonProcessingException {
+
+
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "甯垜鏌ヨ鍖椾含澶╂皵");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        // 鍑芥暟璋冪敤鍙傛暟鏋勫缓閮ㄥ垎
+        List<ChatTool> chatToolList = new ArrayList<>();
+        ChatTool chatTool = new ChatTool();
+
+        chatTool.setType("code_interpreter");
+        ObjectNode objectNode =  mapper.createObjectNode();
+        objectNode.put("code", "鍖椾含澶╂皵");
+//        chatTool.set(chatFunction);
+        chatToolList.add(chatTool);
+
+
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model("glm-4-alltools")
+                .stream(Boolean.TRUE)
+                .invokeMethod(Constants.invokeMethod)
+                .messages(messages)
+                .tools(chatToolList)
+                .toolChoice("auto")
+                .requestId(requestId)
+                .build();
+        ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        if (sseModelApiResp.isSuccess()) {
+            AtomicBoolean isFirst = new AtomicBoolean(true);
+            List<Choice> choices = new ArrayList<>();
+            AtomicReference<ChatMessageAccumulator> lastAccumulator = new AtomicReference<>();
+
+            mapStreamToAccumulator(sseModelApiResp.getFlowable())
+                    .doOnNext(accumulator -> {
+                        {
+                            if (isFirst.getAndSet(false)) {
+                                logger.info("Response: ");
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
+                                String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
+                                logger.info("tool_calls: {}", jsonString);
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
+                                logger.info(accumulator.getDelta().getContent());
+                            }
+                            choices.add(accumulator.getChoice());
+                            lastAccumulator.set(accumulator);
+
+                        }
+                    })
+                    .doOnComplete(() -> System.out.println("Stream completed."))
+                    .doOnError(throwable -> System.err.println("Error: " + throwable)) // Handle errors
+                    .blockingSubscribe();// Use blockingSubscribe instead of blockingGet()
+
+            ChatMessageAccumulator chatMessageAccumulator = lastAccumulator.get();
+            ModelData data = new ModelData();
+            data.setChoices(choices);
+            if (chatMessageAccumulator != null) {
+                data.setUsage(chatMessageAccumulator.getUsage());
+                data.setId(chatMessageAccumulator.getId());
+                data.setCreated(chatMessageAccumulator.getCreated());
+            }
+            data.setRequestId(chatCompletionRequest.getRequestId());
+            sseModelApiResp.setFlowable(null);// 鎵撳嵃鍓嶇疆绌�
+            sseModelApiResp.setData(data);
+        }
+        logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
+        client.getConfig().getHttpClient().dispatcher().executorService().shutdown();
+
+        client.getConfig().getHttpClient().connectionPool().evictAll();
+        // List all active threads
+        for (Thread t : Thread.getAllStackTraces().keySet()) {
+            logger.info("Thread: " + t.getName() + " State: " + t.getState());
+        }
+    }
+
+
+
+    public static Flowable<ChatMessageAccumulator> mapStreamToAccumulator(Flowable<ModelData> flowable) {
+        return flowable.map(chunk -> {
+            return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId());
+        });
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/V4Test.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/V4Test.java
new file mode 100644
index 0000000..afd664e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/V4Test.java
@@ -0,0 +1,646 @@
+package org.ruoyi.common.chat.demo.zhipu;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.zhipu.oapi.ClientV4;
+import com.zhipu.oapi.Constants;
+import com.zhipu.oapi.core.response.HttpxBinaryResponseContent;
+import com.zhipu.oapi.service.v4.batchs.BatchCreateParams;
+import com.zhipu.oapi.service.v4.batchs.BatchResponse;
+import com.zhipu.oapi.service.v4.batchs.QueryBatchResponse;
+import com.zhipu.oapi.service.v4.embedding.EmbeddingApiResponse;
+import com.zhipu.oapi.service.v4.embedding.EmbeddingRequest;
+import com.zhipu.oapi.service.v4.file.*;
+import com.zhipu.oapi.service.v4.fine_turning.*;
+import com.zhipu.oapi.service.v4.image.CreateImageRequest;
+import com.zhipu.oapi.service.v4.image.ImageApiResponse;
+import com.zhipu.oapi.service.v4.model.*;
+import io.reactivex.Flowable;
+
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+public class V4Test {
+
+    private final static Logger logger = LoggerFactory.getLogger(V4Test.class);
+    private static final String API_SECRET_KEY = "28550a39d4cfaabbbf38df04dd3931f5.IUvfTThUf0xBF5l0";
+
+
+    private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY)
+            .enableTokenCache()
+            .networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
+            .connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
+            .build();
+
+    // 璇疯嚜瀹氫箟鑷繁鐨勪笟鍔d
+    private static final String requestIdTemplate = "mycompany-%d";
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+
+    public static ObjectMapper defaultObjectMapper() {
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
+        return mapper;
+    }
+
+    @Test
+    public void test() {
+
+    }
+
+    /**
+     * sse-V4锛歠unction璋冪敤
+     */
+    @Test
+    public void testFunctionSSE() throws JsonProcessingException {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "鎴愰兘鍒板寳浜澶氫箙锛屽ぉ姘斿浣�");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        // 鍑芥暟璋冪敤鍙傛暟鏋勫缓閮ㄥ垎
+        List<ChatTool> chatToolList = new ArrayList<>();
+        ChatTool chatTool = new ChatTool();
+
+        chatTool.setType(ChatToolType.FUNCTION.value());
+        ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
+        chatFunctionParameters.setType("object");
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("location", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("description", "鍩庡競锛屽锛氬寳浜�");
+        }});
+        properties.put("unit", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("enum", new ArrayList<String>() {{
+                add("celsius");
+                add("fahrenheit");
+            }});
+        }});
+        chatFunctionParameters.setProperties(properties);
+        ChatFunction chatFunction = ChatFunction.builder()
+                .name("get_weather")
+                .description("Get the current weather of a location")
+                .parameters(chatFunctionParameters)
+                .build();
+        chatTool.setFunction(chatFunction);
+        chatToolList.add(chatTool);
+        HashMap<String, Object> extraJson = new HashMap<>();
+        extraJson.put("temperature", 0.5);
+        extraJson.put("max_tokens", 50);
+
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelChatGLM4)
+                .stream(Boolean.TRUE)
+                .messages(messages)
+                .requestId(requestId)
+                .tools(chatToolList)
+                .toolChoice("auto")
+                .extraJson(extraJson)
+                .build();
+        ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        if (sseModelApiResp.isSuccess()) {
+            AtomicBoolean isFirst = new AtomicBoolean(true);
+            List<Choice> choices = new ArrayList<>();
+            ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable())
+                    .doOnNext(accumulator -> {
+                        {
+                            if (isFirst.getAndSet(false)) {
+                                logger.info("Response: ");
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
+                                String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
+                                logger.info("tool_calls: {}", jsonString);
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
+                                logger.info(accumulator.getDelta().getContent());
+                            }
+                            choices.add(accumulator.getChoice());
+                        }
+                    })
+                    .doOnComplete(System.out::println)
+                    .lastElement()
+                    .blockingGet();
+
+
+            ModelData data = new ModelData();
+            data.setChoices(choices);
+            data.setUsage(chatMessageAccumulator.getUsage());
+            data.setId(chatMessageAccumulator.getId());
+            data.setCreated(chatMessageAccumulator.getCreated());
+            data.setRequestId(chatCompletionRequest.getRequestId());
+            sseModelApiResp.setFlowable(null);// 鎵撳嵃鍓嶇疆绌�
+            sseModelApiResp.setData(data);
+        }
+        logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
+    }
+
+
+    /**
+     * sse-V4锛氶潪function璋冪敤
+     */
+    @Test
+    public void testNonFunctionSSE() throws JsonProcessingException {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "ChatGLM鍜屼綘鍝釜鏇村己澶�");
+        messages.add(chatMessage);
+        HashMap<String, Object> extraJson = new HashMap<>();
+        extraJson.put("temperature", 0.5);
+        extraJson.put("max_tokens", 3);
+
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelChatGLM4)
+                .stream(Boolean.TRUE)
+                .messages(messages)
+                .requestId(requestId)
+                .extraJson(extraJson)
+                .build();
+        ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        // stream 澶勭悊鏂规硶
+        if (sseModelApiResp.isSuccess()) {
+            AtomicBoolean isFirst = new AtomicBoolean(true);
+            List<Choice> choices = new ArrayList<>();
+            ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable())
+                    .doOnNext(accumulator -> {
+                        {
+                            if (isFirst.getAndSet(false)) {
+                                logger.info("Response: ");
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
+                                String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
+                                logger.info("tool_calls: {}", jsonString);
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
+                                logger.info("accumulator.getDelta().getContent(): {}", accumulator.getDelta().getContent());
+                            }
+                            choices.add(accumulator.getChoice());
+                        }
+                    })
+                    .doOnComplete(System.out::println)
+                    .lastElement()
+                    .blockingGet();
+
+
+            ModelData data = new ModelData();
+            data.setChoices(choices);
+            data.setUsage(chatMessageAccumulator.getUsage());
+            data.setId(chatMessageAccumulator.getId());
+            data.setCreated(chatMessageAccumulator.getCreated());
+            data.setRequestId(chatCompletionRequest.getRequestId());
+            sseModelApiResp.setFlowable(null);// 鎵撳嵃鍓嶇疆绌�
+            sseModelApiResp.setData(data);
+        }
+        logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
+    }
+
+
+    /**
+     * V4-鍚屾function璋冪敤
+     */
+    @Test
+    public void testFunctionInvoke() {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "浣犲彲浠ュ仛浠�涔�");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        // 鍑芥暟璋冪敤鍙傛暟鏋勫缓閮ㄥ垎
+        List<ChatTool> chatToolList = new ArrayList<>();
+        ChatTool chatTool = new ChatTool();
+        chatTool.setType(ChatToolType.FUNCTION.value());
+        ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
+        chatFunctionParameters.setType("object");
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("location", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("description", "鍩庡競锛屽锛氬寳浜�");
+        }});
+        properties.put("unit", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("enum", new ArrayList<String>() {{
+                add("celsius");
+                add("fahrenheit");
+            }});
+        }});
+        chatFunctionParameters.setProperties(properties);
+        ChatFunction chatFunction = ChatFunction.builder()
+                .name("get_weather")
+                .description("Get the current weather of a location")
+                .parameters(chatFunctionParameters)
+                .build();
+        chatTool.setFunction(chatFunction);
+
+
+        ChatTool chatTool1 = new ChatTool();
+        chatTool1.setType(ChatToolType.WEB_SEARCH.value());
+        WebSearch webSearch = new WebSearch();
+        webSearch.setSearch_query("娓呭崕鐨勫崌瀛︾巼");
+        webSearch.setSearch_result(true);
+        webSearch.setEnable(false);
+        chatTool1.setWeb_search(webSearch);
+
+        chatToolList.add(chatTool);
+        chatToolList.add(chatTool1);
+
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelChatGLM4)
+                .stream(Boolean.FALSE)
+                .invokeMethod(Constants.invokeMethod)
+                .messages(messages)
+                .requestId(requestId)
+                .tools(chatToolList)
+                .toolChoice("auto")
+                .build();
+        ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        try {
+            logger.info("model output: {}", mapper.writeValueAsString(invokeModelApiResp));
+        } catch (JsonProcessingException e) {
+            logger.error("model output error", e);
+        }
+    }
+
+
+    /**
+     * V4-鍚屾闈瀎unction璋冪敤
+     */
+    @Test
+    public void testNonFunctionInvoke() throws JsonProcessingException {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "ChatGLM鍜屼綘鍝釜鏇村己澶�");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+
+
+        HashMap<String, Object> extraJson = new HashMap<>();
+        extraJson.put("temperature", 0.5);
+        extraJson.put("max_tokens", 3);
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelChatGLM4)
+                .stream(Boolean.FALSE)
+                .invokeMethod(Constants.invokeMethod)
+                .messages(messages)
+                .requestId(requestId)
+                .extraJson(extraJson)
+                .build();
+        ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(invokeModelApiResp));
+    }
+
+
+    /**
+     * V4-鍚屾闈瀎unction璋冪敤
+     */
+    @Test
+    public void testCharGlmInvoke() throws JsonProcessingException {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "ChatGLM鍜屼綘鍝釜鏇村己澶�");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+
+
+        HashMap<String, Object> extraJson = new HashMap<>();
+        extraJson.put("temperature", 0.5);
+
+        ChatMeta meta = new ChatMeta();
+        meta.setUser_info("鎴戞槸闄嗘槦杈帮紝鏄竴涓敺鎬э紝鏄竴浣嶇煡鍚嶅婕旓紝涔熸槸鑻忔ⅵ杩滅殑鍚堜綔瀵兼紨銆傛垜鎿呴暱鎷嶆憚闊充箰棰樻潗鐨勭數褰便�傝嫃姊﹁繙瀵规垜鐨勬�佸害鏄皧鏁殑锛屽苟瑙嗘垜涓鸿壇甯堢泭鍙嬨��");
+        meta.setBot_info("鑻忔ⅵ杩滐紝鏈悕鑻忚繙蹇冿紝鏄竴浣嶅綋绾㈢殑鍥藉唴濂虫瓕鎵嬪強婕斿憳銆傚湪鍙傚姞閫夌鑺傜洰鍚庯紝鍑�熺嫭鐗圭殑鍡撻煶鍙婂嚭浼楃殑鑸炲彴榄呭姏杩呴�熸垚鍚嶏紝杩涘叆濞变箰鍦堛�傚ス澶栬〃缇庝附鍔ㄤ汉锛屼絾鐪熸鐨勯瓍鍔涘湪浜庡ス鐨勬墠鍗庡拰鍕ゅ銆傝嫃姊﹁繙鏄煶涔愬闄㈡瘯涓氱殑浼樼鐢燂紝鍠勪簬鍒涗綔锛屾嫢鏈夊棣栫儹闂ㄥ師鍒涙瓕鏇层�傞櫎浜嗛煶涔愭柟闈㈢殑鎴愬氨锛屽ス杩樼儹琛蜂簬鎱堝杽浜嬩笟锛岀Н鏋佸弬鍔犲叕鐩婃椿鍔紝鐢ㄥ疄闄呰鍔ㄤ紶閫掓鑳介噺銆傚湪宸ヤ綔涓紝濂瑰寰呭伐浣滈潪甯告暚涓氾紝鎷嶆垙鏃舵�绘槸鍏ㄨ韩蹇冩姇鍏ヨ鑹诧紝璧㈠緱浜嗕笟鍐呬汉澹殑璧炶獕鍜岀矇涓濈殑鍠滅埍銆傝櫧鐒跺湪濞变箰鍦堬紝浣嗗ス濮嬬粓淇濇寔浣庤皟銆佽唉閫婄殑鎬佸害锛屾繁寰楀悓琛屽皧閲嶃�傚湪琛ㄨ揪鏃讹紝鑻忔ⅵ杩滃枩娆娇鐢ㄢ�滄垜浠�濆拰鈥滀竴璧封�濓紝寮鸿皟鍥㈤槦绮剧銆�");
+        meta.setBot_name("鑻忔ⅵ杩�");
+        meta.setUser_name("闄嗘槦杈�");
+
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelCharGLM3)
+                .stream(Boolean.FALSE)
+                .invokeMethod(Constants.invokeMethod)
+                .messages(messages)
+                .requestId(requestId)
+                .meta(meta)
+                .extraJson(extraJson)
+                .build();
+        ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(invokeModelApiResp));
+    }
+
+    /**
+     * V4寮傛璋冪敤
+     */
+    @Test
+    public void testAsyncInvoke() throws JsonProcessingException {
+        String taskId = getAsyncTaskId();
+        testQueryResult(taskId);
+    }
+
+//
+
+    /**
+     * 鏂囩敓鍥�
+     */
+    @Test
+    public void testCreateImage() throws JsonProcessingException {
+        CreateImageRequest createImageRequest = new CreateImageRequest();
+        createImageRequest.setModel(Constants.ModelCogView);
+        createImageRequest.setPrompt("Futuristic cloud data center, showcasing advanced technologgy and a high-tech atmosp\n" +
+                "here. The image should depict a spacious, well-lit interior with rows of server racks, glo\n" +
+                "wing lights, and digital displays. Include abstract representattions of data streams and\n" +
+                "onnectivity, symbolizing the essence of cloud computing. Thee style should be modern a\n" +
+                "nd sleek, with a focus on creating a sense of innovaticon and cutting-edge technology\n" +
+                "The overall ambiance should convey the power and effciency of cloud services in a visu\n" +
+                "ally engaging way.");
+        createImageRequest.setRequestId("test11111111111111");
+        ImageApiResponse imageApiResponse = client.createImage(createImageRequest);
+        logger.info("imageApiResponse: {}", mapper.writeValueAsString(imageApiResponse));
+    }
+
+//
+//    /**
+//     * 鍥剧敓鏂�
+//     */
+//    @Test
+//    public void testImageToWord() throws JsonProcessingException {
+//        List<ChatMessage> messages = new ArrayList<>();
+//        List<Map<String, Object>> contentList = new ArrayList<>();
+//        Map<String, Object> textMap = new HashMap<>();
+//        textMap.put("type", "text");
+//        textMap.put("text", "鍥鹃噷鏈変粈涔�");
+//        Map<String, Object> typeMap = new HashMap<>();
+//        typeMap.put("type", "image_url");
+//        Map<String, Object> urlMap = new HashMap<>();
+//        urlMap.put("url", "https://sfile.chatglm.cn/testpath/275ae5b6-5390-51ca-a81a-60332d1a7cac_0.png");
+//        typeMap.put("image_url", urlMap);
+//        contentList.add(textMap);
+//        contentList.add(typeMap);
+//        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), contentList);
+//        messages.add(chatMessage);
+//        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+//
+//
+//        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+//                .model(Constants.ModelChatGLM4V)
+//                .stream(Boolean.FALSE)
+//                .invokeMethod(Constants.invokeMethod)
+//                .messages(messages)
+//                .requestId(requestId)
+//                .build();
+//        ModelApiResponse modelApiResponse = client.invokeModelApi(chatCompletionRequest);
+//        logger.info("model output: {}", mapper.writeValueAsString(modelApiResponse));
+//    }
+//
+
+    /**
+     * 鍚戦噺妯″瀷V4
+     */
+    @Test
+    public void testEmbeddings() throws JsonProcessingException {
+        EmbeddingRequest embeddingRequest = new EmbeddingRequest();
+        embeddingRequest.setInput("hello world");
+        embeddingRequest.setModel(Constants.ModelEmbedding2);
+        EmbeddingApiResponse apiResponse = client.invokeEmbeddingsApi(embeddingRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(apiResponse));
+    }
+
+
+    /**
+     * V4寰皟涓婁紶鏁版嵁闆�
+     */
+    @Test
+    public void testUploadFile() throws JsonProcessingException {
+        String filePath = "demo.jsonl";
+
+        String path = ClassLoader.getSystemResource(filePath).getPath();
+        String purpose = "fine-tune";
+        UploadFileRequest request = UploadFileRequest.builder()
+                .purpose(purpose)
+                .filePath(path)
+                .build();
+
+        FileApiResponse fileApiResponse = client.invokeUploadFileApi(request);
+        logger.info("model output: {}", mapper.writeValueAsString(fileApiResponse));
+    }
+
+
+    /**
+     * 寰皟V4-鏌ヨ涓婁紶鏂囦欢鍒楄〃
+     */
+    @Test
+    public void testQueryUploadFileList() throws JsonProcessingException {
+        QueryFilesRequest queryFilesRequest = new QueryFilesRequest();
+        QueryFileApiResponse queryFileApiResponse = client.queryFilesApi(queryFilesRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(queryFileApiResponse));
+    }
+
+    @Test
+    public void testFileContent() throws IOException {
+        try {
+
+            HttpxBinaryResponseContent httpxBinaryResponseContent = client.fileContent("20240514_ea19d21b-d256-4586-b0df-e80a45e3c286");
+            String filePath = "demo_output.jsonl";
+            String resourcePath = V4Test.class.getClassLoader().getResource("").getPath();
+
+            httpxBinaryResponseContent.streamToFile(resourcePath + "1" + filePath, 1000);
+
+        } catch (IOException e) {
+            logger.error("file content error", e);
+        }
+    }
+
+////    @Test
+////    public void deletedFile() throws IOException {
+////        FileDelResponse fileDelResponse = client.deletedFile("20240514_ea19d21b-d256-4586-b0df-e80a45e3c286");
+////
+////        logger.info("model output: {}", mapper.writeValueAsString(fileDelResponse));
+////
+////    }
+//
+//
+
+    /**
+     * 寰皟V4-鍒涘缓寰皟浠诲姟
+     */
+    @Test
+    public void testCreateFineTuningJob() throws JsonProcessingException {
+        FineTuningJobRequest request = new FineTuningJobRequest();
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        request.setRequestId(requestId);
+        request.setModel("chatglm3-6b");
+        request.setTraining_file("file-20240118082608327-kp8qr");
+        CreateFineTuningJobApiResponse createFineTuningJobApiResponse = client.createFineTuningJob(request);
+        logger.info("model output: {}", mapper.writeValueAsString(createFineTuningJobApiResponse));
+    }
+
+
+    /**
+     * 寰皟V4-鏌ヨ寰皟浠诲姟
+     */
+    @Test
+    public void testRetrieveFineTuningJobs() throws JsonProcessingException {
+        QueryFineTuningJobRequest queryFineTuningJobRequest = new QueryFineTuningJobRequest();
+        queryFineTuningJobRequest.setJobId("ftjob-20240429172916475-fb7r9");
+//        queryFineTuningJobRequest.setLimit(1);
+//        queryFineTuningJobRequest.setAfter(1);
+        QueryFineTuningJobApiResponse queryFineTuningJobApiResponse = client.retrieveFineTuningJobs(queryFineTuningJobRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(queryFineTuningJobApiResponse));
+    }
+
+
+    /**
+     * 寰皟V4-鏌ヨ寰皟浠诲姟
+     */
+    @Test
+    public void testFueryFineTuningJobsEvents() throws JsonProcessingException {
+        QueryFineTuningJobRequest queryFineTuningJobRequest = new QueryFineTuningJobRequest();
+        queryFineTuningJobRequest.setJobId("ftjob-20240429172916475-fb7r9");
+
+        QueryFineTuningEventApiResponse queryFineTuningEventApiResponse = client.queryFineTuningJobsEvents(queryFineTuningJobRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(queryFineTuningEventApiResponse));
+    }
+
+
+    /**
+     * testQueryPersonalFineTuningJobs V4-鏌ヨ涓汉寰皟浠诲姟
+     */
+    @Test
+    public void testQueryPersonalFineTuningJobs() throws JsonProcessingException {
+        QueryPersonalFineTuningJobRequest queryPersonalFineTuningJobRequest = new QueryPersonalFineTuningJobRequest();
+        queryPersonalFineTuningJobRequest.setLimit(1);
+        QueryPersonalFineTuningJobApiResponse queryPersonalFineTuningJobApiResponse = client.queryPersonalFineTuningJobs(queryPersonalFineTuningJobRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(queryPersonalFineTuningJobApiResponse));
+    }
+
+
+    @Test
+    public void testBatchesCreate() {
+        BatchCreateParams batchCreateParams = new BatchCreateParams(
+                "24h",
+                "/v4/chat/completions",
+                "20240514_ea19d21b-d256-4586-b0df-e80a45e3c286",
+                new HashMap<String, String>() {{
+                    put("key1", "value1");
+                    put("key2", "value2");
+                }}
+        );
+
+        BatchResponse batchResponse = client.batchesCreate(batchCreateParams);
+        logger.info("output: {}", batchResponse);
+//         output: BatchResponse(code=200, msg=璋冪敤鎴愬姛, success=true, data=Batch(id=batch_1791021399316246528, completionWindow=24h, createdAt=1715847751822, endpoint=/v4/chat/completions, inputFileId=20240514_ea19d21b-d256-4586-b0df-e80a45e3c286, object=batch, status=validating, cancelledAt=null, cancellingAt=null, completedAt=null, errorFileId=null, errors=null, expiredAt=null, expiresAt=null, failedAt=null, finalizingAt=null, inProgressAt=null, metadata={key1=value1, key2=value2}, outputFileId=null, requestCounts=BatchRequestCounts(completed=0, failed=0, total=0), error=null))
+    }
+
+    @Test
+    public void testDeleteFineTuningJob() {
+        FineTuningJobIdRequest request = FineTuningJobIdRequest.builder().jobId("test").build();
+        QueryFineTuningJobApiResponse queryFineTuningJobApiResponse = client.deleteFineTuningJob(request);
+        logger.info("output: {}", queryFineTuningJobApiResponse);
+
+    }
+
+    @Test
+    public void testCancelFineTuningJob() {
+        FineTuningJobIdRequest request = FineTuningJobIdRequest.builder().jobId("test").build();
+        QueryFineTuningJobApiResponse queryFineTuningJobApiResponse = client.cancelFineTuningJob(request);
+        logger.info("output: {}", queryFineTuningJobApiResponse);
+
+    }
+
+    @Test
+    public void testBatchesRetrieve() {
+        BatchResponse batchResponse = client.batchesRetrieve("batch_1791021399316246528");
+        logger.info("output: {}", batchResponse);
+
+    }
+
+    @Test
+    public void testDeleteFineTuningModel() {
+        FineTuningJobModelRequest request = FineTuningJobModelRequest.builder().fineTunedModel("test").build();
+
+        FineTunedModelsStatusResponse fineTunedModelsStatusResponse = client.deleteFineTuningModel(request);
+        logger.info("output: {}", fineTunedModelsStatusResponse);
+//        output: BatchResponse(code=200, msg=璋冪敤鎴愬姛, success=true, data=Batch(id=batch_1791021399316246528, completionWindow=24h, createdAt=1715847752000, endpoint=/v4/chat/completions, inputFileId=20240514_ea19d21b-d256-4586-b0df-e80a45e3c286, object=batch, status=validating, cancelledAt=null, cancellingAt=null, completedAt=null, errorFileId=, errors=null, expiredAt=null, expiresAt=null, failedAt=null, finalizingAt=null, inProgressAt=null, metadata={key1=value1, key2=value2}, outputFileId=, requestCounts=BatchRequestCounts(completed=0, failed=0, total=0), error=null))
+
+    }
+
+    @Test
+    public void testBatchesList() {
+        QueryBatchRequest queryBatchRequest = new QueryBatchRequest();
+        queryBatchRequest.setLimit(10);
+        QueryBatchResponse queryBatchResponse = client.batchesList(queryBatchRequest);
+        logger.info("output: {}", queryBatchResponse);
+// output: QueryBatchResponse(code=200, msg=璋冪敤鎴愬姛, success=true, data=BatchPage(object=list, data=[Batch(id=batch_1790291013237211136, completionWindow=24h, createdAt=1715673614000, endpoint=/v4/chat/completions, inputFileId=20240514_ea19d21b-d256-4586-b0df-e80a45e3c286, object=batch, status=completed, cancelledAt=null, cancellingAt=1715673699000, completedAt=null, errorFileId=, errors=null, expiredAt=null, expiresAt=null, failedAt=null, finalizingAt=null, inProgressAt=null, metadata={description=job test}, outputFileId=, requestCounts=BatchRequestCounts(completed=0, failed=0, total=0), error=null), Batch(id=batch_1790292763050508288, completionWindow=24h, createdAt=1715674031000, endpoint=/v4/chat/completions, inputFileId=20240514_ea19d21b-d256-4586-b0df-e80a45e3c286, object=batch, status=completed, cancelledAt=null, cancellingAt=null, completedAt=1715766416000, errorFileId=, errors=null, expiredAt=null, expiresAt=null, failedAt=null, finalizingAt=1715754569000, inProgressAt=null, metadata={description=job test}, outputFileId=1715766415_e5a77222855a406ca8a082de28549c99, requestCounts=BatchRequestCounts(completed=2, failed=0, total=2), error=null), Batch(id=batch_1791021114887909376, completionWindow=24h, createdAt=1715847684000, endpoint=/v4/chat/completions, inputFileId=20240514_ea19d21b-d256-4586-b0df-e80a45e3c286, object=batch, status=validating, cancelledAt=null, cancellingAt=null, completedAt=null, errorFileId=, errors=null, expiredAt=null, expiresAt=null, failedAt=null, finalizingAt=null, inProgressAt=null, metadata={key1=value1, key2=value2}, outputFileId=, requestCounts=BatchRequestCounts(completed=0, failed=0, total=0), error=null), Batch(id=batch_1791021399316246528, completionWindow=24h, createdAt=1715847752000, endpoint=/v4/chat/completions, inputFileId=20240514_ea19d21b-d256-4586-b0df-e80a45e3c286, object=batch, status=validating, cancelledAt=null, cancellingAt=null, completedAt=null, errorFileId=, errors=null, expiredAt=null, expiresAt=null, failedAt=null, finalizingAt=null, inProgressAt=null, metadata={key1=value1, key2=value2}, outputFileId=, requestCounts=BatchRequestCounts(completed=0, failed=0, total=0), error=null)], error=null))
+
+    }
+
+    @Test
+    public void testBatchesCancel() throws JsonProcessingException {
+        getAsyncTaskId();
+    }
+
+    private static String getAsyncTaskId() throws JsonProcessingException {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "ChatGLM鍜屼綘鍝釜鏇村己澶�");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        // 鍑芥暟璋冪敤鍙傛暟鏋勫缓閮ㄥ垎
+        List<ChatTool> chatToolList = new ArrayList<>();
+        ChatTool chatTool = new ChatTool();
+        chatTool.setType(ChatToolType.FUNCTION.value());
+        ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
+        chatFunctionParameters.setType("object");
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("location", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("description", "鍩庡競锛屽锛氬寳浜�");
+        }});
+        properties.put("unit", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("enum", new ArrayList<String>() {{
+                add("celsius");
+                add("fahrenheit");
+            }});
+        }});
+        chatFunctionParameters.setProperties(properties);
+        ChatFunction chatFunction = ChatFunction.builder()
+                .name("get_weather")
+                .description("Get the current weather of a location")
+                .parameters(chatFunctionParameters)
+                .build();
+        chatTool.setFunction(chatFunction);
+        chatToolList.add(chatTool);
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelChatGLM4)
+                .stream(Boolean.FALSE)
+                .invokeMethod(Constants.invokeMethodAsync)
+                .messages(messages)
+                .requestId(requestId)
+                .tools(chatToolList)
+                .toolChoice("auto")
+                .build();
+        ModelApiResponse invokeModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        logger.info("model output: {}", mapper.writeValueAsString(invokeModelApiResp));
+        return invokeModelApiResp.getData().getId();
+    }
+
+
+    private static void testQueryResult(String taskId) throws JsonProcessingException {
+        QueryModelResultRequest request = new QueryModelResultRequest();
+        request.setTaskId(taskId);
+        QueryModelResultResponse queryResultResp = client.queryModelResult(request);
+        logger.info("model output {}", mapper.writeValueAsString(queryResultResp));
+    }
+
+    public static Flowable<ChatMessageAccumulator> mapStreamToAccumulator(Flowable<ModelData> flowable) {
+        return flowable.map(chunk -> {
+            return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId());
+        });
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/WebSearchToolsTest.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/WebSearchToolsTest.java
new file mode 100644
index 0000000..eca586f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/zhipu/WebSearchToolsTest.java
@@ -0,0 +1,246 @@
+package org.ruoyi.common.chat.demo.zhipu;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zhipu.oapi.ClientV4;
+import com.zhipu.oapi.Constants;
+import com.zhipu.oapi.service.v4.tools.*;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.zhipu.oapi.core.response.HttpxBinaryResponseContent;
+import com.zhipu.oapi.service.v4.batchs.BatchCreateParams;
+import com.zhipu.oapi.service.v4.batchs.BatchResponse;
+import com.zhipu.oapi.service.v4.batchs.QueryBatchResponse;
+import com.zhipu.oapi.service.v4.embedding.EmbeddingApiResponse;
+import com.zhipu.oapi.service.v4.embedding.EmbeddingRequest;
+import com.zhipu.oapi.service.v4.file.*;
+import com.zhipu.oapi.service.v4.fine_turning.*;
+import com.zhipu.oapi.service.v4.image.CreateImageRequest;
+import com.zhipu.oapi.service.v4.image.ImageApiResponse;
+import com.zhipu.oapi.service.v4.model.*;
+import io.reactivex.Flowable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+public class WebSearchToolsTest {
+
+    private final static Logger logger = LoggerFactory.getLogger(WebSearchToolsTest.class);
+    private static final String API_SECRET_KEY = "xx";
+
+    private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY)
+            .networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
+            .connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
+            .build();
+    private static final ObjectMapper mapper = new ObjectMapper();
+    // 璇疯嚜瀹氫箟鑷繁鐨勪笟鍔d
+    private static final String requestIdTemplate = "mycompany-%d";
+
+
+    @Test
+    public void test1() throws JsonProcessingException {
+
+//        json 杞崲  ArrayList<SearchChatMessage>
+        String jsonString = "[\n" +
+                "                {\n" +
+                "                    \"content\": \"浠婂ぉ姝︽眽澶╂皵鎬庝箞鏍穃",\n" +
+                "                    \"role\": \"user\"\n" +
+                "                }\n" +
+                "            ]";
+
+        ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
+        });
+
+
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
+                .model("web-search-pro")
+                .stream(Boolean.TRUE)
+                .messages(messages)
+                .requestId(requestId)
+                .build();
+        WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest);
+        if (webSearchApiResponse.isSuccess()) {
+            AtomicBoolean isFirst = new AtomicBoolean(true);
+            List<ChoiceDelta> choices = new ArrayList<>();
+            AtomicReference<WebSearchPro> lastAccumulator = new AtomicReference<>();
+
+            webSearchApiResponse.getFlowable().map(result -> result)
+                    .doOnNext(accumulator -> {
+                        {
+                            if (isFirst.getAndSet(false)) {
+                                logger.info("Response: ");
+                            }
+                            ChoiceDelta delta = accumulator.getChoices().get(0).getDelta();
+                            if (delta != null && delta.getToolCalls() != null) {
+                                logger.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls()));
+                            }
+                            choices.add(delta);
+                            lastAccumulator.set(accumulator);
+
+                        }
+                    })
+                    .doOnComplete(() -> System.out.println("Stream completed."))
+                    .doOnError(throwable -> System.err.println("Error: " + throwable)) // Handle errors
+                    .blockingSubscribe();// Use blockingSubscribe instead of blockingGet()
+
+            WebSearchPro chatMessageAccumulator = lastAccumulator.get();
+
+            webSearchApiResponse.setFlowable(null);// 鎵撳嵃鍓嶇疆绌�
+            webSearchApiResponse.setData(chatMessageAccumulator);
+        }
+        logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
+        client.getConfig().getHttpClient().dispatcher().executorService().shutdown();
+
+        client.getConfig().getHttpClient().connectionPool().evictAll();
+        // List all active threads
+        for (Thread t : Thread.getAllStackTraces().keySet()) {
+            logger.info("Thread: " + t.getName() + " State: " + t.getState());
+        }
+
+    }
+
+
+    @Test
+    public void test2() throws JsonProcessingException {
+
+//        json 杞崲  ArrayList<SearchChatMessage>
+        String jsonString = "[\n" +
+                "                {\n" +
+                "                    \"content\": \"浠婂ぉ澶╂皵鎬庝箞鏍穃",\n" +
+                "                    \"role\": \"user\"\n" +
+                "                }\n" +
+                "            ]";
+
+        ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
+        });
+
+
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
+                .model("web-search-pro")
+                .stream(Boolean.FALSE)
+                .messages(messages)
+                .requestId(requestId)
+                .build();
+        WebSearchApiResponse webSearchApiResponse = client.invokeWebSearchPro(chatCompletionRequest);
+
+        logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
+
+    }
+
+
+    @Test
+    public void testFunctionSSE() throws JsonProcessingException {
+        List<ChatMessage> messages = new ArrayList<>();
+        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "鎴愰兘鍒板寳浜澶氫箙锛屽ぉ姘斿浣�");
+        messages.add(chatMessage);
+        String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+        // 鍑芥暟璋冪敤鍙傛暟鏋勫缓閮ㄥ垎
+        List<ChatTool> chatToolList = new ArrayList<>();
+        ChatTool chatTool = new ChatTool();
+
+        chatTool.setType(ChatToolType.FUNCTION.value());
+        ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
+        chatFunctionParameters.setType("object");
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("location", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("description", "鍩庡競锛屽锛氬寳浜�");
+        }});
+        properties.put("unit", new HashMap<String, Object>() {{
+            put("type", "string");
+            put("enum", new ArrayList<String>() {{
+                add("celsius");
+                add("fahrenheit");
+            }});
+        }});
+        chatFunctionParameters.setProperties(properties);
+        ChatFunction chatFunction = ChatFunction.builder()
+                .name("get_weather")
+                .description("Get the current weather of a location")
+                .parameters(chatFunctionParameters)
+                .build();
+        chatTool.setFunction(chatFunction);
+        chatToolList.add(chatTool);
+        HashMap<String, Object> extraJson = new HashMap<>();
+        extraJson.put("temperature", 0.5);
+        extraJson.put("max_tokens", 50);
+
+        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
+                .model(Constants.ModelChatGLM4)
+                .stream(Boolean.TRUE)
+                .messages(messages)
+                .requestId(requestId)
+                .tools(chatToolList)
+                .toolChoice("auto")
+                .extraJson(extraJson)
+                .build();
+        ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
+        if (sseModelApiResp.isSuccess()) {
+            AtomicBoolean isFirst = new AtomicBoolean(true);
+            List<Choice> choices = new ArrayList<>();
+            ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable())
+                    .doOnNext(accumulator -> {
+                        {
+                            if (isFirst.getAndSet(false)) {
+                                logger.info("Response: ");
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
+                                String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
+                                logger.info("tool_calls: {}", jsonString);
+                            }
+                            if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
+                                logger.info(accumulator.getDelta().getContent());
+                            }
+                            choices.add(accumulator.getChoice());
+                        }
+                    })
+                    .doOnComplete(System.out::println)
+                    .lastElement()
+                    .blockingGet();
+
+
+            ModelData data = new ModelData();
+            data.setChoices(choices);
+            data.setUsage(chatMessageAccumulator.getUsage());
+            data.setId(chatMessageAccumulator.getId());
+            data.setCreated(chatMessageAccumulator.getCreated());
+            data.setRequestId(chatCompletionRequest.getRequestId());
+            sseModelApiResp.setFlowable(null);// 鎵撳嵃鍓嶇疆绌�
+            sseModelApiResp.setData(data);
+        }
+        logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
+    }
+
+    public static Flowable<ChatMessageAccumulator> mapStreamToAccumulator(Flowable<ModelData> flowable) {
+        return flowable.map(chunk -> {
+            return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId());
+        });
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java
index 9637569..86faa47 100644
--- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Message.java
@@ -2,12 +2,11 @@
 
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.AllArgsConstructor;
 import lombok.Data;
-import lombok.Getter;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * 鎻忚堪锛�
@@ -18,20 +17,9 @@
 @Data
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @JsonIgnoreProperties(ignoreUnknown = true)
-public class Message implements Serializable {
-
-    /**
-     * 鐩墠鏀寔鍥涗釜涓鑹插弬鑰冨畼缃戯紝杩涜鎯呮櫙杈撳叆锛�
-     * https://platform.openai.com/docs/guides/chat/introduction
-     */
-    private String role;
+public class Message extends BaseMessage implements Serializable {
 
     private Object content;
-
-    private String name;
-
-    @JsonProperty("function_call")
-    private FunctionCall functionCall;
 
     public static Builder builder() {
         return new Builder();
@@ -41,44 +29,37 @@
      * 鏋勯�犲嚱鏁�
      *
      * @param role         瑙掕壊
-     * @param content      鎻忚堪涓婚淇℃伅
      * @param name         name
+     * @param content      content
      * @param functionCall functionCall
      */
-    public Message(String role, String content, String name, FunctionCall functionCall) {
-        this.role = role;
+    public Message(String role, String name, String content, List<ToolCalls> toolCalls, String toolCallId, FunctionCall functionCall) {
         this.content = content;
-        this.name = name;
-        this.functionCall = functionCall;
+        super.setRole(role);
+        super.setName(name);
+        super.setToolCalls(toolCalls);
+        super.setToolCallId(toolCallId);
+        super.setFunctionCall(functionCall);
     }
 
     public Message() {
     }
 
     private Message(Builder builder) {
-        setRole(builder.role);
         setContent(builder.content);
-        setName(builder.name);
-        setFunctionCall(builder.functionCall);
-    }
-
-
-    @Getter
-    @AllArgsConstructor
-    public enum Role {
-
-        SYSTEM("system"),
-        USER("user"),
-        ASSISTANT("assistant"),
-        FUNCTION("function"),
-        ;
-        private String name;
+        super.setRole(builder.role);
+        super.setName(builder.name);
+        super.setFunctionCall(builder.functionCall);
+        super.setToolCalls(builder.toolCalls);
+        super.setToolCallId(builder.toolCallId);
     }
 
     public static final class Builder {
         private String role;
         private String content;
         private String name;
+        private String toolCallId;
+        private List<ToolCalls> toolCalls;
         private FunctionCall functionCall;
 
         public Builder() {
@@ -109,6 +90,16 @@
             return this;
         }
 
+        public Builder toolCalls(List<ToolCalls> toolCalls) {
+            this.toolCalls = toolCalls;
+            return this;
+        }
+
+        public Builder toolCallId(String toolCallId) {
+            this.toolCallId = toolCallId;
+            return this;
+        }
+
         public Message build() {
             return new Message(this);
         }
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java
index 5c15484..d1c691b 100644
--- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/ResponseFormat.java
@@ -24,5 +24,6 @@
         TEXT("text"),
         ;
         private final String name;
+
     }
 }
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java
index 2cc3529..08088c4 100644
--- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/listener/WebSocketEventListener.java
@@ -75,6 +75,8 @@
             return;
         }
         ResponseBody body = response.body();
+
+
         if (Objects.nonNull(body)) {
             // 杩斿洖闈炴祦寮忓洖澶嶅唴瀹�
             if(response.code() == OpenAIConst.SUCCEED_CODE){
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java
index 0ee9766..ccc5214 100644
--- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiClient.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import io.reactivex.Single;
 import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
@@ -12,9 +13,7 @@
 import org.ruoyi.common.chat.constant.OpenAIConst;
 import org.ruoyi.common.chat.entity.billing.BillingUsage;
 import org.ruoyi.common.chat.entity.billing.Subscription;
-import org.ruoyi.common.chat.entity.chat.ChatCompletion;
-import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
-import org.ruoyi.common.chat.entity.chat.Message;
+import org.ruoyi.common.chat.entity.chat.*;
 import org.ruoyi.common.chat.entity.common.DeleteResponse;
 import org.ruoyi.common.chat.entity.common.OpenAiResponse;
 import org.ruoyi.common.chat.entity.completions.Completion;
@@ -43,6 +42,8 @@
 import org.ruoyi.common.chat.openai.interceptor.DefaultOpenAiAuthInterceptor;
 import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
 import org.ruoyi.common.chat.openai.interceptor.OpenAiAuthInterceptor;
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+import org.ruoyi.common.chat.openai.plugin.PluginParam;
 import org.ruoyi.common.core.exception.base.BaseException;
 import org.jetbrains.annotations.NotNull;
 import retrofit2.Retrofit;
@@ -697,6 +698,90 @@
     }
 
     /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param chatCompletion 鍙傛暟
+     * @param plugin         鎻掍欢
+     * @param <R>            鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>            鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     * @return ChatCompletionResponse
+     */
+    public <R extends PluginParam, T> ChatCompletionResponse chatCompletionWithPlugin(ChatCompletion chatCompletion, PluginAbstract<R, T> plugin) {
+        if (Objects.isNull(plugin)) {
+            return this.chatCompletion(chatCompletion);
+        }
+        if (CollectionUtil.isEmpty(chatCompletion.getMessages())) {
+            throw new BaseException(CommonError.MESSAGE_NOT_NUL.msg());
+        }
+        List<Message> messages = chatCompletion.getMessages();
+        Functions functions = Functions.builder()
+                .name(plugin.getFunction())
+                .description(plugin.getDescription())
+                .parameters(plugin.getParameters())
+                .build();
+        //娌℃湁鍊硷紝璁剧疆榛樿鍊�
+        if (Objects.isNull(chatCompletion.getFunctionCall())) {
+            chatCompletion.setFunctionCall("auto");
+        }
+        //tip: 瑕嗙洊鑷繁璁剧疆鐨刦unctions鍙傛暟锛屼娇鐢╬lugin鏋勯�犵殑functions
+        chatCompletion.setFunctions(Collections.singletonList(functions));
+        //璋冪敤OpenAi
+        ChatCompletionResponse functionCallChatCompletionResponse = this.chatCompletion(chatCompletion);
+        ChatChoice chatChoice = functionCallChatCompletionResponse.getChoices().get(0);
+        log.debug("鏋勯�犵殑鏂规硶鍊硷細{}", chatChoice.getMessage().getFunctionCall());
+
+        R realFunctionParam = (R) JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), plugin.getR());
+        T tq = plugin.func(realFunctionParam);
+
+        FunctionCall functionCall = FunctionCall.builder()
+                .arguments(chatChoice.getMessage().getFunctionCall().getArguments())
+                .name(plugin.getFunction())
+                .build();
+        messages.add(Message.builder().role(Message.Role.ASSISTANT).content("function_call").functionCall(functionCall).build());
+        messages.add(Message.builder().role(Message.Role.FUNCTION).name(plugin.getFunction()).content(plugin.content(tq)).build());
+        //璁剧疆绗簩娆★紝璇锋眰鐨勫弬鏁�
+        chatCompletion.setFunctionCall(null);
+        chatCompletion.setFunctions(null);
+
+        ChatCompletionResponse chatCompletionResponse = this.chatCompletion(chatCompletion);
+        log.debug("鑷畾涔夌殑鏂规硶杩斿洖鍊硷細{}", chatCompletionResponse.getChoices());
+        return chatCompletionResponse;
+    }
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param messages 闂瓟鍙傛暟
+     * @param plugin   鎻掍欢
+     * @param <R>      鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>      鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     * @return ChatCompletionResponse
+     */
+    public <R extends PluginParam, T> ChatCompletionResponse chatCompletionWithPlugin(List<Message> messages, PluginAbstract<R, T> plugin) {
+        return chatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_3_5_TURBO_16K_0613.getName(), plugin);
+    }
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     *
+     * @param messages 闂瓟鍙傛暟
+     * @param model    妯″瀷
+     * @param plugin   鎻掍欢
+     * @param <R>      鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>      鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     * @return ChatCompletionResponse
+     */
+    public <R extends PluginParam, T> ChatCompletionResponse chatCompletionWithPlugin(List<Message> messages, String model, PluginAbstract<R, T> plugin) {
+        ChatCompletion chatCompletion = ChatCompletion.builder().messages(messages).model(model).build();
+        return this.chatCompletionWithPlugin(chatCompletion, plugin);
+    }
+
+    /**
      * 绠�鏄撶増 璇煶缈昏瘧锛氱洰鍓嶄粎鏀寔缈昏瘧涓鸿嫳鏂�
      *
      * @param file 璇煶鏂囦欢 鏈�澶ф敮鎸�25MB mp3, mp4, mpeg, mpga, m4a, wav, webm
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java
index cad1bbb..e799202 100644
--- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java
@@ -3,6 +3,7 @@
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.ContentType;
+import cn.hutool.json.JSONUtil;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.reactivex.Single;
 import lombok.Getter;
@@ -17,10 +18,7 @@
 import org.ruoyi.common.chat.entity.billing.BillingUsage;
 import org.ruoyi.common.chat.entity.billing.KeyInfo;
 import org.ruoyi.common.chat.entity.billing.Subscription;
-import org.ruoyi.common.chat.entity.chat.BaseChatCompletion;
-import org.ruoyi.common.chat.entity.chat.ChatCompletion;
-import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
-import org.ruoyi.common.chat.entity.chat.ChatCompletionWithPicture;
+import org.ruoyi.common.chat.entity.chat.*;
 import org.ruoyi.common.chat.entity.embeddings.Embedding;
 import org.ruoyi.common.chat.entity.embeddings.EmbeddingResponse;
 import org.ruoyi.common.chat.entity.files.UploadFileResponse;
@@ -29,6 +27,7 @@
 import org.ruoyi.common.chat.entity.models.Model;
 import org.ruoyi.common.chat.entity.models.ModelResponse;
 import org.ruoyi.common.chat.entity.whisper.Transcriptions;
+import org.ruoyi.common.chat.entity.whisper.Translations;
 import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
 import org.ruoyi.common.chat.openai.exception.CommonError;
 import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
@@ -36,6 +35,10 @@
 import org.ruoyi.common.chat.openai.interceptor.DefaultOpenAiAuthInterceptor;
 import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
 import org.ruoyi.common.chat.openai.interceptor.OpenAiAuthInterceptor;
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+import org.ruoyi.common.chat.openai.plugin.PluginParam;
+import org.ruoyi.common.chat.sse.DefaultPluginListener;
+import org.ruoyi.common.chat.sse.PluginListener;
 import org.ruoyi.common.core.exception.base.BaseException;
 import org.jetbrains.annotations.NotNull;
 import retrofit2.Call;
@@ -184,6 +187,93 @@
         } catch (Exception e) {
             log.error("璇锋眰鍙傛暟瑙f瀽寮傚父锛歿}", e.getMessage());
         }
+    }
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param chatCompletion            鍙傛暟
+     * @param eventSourceListener       sse鐩戝惉鍣�
+     * @param pluginEventSourceListener 鎻掍欢sse鐩戝惉鍣紝鏀堕泦function call杩斿洖淇℃伅
+     * @param plugin                    鎻掍欢
+     * @param <R>                       鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>                       鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     */
+    public <R extends PluginParam, T> void streamChatCompletionWithPlugin(ChatCompletion chatCompletion, EventSourceListener eventSourceListener, PluginListener pluginEventSourceListener, PluginAbstract<R, T> plugin) {
+        if (Objects.isNull(plugin)) {
+            this.streamChatCompletion(chatCompletion, eventSourceListener);
+            return;
+        }
+        if (CollectionUtil.isEmpty(chatCompletion.getMessages())) {
+            throw new BaseException(CommonError.MESSAGE_NOT_NUL.msg());
+        }
+        Functions functions = Functions.builder()
+                .name(plugin.getFunction())
+                .description(plugin.getDescription())
+                .parameters(plugin.getParameters())
+                .build();
+        //娌℃湁鍊硷紝璁剧疆榛樿鍊�
+        if (Objects.isNull(chatCompletion.getFunctionCall())) {
+            chatCompletion.setFunctionCall("auto");
+        }
+        //tip: 瑕嗙洊鑷繁璁剧疆鐨刦unctions鍙傛暟锛屼娇鐢╬lugin鏋勯�犵殑functions
+        chatCompletion.setFunctions(Collections.singletonList(functions));
+        //璋冪敤OpenAi
+        if (Objects.isNull(pluginEventSourceListener)) {
+            pluginEventSourceListener = new DefaultPluginListener(this, eventSourceListener, plugin, chatCompletion);
+        }
+        this.streamChatCompletion(chatCompletion, pluginEventSourceListener);
+    }
+
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param chatCompletion      鍙傛暟
+     * @param eventSourceListener sse鐩戝惉鍣�
+     * @param plugin              鎻掍欢
+     * @param <R>                 鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>                 鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     */
+    public <R extends PluginParam, T> void streamChatCompletionWithPlugin(ChatCompletion chatCompletion, EventSourceListener eventSourceListener, PluginAbstract<R, T> plugin) {
+        PluginListener pluginEventSourceListener = new DefaultPluginListener(this, eventSourceListener, plugin, chatCompletion);
+        this.streamChatCompletionWithPlugin(chatCompletion, eventSourceListener, pluginEventSourceListener, plugin);
+    }
+
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param messages            闂瓟鍙傛暟
+     * @param eventSourceListener sse鐩戝惉鍣�
+     * @param plugin              鎻掍欢
+     * @param <R>                 鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>                 鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     */
+    public <R extends PluginParam, T> void streamChatCompletionWithPlugin(List<Message> messages, EventSourceListener eventSourceListener, PluginAbstract<R, T> plugin) {
+        this.streamChatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_3_5_TURBO_16K_0613.getName(), eventSourceListener, plugin);
+    }
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     *
+     * @param messages            闂瓟鍙傛暟
+     * @param model               妯″瀷
+     * @param eventSourceListener eventSourceListener
+     * @param plugin              鎻掍欢
+     * @param <R>                 鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>                 鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     */
+    public <R extends PluginParam, T> void streamChatCompletionWithPlugin(List<Message> messages, String model, EventSourceListener eventSourceListener, PluginAbstract<R, T> plugin) {
+        ChatCompletion chatCompletion = ChatCompletion.builder().messages(messages).model(model).build();
+        this.streamChatCompletionWithPlugin(chatCompletion, eventSourceListener, plugin);
     }
 
 
@@ -418,6 +508,95 @@
         }
     }
 
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param chatCompletion 鍙傛暟
+     * @param plugin         鎻掍欢
+     * @param <R>            鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>            鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     * @return ChatCompletionResponse
+     */
+    public <R extends PluginParam, T> ChatCompletionResponse chatCompletionWithPlugin(ChatCompletion chatCompletion, PluginAbstract<R, T> plugin) {
+        if (Objects.isNull(plugin)) {
+            return this.chatCompletion(chatCompletion);
+        }
+        if (CollectionUtil.isEmpty(chatCompletion.getMessages())) {
+            throw new BaseException(CommonError.MESSAGE_NOT_NUL.msg());
+        }
+        List<Message> messages = chatCompletion.getMessages();
+        Functions functions = Functions.builder()
+                .name(plugin.getFunction())
+                .description(plugin.getDescription())
+                .parameters(plugin.getParameters())
+                .build();
+        //娌℃湁鍊硷紝璁剧疆榛樿鍊�
+        if (Objects.isNull(chatCompletion.getFunctionCall())) {
+            chatCompletion.setFunctionCall("auto");
+        }
+        //tip: 瑕嗙洊鑷繁璁剧疆鐨刦unctions鍙傛暟锛屼娇鐢╬lugin鏋勯�犵殑functions
+        chatCompletion.setFunctions(Collections.singletonList(functions));
+        //璋冪敤OpenAi
+        ChatCompletionResponse functionCallChatCompletionResponse = this.chatCompletion(chatCompletion);
+        ChatChoice chatChoice = functionCallChatCompletionResponse.getChoices().get(0);
+        log.debug("鏋勯�犵殑鏂规硶鍊硷細{}", chatChoice.getMessage().getFunctionCall());
+
+        R realFunctionParam = (R) JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), plugin.getR());
+        T tq = plugin.func(realFunctionParam);
+
+        FunctionCall functionCall = FunctionCall.builder()
+                .arguments(chatChoice.getMessage().getFunctionCall().getArguments())
+                .name(plugin.getFunction())
+                .build();
+        messages.add(Message.builder().role(Message.Role.ASSISTANT).content("function_call").functionCall(functionCall).build());
+        messages.add(Message.builder().role(Message.Role.FUNCTION).name(plugin.getFunction()).content(plugin.content(tq)).build());
+        //璁剧疆绗簩娆★紝璇锋眰鐨勫弬鏁�
+        chatCompletion.setFunctionCall(null);
+        chatCompletion.setFunctions(null);
+
+        ChatCompletionResponse chatCompletionResponse = this.chatCompletion(chatCompletion);
+        log.debug("鑷畾涔夌殑鏂规硶杩斿洖鍊硷細{}", chatCompletionResponse.getChoices());
+        return chatCompletionResponse;
+    }
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     * 榛樿妯″瀷锛欳hatCompletion.Model.GPT_3_5_TURBO_16K_0613
+     *
+     * @param messages 闂瓟鍙傛暟
+     * @param plugin   鎻掍欢
+     * @param <R>      鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>      鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     * @return ChatCompletionResponse
+     */
+    public <R extends PluginParam, T> ChatCompletionResponse chatCompletionWithPlugin(List<Message> messages, PluginAbstract<R, T> plugin) {
+        return chatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_3_5_TURBO_16K_0613.getName(), plugin);
+    }
+
+    /**
+     * 鎻掍欢闂瓟绠�鏄撶増
+     * 榛樿鍙杕essages鏈�鍚庝竴涓厓绱犳瀯寤烘彃浠跺璇�
+     *
+     * @param messages 闂瓟鍙傛暟
+     * @param model    妯″瀷
+     * @param plugin   鎻掍欢
+     * @param <R>      鎻掍欢鑷畾涔夊嚱鏁扮殑璇锋眰鍊�
+     * @param <T>      鎻掍欢鑷畾涔夊嚱鏁扮殑杩斿洖鍊�
+     * @return ChatCompletionResponse
+     */
+    public <R extends PluginParam, T> ChatCompletionResponse chatCompletionWithPlugin(List<Message> messages, String model, PluginAbstract<R, T> plugin) {
+        ChatCompletion chatCompletion = ChatCompletion.builder().messages(messages).model(model).build();
+        return this.chatCompletionWithPlugin(chatCompletion, plugin);
+    }
+
+
+
+
+
+
 
     /**
      * 鏋勯��
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java
index 9bd65b8..e59e9cd 100644
--- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/exception/CommonError.java
@@ -7,6 +7,7 @@
  *  2023-02-11
  */
 public enum CommonError implements IError {
+    MESSAGE_NOT_NUL(500, "Message 涓嶈兘涓虹┖"),
     API_KEYS_NOT_NUL(500, "API KEYS 涓嶈兘涓虹┖"),
     NO_ACTIVE_API_KEYS(500, "娌℃湁鍙敤鐨凙PI KEYS"),
     SYS_ERROR(500, "绯荤粺绻佸繖"),
@@ -19,8 +20,8 @@
     ;
 
 
-    private int code;
-    private String msg;
+    private final int code;
+    private final String msg;
 
     CommonError(int code, String msg) {
         this.code = code;
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginAbstract.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginAbstract.java
new file mode 100644
index 0000000..ddb2045
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginAbstract.java
@@ -0,0 +1,88 @@
+package org.ruoyi.common.chat.openai.plugin;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.json.JSONObject;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.ruoyi.common.chat.entity.chat.Parameters;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Data
+@AllArgsConstructor
+public abstract class PluginAbstract<R extends PluginParam, T> {
+
+    private Class<?> R;
+
+    private String name;
+
+    private String function;
+
+    private String description;
+
+    private List<Arg> args;
+
+    private List<String> required;
+
+    private Parameters parameters;
+
+    public PluginAbstract(Class<?> r) {
+        R = r;
+    }
+
+    public void setRequired(List<String> required) {
+        if (CollectionUtil.isEmpty(required)) {
+            this.required = this.getArgs().stream().filter(e -> e.isRequired()).map(Arg::getName).collect(Collectors.toList());
+            return;
+        }
+        this.required = required;
+    }
+
+    private void setRequired() {
+        if (CollectionUtil.isEmpty(required)) {
+            this.required = this.getArgs().stream().filter(e -> e.isRequired()).map(Arg::getName).collect(Collectors.toList());
+        }
+    }
+
+    private void setParameters() {
+        JSONObject properties = new JSONObject();
+        args.forEach(e -> {
+            JSONObject param = new JSONObject();
+            param.putOpt("type", e.getType());
+            param.putOpt("enum", e.getEnumDictValue());
+            param.putOpt("description", e.getDescription());
+            properties.putOpt(e.getName(), param);
+        });
+        this.parameters = Parameters.builder()
+                .type("object")
+                .properties(properties)
+                .required(this.getRequired())
+                .build();
+    }
+
+    public void setArgs(List<Arg> args) {
+        this.args = args;
+        setRequired();
+        setParameters();
+    }
+
+    @Data
+    public static class Arg {
+        private String name;
+        private String type;
+        private String description;
+        @JsonIgnore
+        private boolean enumDict;
+        @JsonProperty("enum")
+        private List<String> enumDictValue;
+        @JsonIgnore
+        private boolean required;
+    }
+
+    public abstract T func(R args);
+
+    public abstract String content(T t);
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginParam.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginParam.java
new file mode 100644
index 0000000..c5a5909
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/plugin/PluginParam.java
@@ -0,0 +1,7 @@
+package org.ruoyi.common.chat.openai.plugin;
+
+import lombok.Data;
+
+@Data
+public class PluginParam {
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java
new file mode 100644
index 0000000..428d6e0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java
@@ -0,0 +1,36 @@
+package org.ruoyi.common.chat.plugin;
+
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+
+import java.io.IOException;
+
+public class CmdPlugin extends PluginAbstract<CmdReq, CmdResp> {
+
+    public CmdPlugin(Class<?> r) {
+        super(r);
+    }
+
+    @Override
+    public CmdResp func(CmdReq args) {
+        try {
+            if("璁$畻鍣�".equals(args.getCmd())){
+                Runtime.getRuntime().exec("calc");
+            }else if("璁颁簨鏈�".equals(args.getCmd())){
+                Runtime.getRuntime().exec("notepad");
+            }else if("鍛戒护琛�".equals(args.getCmd())){
+                String [] cmd={"cmd","/C","start copy exel exe2"};
+                Runtime.getRuntime().exec(cmd);
+            }
+        } catch (IOException e) {
+           throw new RuntimeException("鎸囦护鎵ц澶辫触");
+        }
+        CmdResp resp = new CmdResp();
+        resp.setResult(args.getCmd()+"鎸囦护鎵ц鎴愬姛!");
+        return resp;
+    }
+
+    @Override
+    public String content(CmdResp resp) {
+        return resp.getResult();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdReq.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdReq.java
new file mode 100644
index 0000000..a275150
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdReq.java
@@ -0,0 +1,13 @@
+package org.ruoyi.common.chat.plugin;
+
+
+import lombok.Data;
+import org.ruoyi.common.chat.openai.plugin.PluginParam;
+
+@Data
+public class CmdReq extends PluginParam {
+    /**
+     * 鎸囦护
+     */
+    private String cmd;
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdResp.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdResp.java
new file mode 100644
index 0000000..4e10139
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdResp.java
@@ -0,0 +1,12 @@
+package org.ruoyi.common.chat.plugin;
+
+import lombok.Data;
+
+@Data
+public class CmdResp {
+
+    /**
+     * 杩斿洖缁撴灉
+     */
+    private String result;
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlPlugin.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlPlugin.java
new file mode 100644
index 0000000..a40734d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlPlugin.java
@@ -0,0 +1,88 @@
+package org.ruoyi.common.chat.plugin;
+
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+
+import java.sql.*;
+
+/**
+ * @author ageer
+ */
+public class SqlPlugin extends PluginAbstract<SqlReq, SqlResp> {
+
+    public SqlPlugin(Class<?> r) {
+        super(r);
+    }
+
+
+
+    @Override
+    public SqlResp func(SqlReq args) {
+        SqlResp resp = new SqlResp();
+        resp.setUserBalance(getBalance(args.getUsername()));
+        return resp;
+    }
+
+    @Override
+    public String content(SqlResp resp) {
+        return  "鐢ㄦ埛浣欓锛�"+resp.getUserBalance();
+    }
+
+
+    public String getBalance(String userName) {
+        // MySQL 8.0 浠ヤ笅鐗堟湰 - JDBC 椹卞姩鍚嶅強鏁版嵁搴� URL
+        String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
+        String DB_URL = "jdbc:mysql://43.139.70.230:3306/ry-vue";
+        // 鏁版嵁搴撶殑鐢ㄦ埛鍚嶄笌瀵嗙爜锛岄渶瑕佹牴鎹嚜宸辩殑璁剧疆
+        String USER = "ry-vue";
+        String PASS = "BXZiGsY35K523Xfx";
+        Connection conn = null;
+        Statement stmt = null;
+        String balance = "0.1";
+
+        try{
+            // 娉ㄥ唽 JDBC 椹卞姩
+            Class.forName(JDBC_DRIVER);
+
+            // 鎵撳紑閾炬帴
+            System.out.println("杩炴帴鏁版嵁搴�...");
+            conn = DriverManager.getConnection(DB_URL,USER,PASS);
+
+            // 鎵ц鏌ヨ
+            System.out.println(" 瀹炰緥鍖朣tatement瀵硅薄...");
+            stmt = conn.createStatement();
+            String sql;
+            sql = "SELECT user_balance FROM sys_user where user_name ='" + userName + "'";
+            ResultSet rs = stmt.executeQuery(sql);
+            // 灞曞紑缁撴灉闆嗘暟鎹簱
+            while(rs.next()){
+                // 閫氳繃瀛楁妫�绱�
+                balance = rs.getString("user_balance");
+                // 杈撳嚭鏁版嵁
+                System.out.print("浣欓: " + balance);
+                System.out.print("\n");
+            }
+            // 瀹屾垚鍚庡叧闂�
+            rs.close();
+            stmt.close();
+            conn.close();
+        }catch(SQLException se){
+            // 澶勭悊 JDBC 閿欒
+            se.printStackTrace();
+        }catch(Exception e){
+            // 澶勭悊 Class.forName 閿欒
+            e.printStackTrace();
+        }finally{
+            // 鍏抽棴璧勬簮
+            try{
+                if(stmt!=null) stmt.close();
+            }catch(SQLException se2){
+            }// 浠�涔堥兘涓嶅仛
+            try{
+                if(conn!=null) conn.close();
+            }catch(SQLException se){
+                se.printStackTrace();
+            }
+        }
+       return balance;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlReq.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlReq.java
new file mode 100644
index 0000000..481ba72
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlReq.java
@@ -0,0 +1,13 @@
+package org.ruoyi.common.chat.plugin;
+
+
+import lombok.Data;
+import org.ruoyi.common.chat.openai.plugin.PluginParam;
+
+@Data
+public class SqlReq extends PluginParam {
+    /**
+     * 鐢ㄦ埛鍚嶇О
+     */
+    private String username;
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlResp.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlResp.java
new file mode 100644
index 0000000..b84b555
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/SqlResp.java
@@ -0,0 +1,12 @@
+package org.ruoyi.common.chat.plugin;
+
+import lombok.Data;
+
+@Data
+public class SqlResp {
+
+    /**
+     * 鐢ㄦ埛浣欓
+     */
+    private String userBalance;
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java
new file mode 100644
index 0000000..c05fa19
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java
@@ -0,0 +1,56 @@
+package org.ruoyi.common.chat.sse;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okhttp3.sse.EventSource;
+import okhttp3.sse.EventSourceListener;
+
+import java.util.Objects;
+
+/**
+ * 鎻忚堪锛� sse
+ *
+ * @author https:www.unfbx.com
+ * 2023-02-28
+ */
+@Slf4j
+public class ConsoleEventSourceListener extends EventSourceListener {
+
+    @Override
+    public void onOpen(EventSource eventSource, Response response) {
+        log.info("OpenAI寤虹珛sse杩炴帴...");
+    }
+
+    @Override
+    public void onEvent(EventSource eventSource, String id, String type, String data) {
+        log.info("OpenAI杩斿洖鏁版嵁锛歿}", data);
+        if ("[DONE]".equals(data)) {
+            log.info("OpenAI杩斿洖鏁版嵁缁撴潫浜�");
+            return;
+        }
+    }
+
+    @Override
+    public void onClosed(EventSource eventSource) {
+        log.info("OpenAI鍏抽棴sse杩炴帴...");
+    }
+
+    @SneakyThrows
+    @Override
+    public void onFailure(EventSource eventSource, Throwable t, Response response) {
+        if(Objects.isNull(response)){
+            log.error("OpenAI  sse杩炴帴寮傚父:{}", t);
+            eventSource.cancel();
+            return;
+        }
+        ResponseBody body = response.body();
+        if (Objects.nonNull(body)) {
+            log.error("OpenAI  sse杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", body.string(), t);
+        } else {
+            log.error("OpenAI  sse杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", response, t);
+        }
+        eventSource.cancel();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/DefaultPluginListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/DefaultPluginListener.java
new file mode 100644
index 0000000..ab6fcf1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/DefaultPluginListener.java
@@ -0,0 +1,22 @@
+package org.ruoyi.common.chat.sse;
+
+import lombok.extern.slf4j.Slf4j;
+
+import okhttp3.sse.EventSourceListener;
+import org.ruoyi.common.chat.entity.chat.ChatCompletion;
+import org.ruoyi.common.chat.openai.OpenAiStreamClient;
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+
+/**
+ * 鎻忚堪锛� 鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣�
+ *
+ * @author https:www.unfbx.com
+ * 2023-08-18
+ */
+@Slf4j
+public class DefaultPluginListener extends PluginListener {
+
+    public DefaultPluginListener(OpenAiStreamClient client, EventSourceListener eventSourceListener, PluginAbstract plugin, ChatCompletion chatCompletion) {
+        super(client, eventSourceListener, plugin, chatCompletion);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java
new file mode 100644
index 0000000..6701a25
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java
@@ -0,0 +1,126 @@
+package org.ruoyi.common.chat.sse;
+
+import cn.hutool.json.JSONUtil;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okhttp3.sse.EventSource;
+import okhttp3.sse.EventSourceListener;
+import org.jetbrains.annotations.NotNull;
+import org.ruoyi.common.chat.constant.OpenAIConst;
+import org.ruoyi.common.chat.entity.chat.ChatCompletion;
+import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
+import org.ruoyi.common.chat.entity.chat.FunctionCall;
+import org.ruoyi.common.chat.entity.chat.Message;
+import org.ruoyi.common.chat.openai.OpenAiStreamClient;
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+import org.ruoyi.common.chat.openai.plugin.PluginParam;
+
+import java.util.Objects;
+
+/**
+ * 鎻忚堪锛� 鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣�
+ *
+ * @author https:www.unfbx.com
+ * 2023-08-18
+ */
+@Slf4j
+public abstract class PluginListener<R extends PluginParam, T> extends EventSourceListener {
+    /**
+     * openAi鎻掍欢鏋勫缓鐨勫弬鏁�
+     */
+    private String arguments = "";
+
+    /**
+     * 鑾峰彇openAi鎻掍欢鏋勫缓鐨勫弬鏁�
+     *
+     * @return arguments
+     */
+    private String getArguments() {
+        return this.arguments;
+    }
+
+    private OpenAiStreamClient client;
+    private EventSourceListener eventSourceListener;
+    private PluginAbstract<R, T> plugin;
+    private ChatCompletion chatCompletion;
+
+    /**
+     * 鏋勯�犳柟娉曞繀澶囧洓涓厓绱�
+     *
+     * @param client              OpenAiStreamClient
+     * @param eventSourceListener 澶勭悊鐪熷疄绗簩娆se璇锋眰鐨勮嚜瀹氫箟鐩戝惉
+     * @param plugin              鎻掍欢淇℃伅
+     * @param chatCompletion      璇锋眰鍙傛暟
+     */
+    public PluginListener(OpenAiStreamClient client, EventSourceListener eventSourceListener, PluginAbstract<R, T> plugin, ChatCompletion chatCompletion) {
+        this.client = client;
+        this.eventSourceListener = eventSourceListener;
+        this.plugin = plugin;
+        this.chatCompletion = chatCompletion;
+    }
+
+    /**
+     * sse鍏抽棴鍚庡鐞嗭紝绗簩娆¤姹傛柟娉�
+     */
+    public void onClosedAfter() {
+        log.debug("鏋勯�犵殑鏂规硶鍊硷細{}", getArguments());
+
+        R realFunctionParam = (R) JSONUtil.toBean(getArguments(), plugin.getR());
+        T tq = plugin.func(realFunctionParam);
+
+        FunctionCall functionCall = FunctionCall.builder()
+                .arguments(getArguments())
+                .name(plugin.getFunction())
+                .build();
+        chatCompletion.getMessages().add(Message.builder().role(Message.Role.ASSISTANT).content("function_call").functionCall(functionCall).build());
+        chatCompletion.getMessages().add(Message.builder().role(Message.Role.FUNCTION).name(plugin.getFunction()).content(plugin.content(tq)).build());
+        //璁剧疆绗簩娆★紝璇锋眰鐨勫弬鏁�
+        chatCompletion.setFunctionCall(null);
+        chatCompletion.setFunctions(null);
+        client.streamChatCompletion(chatCompletion, eventSourceListener);
+    }
+
+    @SneakyThrows
+    @Override
+    public final void onEvent(@NotNull EventSource eventSource, String id, String type, String data) {
+        log.debug("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣ㄨ繑鍥炴暟鎹細{}", data);
+        if ("[DONE]".equals(data)) {
+            log.debug("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣ㄨ繑鍥炴暟鎹粨鏉熶簡");
+            return;
+        }
+        ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
+        if (Objects.nonNull(chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall())) {
+            this.arguments += chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall().getArguments();
+        }
+    }
+
+    @Override
+    public final void onClosed(EventSource eventSource) {
+        log.debug("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣ㄥ叧闂繛鎺�...");
+        this.onClosedAfter();
+    }
+
+    @Override
+    public void onOpen(EventSource eventSource, Response response) {
+        log.debug("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣ㄥ缓绔嬭繛鎺�...");
+    }
+
+    @SneakyThrows
+    @Override
+    public void onFailure(EventSource eventSource, Throwable t, Response response) {
+        if (Objects.isNull(response)) {
+            log.error("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣�,杩炴帴寮傚父:{}", t);
+            eventSource.cancel();
+            return;
+        }
+        ResponseBody body = response.body();
+        if (Objects.nonNull(body)) {
+            log.error("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣�,杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", body.string(), t);
+        } else {
+            log.error("鎻掍欢寮�鍙戣繑鍥炰俊鎭敹闆唖se鐩戝惉鍣�,杩炴帴寮傚父data锛歿}锛屽紓甯革細{}", response, t);
+        }
+        eventSource.cancel();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java
index 5e354b7..152a418 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/service/UserService.java
@@ -15,4 +15,11 @@
      */
     String selectUserNameById(Long userId);
 
+    /**
+     * 閫氳繃鐢ㄦ埛鍚嶇О鏌ヨ浣欓
+     *
+     * @param userName
+     * @return
+     */
+    String selectUserByName(String userName);
 }
diff --git a/ruoyi-modules/ruoyi-knowledge/pom.xml b/ruoyi-modules/ruoyi-knowledge/pom.xml
index dcba199..46576f8 100644
--- a/ruoyi-modules/ruoyi-knowledge/pom.xml
+++ b/ruoyi-modules/ruoyi-knowledge/pom.xml
@@ -83,6 +83,9 @@
             <scope>runtime</scope>
             <optional>true</optional>
         </dependency>
+
+
+
         <dependency>
             <groupId>com.mysql</groupId>
             <artifactId>mysql-connector-j</artifactId>
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/MilvusVectorStore.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/MilvusVectorStore.java
index 82cd896..c083ef4 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/MilvusVectorStore.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/MilvusVectorStore.java
@@ -41,13 +41,13 @@
     @Resource
     private ConfigService configService;
 
-    // @PostConstruct
+    @PostConstruct
     public void loadConfig() {
         this.dimension = Integer.parseInt(configService.getConfigValue("milvus", "dimension"));
         this.collectionName = configService.getConfigValue("milvus", "collection");
     }
 
-    //@PostConstruct
+    @PostConstruct
     public void init(){
         String milvusHost = configService.getConfigValue("milvus", "host");
         String milvausPort = configService.getConfigValue("milvus", "port");
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStore.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStore.java
index 6852cfd..6be022d 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStore.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStore.java
@@ -6,11 +6,16 @@
  * 鍚戦噺瀛樺偍
  */
 public interface VectorStore {
-    void storeEmbeddings(List<String> chunkList,List<List<Double>> vectorList, String kid, String docId,List<String> fidList);
-    void removeByDocId(String kid,String docId);
+
+    void storeEmbeddings(List<String> chunkList, List<List<Double>> vectorList, String kid, String docId, List<String> fidList);
+
+    void removeByDocId(String kid, String docId);
+
     void removeByKid(String kid);
-    List<String> nearest(List<Double> queryVector,String kid);
-    List<String> nearest(String query,String kid);
+
+    List<String> nearest(List<Double> queryVector, String kid);
+
+    List<String> nearest(String query, String kid);
 
     void newSchema(String kid);
 
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreFactory.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreFactory.java
index 1cfb6b3..5acf28b 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreFactory.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreFactory.java
@@ -1,18 +1,21 @@
 package org.ruoyi.knowledge.chain.vectorstore;
 
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
+import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
+import org.ruoyi.knowledge.mapper.KnowledgeInfoMapper;
 import org.springframework.stereotype.Component;
 
 @Component
 @Slf4j
 public class VectorStoreFactory {
 
-    private final String type = "weaviate";
-
     private final WeaviateVectorStore weaviateVectorStore;
 
     private final MilvusVectorStore milvusVectorStore;
+
+    @Resource
+    private KnowledgeInfoMapper knowledgeInfoMapper;
 
     public VectorStoreFactory(WeaviateVectorStore weaviateVectorStore, MilvusVectorStore milvusVectorStore) {
         this.weaviateVectorStore = weaviateVectorStore;
@@ -20,13 +23,13 @@
     }
 
     public VectorStore getVectorStore(String kid){
-//        if ("weaviate".equals(type)){
-//            return weaviateVectorStore;
-//        }else if ("milvus".equals(type)){
-//            return milvusVectorStore;
-//        }
-//
-//        return null;
-        return weaviateVectorStore;
+        KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoMapper.selectVoById(Long.valueOf(kid));
+        String vectorModel = knowledgeInfoVo.getVector();
+        if ("weaviate".equals(vectorModel)){
+            return weaviateVectorStore;
+        }else if ("milvus".equals(vectorModel)){
+            return milvusVectorStore;
+        }
+        return null;
     }
 }
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreWrapper.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreWrapper.java
index eb6b1f4..e1daa61 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreWrapper.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/chain/vectorstore/VectorStoreWrapper.java
@@ -11,19 +11,20 @@
 @Slf4j
 @Primary
 @AllArgsConstructor
-public class VectorStoreWrapper implements VectorStore{
+public class VectorStoreWrapper implements VectorStore {
 
     private final VectorStoreFactory vectorStoreFactory;
+
     @Override
     public void storeEmbeddings(List<String> chunkList, List<List<Double>> vectorList, String kid, String docId, List<String> fidList) {
         VectorStore vectorStore = vectorStoreFactory.getVectorStore(kid);
-        vectorStore.storeEmbeddings(chunkList, vectorList,  kid,  docId, fidList);
+        vectorStore.storeEmbeddings(chunkList, vectorList, kid, docId, fidList);
     }
 
     @Override
     public void removeByDocId(String kid, String docId) {
         VectorStore vectorStore = vectorStoreFactory.getVectorStore(kid);
-        vectorStore.removeByDocId(kid,docId);
+        vectorStore.removeByDocId(kid, docId);
     }
 
     @Override
@@ -35,7 +36,7 @@
     @Override
     public List<String> nearest(List<Double> queryVector, String kid) {
         VectorStore vectorStore = vectorStoreFactory.getVectorStore(kid);
-        return vectorStore.nearest(queryVector,kid);
+        return vectorStore.nearest(queryVector, kid);
     }
 
     @Override
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/domain/KnowledgeInfo.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/domain/KnowledgeInfo.java
index fb8407d..292bdb0 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/domain/KnowledgeInfo.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/domain/KnowledgeInfo.java
@@ -45,7 +45,7 @@
     private String kname;
 
     /**
-     * 鐭ヨ瘑搴撳悕绉�
+     * 鏄惁鍏紑鐭ヨ瘑搴擄紙0 鍚� 1鏄級
      */
     private String share;
 
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeAttachService.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeAttachService.java
index 0469888..6ed856e 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeAttachService.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeAttachService.java
@@ -49,8 +49,6 @@
 
     /**
      * 鍒犻櫎鐭ヨ瘑闄勪欢
-     *
-     * @return
      */
-    void removeKnowledgeAttach(String kid);
+    void removeKnowledgeAttach(String docId);
 }
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeInfoService.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeInfoService.java
index 121f8fb..14e8e97 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeInfoService.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/IKnowledgeInfoService.java
@@ -2,13 +2,10 @@
 
 import org.ruoyi.common.mybatis.core.page.PageQuery;
 import org.ruoyi.common.mybatis.core.page.TableDataInfo;
-import org.ruoyi.knowledge.domain.KnowledgeAttach;
-import org.ruoyi.knowledge.domain.bo.KnowledgeAttachBo;
 import org.ruoyi.knowledge.domain.bo.KnowledgeInfoBo;
 import org.ruoyi.knowledge.domain.req.KnowledgeInfoUploadRequest;
 import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
 
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -39,7 +36,6 @@
      * 淇敼鐭ヨ瘑搴�
      */
     Boolean updateByBo(KnowledgeInfoBo bo);
-
 
     /**
      * 鏂板鐭ヨ瘑搴�
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/EmbeddingServiceImpl.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/EmbeddingServiceImpl.java
index 73503a4..96e568a 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/EmbeddingServiceImpl.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/EmbeddingServiceImpl.java
@@ -40,8 +40,7 @@
 
     @Override
     public List<Double> getQueryVector(String query, String kid) {
-        List<Double> queryVector = vectorization.singleVectorization(query,kid);
-        return queryVector;
+        return vectorization.singleVectorization(query,kid);
     }
 
     @Override
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeAttachServiceImpl.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeAttachServiceImpl.java
index 9bea5dd..8ae5340 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeAttachServiceImpl.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeAttachServiceImpl.java
@@ -126,13 +126,9 @@
     }
 
     @Override
-    public void removeKnowledgeAttach(String kid) {
-        LoginUser loginUser = LoginHelper.getLoginUser();
+    public void removeKnowledgeAttach(String docId) {
         Map<String,Object> map = new HashMap<>();
-        map.put("kid",kid);
-        List<KnowledgeInfoVo> knowledgeInfoList = knowledgeInfoMapper.selectVoByMap(map);
-        knowledgeInfoService.check(knowledgeInfoList);
-
+        map.put("doc_id",docId);
         baseMapper.deleteByMap(map);
         fragmentMapper.deleteByMap(map);
     }
diff --git a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeInfoServiceImpl.java b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeInfoServiceImpl.java
index 3bd9390..23f255b 100644
--- a/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeInfoServiceImpl.java
+++ b/ruoyi-modules/ruoyi-knowledge/src/main/java/org/ruoyi/knowledge/service/impl/KnowledgeInfoServiceImpl.java
@@ -1,14 +1,10 @@
 package org.ruoyi.knowledge.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.RandomUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import io.github.ollama4j.OllamaAPI;
-import io.github.ollama4j.models.chat.OllamaChatMessageRole;
-import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
-import io.github.ollama4j.models.chat.OllamaChatRequestModel;
-import io.github.ollama4j.models.chat.OllamaChatResult;
 import lombok.RequiredArgsConstructor;
 import org.ruoyi.common.core.domain.model.LoginUser;
 import org.ruoyi.common.core.utils.MapstructUtils;
@@ -30,6 +26,7 @@
 import org.ruoyi.knowledge.service.EmbeddingService;
 import org.ruoyi.knowledge.service.IKnowledgeInfoService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
@@ -41,8 +38,8 @@
  * @author Lion Li
  * @date 2024-10-21
  */
-@RequiredArgsConstructor
 @Service
+@RequiredArgsConstructor
 public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
 
     private final KnowledgeInfoMapper baseMapper;
@@ -110,9 +107,8 @@
         //TODO 鍋氫竴浜涙暟鎹牎楠�,濡傚敮涓�绾︽潫
     }
 
-
-
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void saveOne(KnowledgeInfoBo bo) {
         KnowledgeInfo knowledgeInfo = MapstructUtils.convert(bo, KnowledgeInfo.class);
         if (StringUtils.isBlank(bo.getKid())){
@@ -122,7 +118,7 @@
                 knowledgeInfo.setUid(LoginHelper.getLoginUser().getUserId());
             }
             baseMapper.insert(knowledgeInfo);
-            embeddingService.createSchema(kid);
+            embeddingService.createSchema(String.valueOf(knowledgeInfo.getId()));
         }else {
             baseMapper.updateById(knowledgeInfo);
         }
@@ -148,19 +144,23 @@
         try {
             content = resourceLoader.getContent(file.getInputStream());
             chunkList = resourceLoader.getChunkList(content, kid);
-            for (int i = 0; i < chunkList.size(); i++) {
-                String fid = RandomUtil.randomString(16);
-                fids.add(fid);
-                KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
-                knowledgeFragment.setKid(kid);
-                knowledgeFragment.setDocId(docId);
-                knowledgeFragment.setFid(fid);
-                knowledgeFragment.setIdx(i);
-               // String text = convertTextBlockToPretrainData(chunkList.get(i));
-                knowledgeFragment.setContent(chunkList.get(i));
-                knowledgeFragment.setCreateTime(new Date());
-                fragmentMapper.insert(knowledgeFragment);
+            List<KnowledgeFragment> knowledgeFragmentList = new ArrayList<>();
+            if (CollUtil.isNotEmpty(chunkList)) {
+                for (int i = 0; i < chunkList.size(); i++) {
+                    String fid = RandomUtil.randomString(16);
+                    fids.add(fid);
+                    KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
+                    knowledgeFragment.setKid(kid);
+                    knowledgeFragment.setDocId(docId);
+                    knowledgeFragment.setFid(fid);
+                    knowledgeFragment.setIdx(i);
+                    // String text = convertTextBlockToPretrainData(chunkList.get(i));
+                    knowledgeFragment.setContent(chunkList.get(i));
+                    knowledgeFragment.setCreateTime(new Date());
+                    knowledgeFragmentList.add(knowledgeFragment);
+                }
             }
+            fragmentMapper.insertBatch(knowledgeFragmentList);
         } catch (IOException e) {
             e.printStackTrace();
         }
@@ -171,19 +171,21 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void removeKnowledge(String id) {
-
         Map<String,Object> map = new HashMap<>();
         map.put("kid",id);
         List<KnowledgeInfoVo> knowledgeInfoList = baseMapper.selectVoByMap(map);
         check(knowledgeInfoList);
-        // 鍒犻櫎鐭ヨ瘑搴�
-        baseMapper.deleteByMap(map);
+        // 鍒犻櫎鍚戦噺搴撲俊鎭�
+        knowledgeInfoList.forEach(knowledgeInfoVo -> {
+            embeddingService.removeByKid(String.valueOf(knowledgeInfoVo.getId()));
+        });
         // 鍒犻櫎闄勪欢鍜岀煡璇嗙墖娈�
         fragmentMapper.deleteByMap(map);
         attachMapper.deleteByMap(map);
-        // 鍒犻櫎鍚戦噺搴撲俊鎭�
-        embeddingService.removeByKid(id);
+        // 鍒犻櫎鐭ヨ瘑搴�
+        baseMapper.deleteByMap(map);
     }
 
     @Override
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java
index aee284d..c6d7e3c 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/controller/system/SysProfileController.java
@@ -12,6 +12,7 @@
 import org.ruoyi.common.satoken.utils.LoginHelper;
 import org.ruoyi.common.web.core.BaseController;
 import org.ruoyi.system.domain.bo.SysUserBo;
+import org.ruoyi.system.domain.bo.SysUserPasswordBo;
 import org.ruoyi.system.domain.bo.SysUserProfileBo;
 import org.ruoyi.system.domain.vo.AvatarVo;
 import org.ruoyi.system.domain.vo.ProfileVo;
@@ -75,23 +76,20 @@
 
     /**
      * 閲嶇疆瀵嗙爜
-     *
-     * @param newPassword 鏃у瘑鐮�
-     * @param oldPassword 鏂板瘑鐮�
      */
     @Log(title = "涓汉淇℃伅", businessType = BusinessType.UPDATE)
     @PutMapping("/updatePwd")
-    public R<Void> updatePwd(String oldPassword, String newPassword) {
+    public R<Void> updatePwd(@Validated @RequestBody SysUserPasswordBo bo) {
         SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
         String password = user.getPassword();
-        if (!BCrypt.checkpw(oldPassword, password)) {
+        if (!BCrypt.checkpw(bo.getOldPassword(), password)) {
             return R.fail("淇敼瀵嗙爜澶辫触锛屾棫瀵嗙爜閿欒");
         }
-        if (BCrypt.checkpw(newPassword, password)) {
+        if (BCrypt.checkpw(bo.getNewPassword(), password)) {
             return R.fail("鏂板瘑鐮佷笉鑳戒笌鏃у瘑鐮佺浉鍚�");
         }
 
-        if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(newPassword)) > 0) {
+        if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())) > 0) {
             return R.ok();
         }
         return R.fail("淇敼瀵嗙爜寮傚父锛岃鑱旂郴绠$悊鍛�");
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserPasswordBo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserPasswordBo.java
new file mode 100644
index 0000000..493ba0a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/bo/SysUserPasswordBo.java
@@ -0,0 +1,32 @@
+package org.ruoyi.system.domain.bo;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 鎻忚堪锛氱敤鎴峰瘑鐮佷慨鏀筨o
+ *
+ * @author ageerle@163.com
+ * date 2025/3/9
+ */
+@Data
+public class SysUserPasswordBo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鏃у瘑鐮�
+     */
+    @NotBlank(message = "鏃у瘑鐮佷笉鑳戒负绌�")
+    private String oldPassword;
+
+    /**
+     * 鏂板瘑鐮�
+     */
+    @NotBlank(message = "鏂板瘑鐮佷笉鑳戒负绌�")
+    private String newPassword;
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java
index 787cba4..bdf4f34 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/listener/SSEEventSourceListener.java
@@ -65,7 +65,7 @@
     @Override
     public void onEvent(@NotNull EventSource eventSource, String id, String type, String data) {
         try {
-            if (data.equals("[DONE]")) {
+            if ("[DONE]".equals(data)) {
                 //鎴愬姛鍝嶅簲
                 emitter.complete();
                 if(StringUtils.isNotEmpty(modelName)){
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/plugin/WebSearchPlugin.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/plugin/WebSearchPlugin.java
new file mode 100644
index 0000000..63b290a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/plugin/WebSearchPlugin.java
@@ -0,0 +1,212 @@
+package org.ruoyi.system.plugin;
+
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSONObject;
+import lombok.Builder;
+import lombok.Data;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import org.junit.Before;
+import org.junit.Test;
+import org.ruoyi.common.chat.demo.ConsoleEventSourceListenerV3;
+import org.ruoyi.common.chat.entity.chat.ChatCompletion;
+import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
+import org.ruoyi.common.chat.entity.chat.Message;
+import org.ruoyi.common.chat.entity.chat.Parameters;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
+import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
+import org.ruoyi.common.chat.entity.chat.tool.Tools;
+import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
+import org.ruoyi.common.chat.openai.OpenAiClient;
+import org.ruoyi.common.chat.openai.OpenAiStreamClient;
+import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
+import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
+import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
+import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class WebSearchPlugin {
+
+    private OpenAiClient openAiClient;
+    private OpenAiStreamClient openAiStreamClient;
+
+    @Before
+    public void before() {
+        //鍙互涓簄ull
+//        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
+        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
+        //锛侊紒锛侊紒鍗冧竾鍒啀鐢熶骇鎴栬�呮祴璇曠幆澧冩墦寮�BODY绾у埆鏃ュ織锛侊紒锛侊紒
+        //锛侊紒锛佺敓浜ф垨鑰呮祴璇曠幆澧冨缓璁缃负杩欎笁绉嶇骇鍒細NONE,BASIC,HEADERS,锛侊紒锛�
+        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
+        OkHttpClient okHttpClient = new OkHttpClient
+                .Builder()
+//                .proxy(proxy)
+                .addInterceptor(httpLoggingInterceptor)
+                .addInterceptor(new OpenAiResponseInterceptor())
+                .connectTimeout(10, TimeUnit.SECONDS)
+                .writeTimeout(30, TimeUnit.SECONDS)
+                .readTimeout(30, TimeUnit.SECONDS)
+                .build();
+        openAiClient = OpenAiClient.builder()
+                //鏀寔澶歬ey浼犲叆锛岃姹傛椂鍊欓殢鏈洪�夋嫨
+                .apiKey(Arrays.asList("xx"))
+                //鑷畾涔塳ey鐨勮幏鍙栫瓥鐣ワ細榛樿KeyRandomStrategy
+                //.keyStrategy(new KeyRandomStrategy())
+                .keyStrategy(new KeyRandomStrategy())
+                .okHttpClient(okHttpClient)
+                //鑷繁鍋氫簡浠g悊灏变紶浠g悊鍦板潃锛屾病鏈夊彲涓嶄笉浼�,(鍏虫敞鍏紬鍙峰洖澶嶏細openai 锛岃幏鍙栧厤璐圭殑娴嬭瘯浠g悊鍦板潃)
+                .apiHost("https://open.bigmodel.cn/")
+                .build();
+
+        openAiStreamClient = OpenAiStreamClient.builder()
+                //鏀寔澶歬ey浼犲叆锛岃姹傛椂鍊欓殢鏈洪�夋嫨
+                .apiKey(Arrays.asList("xx"))
+                //鑷畾涔塳ey鐨勮幏鍙栫瓥鐣ワ細榛樿KeyRandomStrategy
+                .keyStrategy(new KeyRandomStrategy())
+                .authInterceptor(new DynamicKeyOpenAiAuthInterceptor())
+                .okHttpClient(okHttpClient)
+                //鑷繁鍋氫簡浠g悊灏变紶浠g悊鍦板潃锛屾病鏈夊彲涓嶄笉浼�,(鍏虫敞鍏紬鍙峰洖澶嶏細openai 锛岃幏鍙栧厤璐圭殑娴嬭瘯浠g悊鍦板潃)
+                .apiHost("https://open.bigmodel.cn/")
+                .build();
+    }
+
+    @Test
+    public void test() {
+        Message message = Message.builder().role(Message.Role.USER).content("浠婂ぉ姝︽眽澶╂皵鎬庝箞鏍�").build();
+        ChatCompletion chatCompletion = ChatCompletion
+                .builder()
+                .messages(Collections.singletonList(message))
+//                .tools(Collections.singletonList(tools))
+                .model("web-search-pro")
+                .build();
+        ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
+
+        System.out.printf("chatCompletionResponse=%s\n", JSONUtil.toJsonStr(chatCompletionResponse));
+    }
+
+    @Test
+    public void streamToolsChat() {
+
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch);
+
+        Message message = Message.builder().role(Message.Role.USER).content("缁欐垜杈撳嚭涓�涓暱搴︿负2鐨勪腑鏂囪瘝璇紝骞惰В閲婁笅璇嶈瀵瑰簲鐗╁搧鐨勭敤閫�").build();
+        //灞炴�т竴
+        JSONObject wordLength = new JSONObject();
+        wordLength.put("type", "number");
+        wordLength.put("description", "璇嶈鐨勯暱搴�");
+        //灞炴�т簩
+        JSONObject language = new JSONObject();
+        language.put("type", "string");
+        language.put("enum", Arrays.asList("zh", "en"));
+        language.put("description", "璇█绫诲瀷锛屼緥濡傦細zh浠h〃涓枃銆乪n浠h〃鑻辫");
+        //鍙傛暟
+        JSONObject properties = new JSONObject();
+        properties.put("wordLength", wordLength);
+        properties.put("language", language);
+        Parameters parameters = Parameters.builder()
+                .type("object")
+                .properties(properties)
+                .required(Collections.singletonList("wordLength")).build();
+        Tools tools = Tools.builder()
+                .type(Tools.Type.FUNCTION.getName())
+                .function(ToolsFunction.builder().name("getOneWord").description("鑾峰彇涓�涓寚瀹氶暱搴﹀拰璇█绫诲瀷鐨勮瘝璇�").parameters(parameters).build())
+                .build();
+
+        ChatCompletion chatCompletion = ChatCompletion
+                .builder()
+                .messages(Collections.singletonList(message))
+                .tools(Collections.singletonList(tools))
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+        openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
+
+        try {
+            countDownLatch.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls();
+        WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
+        String oneWord = getOneWord(wordParam);
+
+
+        ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
+        ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
+        //鏋勯�爐ool call
+        Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("鏂规硶鍙傛暟").toolCalls(Collections.singletonList(tc)).build();
+        String content
+                = "{ " +
+                "\"wordLength\": \"3\", " +
+                "\"language\": \"zh\", " +
+                "\"word\": \"" + oneWord + "\"," +
+                "\"鐢ㄩ�擻": [\"鐩存帴鍚僜", \"鍋氭矙鎷塡", \"鍞崠\"]" +
+                "}";
+        Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
+        List<Message> messageList = Arrays.asList(message, message2, message3);
+        ChatCompletion chatCompletionV2 = ChatCompletion
+                .builder()
+                .messages(messageList)
+                .model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
+                .build();
+
+
+        CountDownLatch countDownLatch1 = new CountDownLatch(1);
+        openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch));
+        try {
+            countDownLatch1.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        try {
+            countDownLatch1.await();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    @Data
+    @Builder
+    static class WordParam {
+        private int wordLength;
+        @Builder.Default
+        private String language = "zh";
+    }
+
+
+    /**
+     * 鑾峰彇涓�涓瘝璇�(鏍规嵁璇█鍜屽瓧绗﹂暱搴︽煡璇�)
+     * @param wordParam
+     * @return
+     */
+    public String getOneWord(WordParam wordParam) {
+
+        List<String> zh = Arrays.asList("澶ч钑�", "鍝堝瘑鐡�", "鑻规灉");
+        List<String> en = Arrays.asList("apple", "banana", "cantaloupe");
+        if (wordParam.getLanguage().equals("zh")) {
+            for (String e : zh) {
+                if (e.length() == wordParam.getWordLength()) {
+                    return e;
+                }
+            }
+        }
+        if (wordParam.getLanguage().equals("en")) {
+            for (String e : en) {
+                if (e.length() == wordParam.getWordLength()) {
+                    return e;
+                }
+            }
+        }
+        return "瑗跨摐";
+    }
+
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java
index e2f02b5..a2cfeff 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SseServiceImpl.java
@@ -3,16 +3,11 @@
 import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import com.alibaba.fastjson.JSONObject;
-import com.azure.ai.openai.OpenAIClient;
-import com.azure.ai.openai.OpenAIClientBuilder;
-import com.azure.ai.openai.models.*;
-import com.azure.core.credential.AzureKeyCredential;
 import io.github.ollama4j.OllamaAPI;
 import io.github.ollama4j.models.chat.OllamaChatMessageRole;
 import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
 import io.github.ollama4j.models.chat.OllamaChatRequestModel;
 import io.github.ollama4j.models.generate.OllamaStreamHandler;
-import io.github.ollama4j.utils.Options;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -33,6 +28,12 @@
 import org.ruoyi.common.chat.entity.images.ResponseFormat;
 import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
 import org.ruoyi.common.chat.openai.OpenAiStreamClient;
+import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
+import org.ruoyi.common.chat.plugin.CmdPlugin;
+import org.ruoyi.common.chat.plugin.CmdReq;
+import org.ruoyi.common.chat.plugin.SqlPlugin;
+import org.ruoyi.common.chat.plugin.SqlReq;
+import org.ruoyi.common.chat.sse.ConsoleEventSourceListener;
 import org.ruoyi.common.chat.utils.TikTokensUtil;
 import org.ruoyi.common.core.domain.model.LoginUser;
 import org.ruoyi.common.core.exception.base.BaseException;
@@ -43,12 +44,10 @@
 import org.ruoyi.system.domain.bo.SysModelBo;
 import org.ruoyi.system.domain.request.translation.TranslationRequest;
 import org.ruoyi.system.domain.vo.SysModelVo;
-import org.ruoyi.system.domain.vo.SysUserVo;
 import org.ruoyi.system.listener.SSEEventSourceListener;
 import org.ruoyi.system.service.*;
 import org.springframework.core.io.InputStreamResource;
 import org.springframework.core.io.Resource;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
@@ -63,10 +62,10 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
-import io.github.ollama4j.utils.OptionsBuilder;
 
 @Service
 @Slf4j
@@ -89,9 +88,6 @@
 
     static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
 
-    private final ISysPackagePlanService sysPackagePlanService;
-
-
     @Override
     public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) {
         openAiStreamClient = chatConfig.getOpenAiStreamClient();
@@ -101,12 +97,7 @@
         List<Message> messages = chatRequest.getMessages();
         try {
             if (StpUtil.isLogin()) {
-                SysUserVo sysUserVo = userService.selectUserById(getUserId());
-//                if (!checkModel(sysUserVo.getUserPlan(), chatRequest.getModel())) {
-//                    throw new BaseException("褰撳墠濂楅涓嶆敮鎸佹妯″瀷!");
-//                }
                 LocalCache.CACHE.put("userId", getUserId());
-
                 Object content = messages.get(messages.size() - 1).getContent();
 
                 String chatString = "";
@@ -161,36 +152,23 @@
                     }
                 }
             }
-
-//            else {
-//
-//                    // 鍒濆璇锋眰娆℃暟
-//                    int number = 1;
-//                    // 鑾峰彇璇锋眰IP
-//                    String realIp = getClientIpAddress(request);
-//                    // 鏍规嵁IP鑾峰彇娆℃暟
-//                    Integer requestNumber = RedisUtils.getCacheObject(realIp);
-//                    if (requestNumber == null) {
-//                        // 璁板綍ip浣跨敤娆℃暟
-//                        RedisUtils.setCacheObject(realIp, number);
-//                    } else {
-//                        String configValue = configService.getConfigValue("mail", "free");
-//                        if (requestNumber > Integer.parseInt(configValue)) {
-//                            throw new BaseException("鍓╀綑娆℃暟涓嶈冻锛岃鍏呭�煎悗浣跨敤");
-//                        }
-//                        RedisUtils.setCacheObject(realIp, requestNumber + 1);
-//                    }
-//
-//            }
-            ChatCompletion completion = ChatCompletion
-                .builder()
-                .messages(messages)
-                .model(chatRequest.getModel())
-                .temperature(chatRequest.getTemperature())
-                .topP(chatRequest.getTop_p())
-                .stream(true)
-                .build();
-            openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
+            if("openCmd".equals(chatRequest.getModel())) {
+                sseEmitter.send(cmdPlugin(messages));
+                sseEmitter.complete();
+            }else if ("sqlPlugin".equals(chatRequest.getModel())){
+                sseEmitter.send(sqlPlugin(messages));
+                sseEmitter.complete();
+            } else {
+                ChatCompletion completion = ChatCompletion
+                        .builder()
+                        .messages(messages)
+                        .model(chatRequest.getModel())
+                        .temperature(chatRequest.getTemperature())
+                        .topP(chatRequest.getTop_p())
+                        .stream(true)
+                        .build();
+                openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
+            }
         } catch (Exception e) {
             String message = e.getMessage();
             sendErrorEvent(sseEmitter, message);
@@ -199,32 +177,51 @@
         return sseEmitter;
     }
 
+    public String cmdPlugin(List<Message> messages) {
+        CmdPlugin plugin = new CmdPlugin(CmdReq.class);
+        // 鎻掍欢鍚嶇О
+        plugin.setName("鍛戒护琛屽伐鍏�");
+        // 鏂规硶鍚嶇О
+        plugin.setFunction("openCmd");
+        // 鏂规硶璇存槑
+        plugin.setDescription("鎻愪緵涓�涓懡浠よ鎸囦护,姣斿<璁颁簨鏈�>,鎸囦护浣跨敤涓枃");
 
-//    /**
-//     * 鏌ュ綋鍓嶇敤鎴锋槸鍚﹀彲浠ヨ皟鐢ㄦ妯″瀷
-//     *
-//     * @param planId
-//     * @return
-//     */
-//    public Boolean checkModel(String planId, String modelName) {
-//        SysPackagePlanBo sysPackagePlanBo = new SysPackagePlanBo();
-//        if (modelName.startsWith("gpt-4-gizmo")) {
-//            modelName = "gpt-4-gizmo";
-//        }
-//        if (StringUtils.isEmpty(planId)) {
-//            sysPackagePlanBo.setName("Visitor");
-//        } else if ("Visitor".equals(planId) || "Free".equals(planId)) {
-//            sysPackagePlanBo.setName(planId);
-//        } else {
-//            // sysPackagePlanBo.setId(Long.valueOf(planId));
-//            return true;
-//        }
-//
-//        SysPackagePlanVo sysPackagePlanVo = sysPackagePlanService.queryList(sysPackagePlanBo).get(0);
-//        // 灏嗗瓧绗︿覆杞崲涓烘暟缁�
-//        String[] array = sysPackagePlanVo.getPlanDetail().split(",");
-//        return Arrays.asList(array).contains(modelName);
-//    }
+        PluginAbstract.Arg arg = new PluginAbstract.Arg();
+        // 鍙傛暟鍚嶇О
+        arg.setName("cmd");
+        // 鍙傛暟璇存槑
+        arg.setDescription("鍛戒护琛屾寚浠�");
+        // 鍙傛暟绫诲瀷
+        arg.setType("string");
+        arg.setRequired(true);
+        plugin.setArgs(Collections.singletonList(arg));
+        //鏈夊洓涓噸杞芥柟娉曪紝閮藉彲浠ヤ娇鐢�
+        ChatCompletionResponse response = openAiStreamClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
+        return response.getChoices().get(0).getMessage().getContent().toString();
+    }
+
+    public String sqlPlugin(List<Message> messages) {
+        SqlPlugin plugin = new SqlPlugin(SqlReq.class);
+        // 鎻掍欢鍚嶇О
+        plugin.setName("鏁版嵁搴撴煡璇㈡彃浠�");
+        // 鏂规硶鍚嶇О
+        plugin.setFunction("sqlPlugin");
+        // 鏂规硶璇存槑
+        plugin.setDescription("鎻愪緵涓�涓敤鎴峰悕绉版煡璇綑棰濅俊鎭�");
+
+        PluginAbstract.Arg arg = new PluginAbstract.Arg();
+        // 鍙傛暟鍚嶇О
+        arg.setName("username");
+        // 鍙傛暟璇存槑
+        arg.setDescription("鐢ㄦ埛鍚嶇О");
+        // 鍙傛暟绫诲瀷
+        arg.setType("string");
+        arg.setRequired(true);
+        plugin.setArgs(Collections.singletonList(arg));
+        //鏈夊洓涓噸杞芥柟娉曪紝閮藉彲浠ヤ娇鐢�
+        ChatCompletionResponse response = openAiStreamClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
+        return response.getChoices().get(0).getMessage().getContent().toString();
+    }
 
     /**
      * 鏍规嵁娆℃暟鎵i櫎浣欓
@@ -295,25 +292,6 @@
 
     @Override
     public String chat(ChatRequest chatRequest, String userId) {
-//        chatService.deductUserBalance(Long.valueOf(userId), 0.01);
-//        // 淇濆瓨娑堟伅璁板綍
-//        ChatMessageBo chatMessageBo = new ChatMessageBo();
-//        chatMessageBo.setUserId(Long.valueOf(userId));
-//        chatMessageBo.setModelName(ChatCompletion.Model.GPT_3_5_TURBO.getName());
-//        chatMessageBo.setContent(chatRequest.getPrompt());
-//        chatMessageBo.setDeductCost(0.01);
-//        chatMessageBo.setTotalTokens(0);
-//        chatMessageService.insertByBo(chatMessageBo);
-//
-//        openAiStreamClient = chatConfig.getOpenAiStreamClient();
-//        Message message = Message.builder().role(Message.Role.USER).content(chatRequest.getPrompt()).build();
-//        ChatCompletion chatCompletion = ChatCompletion
-//            .builder()
-//            .messages(Collections.singletonList(message))
-//            .model(chatRequest.getModel())
-//            .build();
-//        ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
-//        return chatCompletionResponse.getChoices().get(0).getMessage().getContent();
          return  null;
     }
 
@@ -540,7 +518,8 @@
 
     @Override
     public String translation(TranslationRequest translationRequest) {
-
+        // 缈昏瘧妯″瀷鍥哄畾涓篻pt-4o-mini
+        translationRequest.setModel("gpt-4o-mini");
         ChatMessageBo chatMessageBo = new ChatMessageBo();
         chatMessageBo.setUserId(getUserId());
         chatMessageBo.setModelName(translationRequest.getModel());
@@ -557,17 +536,12 @@
             "\n" +
             "璇峰皢鐢ㄦ埛杈撳叆璇嶈缈昏瘧鎴恵" + translationRequest.getTargetLanguage() + "}\n" +
             "\n" +
-            "璁╂垜浠竴姝ヤ竴姝ユ潵鎬濊�僜n" +
             "==绀轰緥杈撳嚭==\n" +
+            "**鍘熸枃** : <杩欓噷鏄剧ず瑕佺炕璇戠殑鍘熸枃淇℃伅>\n" +
             "**缈昏瘧** : <杩欓噷鏄剧ず缈昏瘧鎴愯嫳璇殑缁撴灉>\n" +
-            "\n" +
-            "**閫犲彞** : What's the weather like today? Use the 'Weather Query' plugin to find out instantly! <閫犱竴涓嫳璇彞瀛�>\n" +
-            "\n" +
-            "**鍚屼箟璇�** : Add-on銆丒xtension銆丮odule  <杩欓噷鏄剧ず1-3涓嫳鏂囩殑鍚屼箟璇�>\n" +
-            "\n" +
             "==绀轰緥缁撴潫==\n" +
             "\n" +
-            "娉ㄦ剰锛氳涓ユ牸鎸夌ず渚嬭繘琛岃緭鍑�").build();
+            "娉ㄦ剰锛氳涓ユ牸鎸夌ず渚嬭繘琛岃緭鍑猴紝杩斿洖markdown鏍煎紡").build();
         messageList.add(sysMessage);
         Message message = Message.builder().role(Message.Role.USER).content(translationRequest.getPrompt()).build();
         messageList.add(message);
@@ -646,4 +620,6 @@
         ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
         return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
     }
+
+
 }
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
index 39e51ea..7b895da 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -557,4 +557,11 @@
             .select(SysUser::getUserName).eq(SysUser::getUserId, userId));
         return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserName();
     }
+
+    @Override
+    public String selectUserByName(String userName) {
+        SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+                .eq(SysUser::getUserName, userName));
+        return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserBalance().toString();
+    }
 }
diff --git a/script/sql/update/update20250307.sql b/script/sql/update/update20250307.sql
index ba83d69..65c6d8c 100644
--- a/script/sql/update/update20250307.sql
+++ b/script/sql/update/update20250307.sql
@@ -8,8 +8,10 @@
 ADD COLUMN `vector_model` varchar(50) NULL COMMENT '鍚戦噺妯″瀷' AFTER `vector`;
 
 
-
-INSERT INTO `chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412050, 'weaviate', 'protocol', 'http', '鍗忚', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
-INSERT INTO `chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412051, 'weaviate', 'host', '127.0.0.1:6038', '鍦板潃', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
-INSERT INTO `chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412052, 'weaviate', 'classname', 'LocalKnowledge', '鍒嗙被鍚嶇О', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
-
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412050, 'weaviate', 'protocol', 'http', '鍗忚', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412051, 'weaviate', 'host', '127.0.0.1:6038', '鍦板潃', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412052, 'weaviate', 'classname', 'LocalKnowledge', '鍒嗙被鍚嶇О', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412053, 'milvus', 'host', '127.0.0.1', '鍦板潃', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412054, 'milvus', 'port', '19530', '绔彛', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412055, 'milvus', 'dimension', '1536', '缁村害', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);
+INSERT INTO `ruoyi-ai`.`chat_config` (`id`, `category`, `config_name`, `config_value`, `config_dict`, `create_dept`, `create_time`, `create_by`, `update_by`, `update_time`, `remark`, `version`, `del_flag`, `update_ip`, `tenant_id`) VALUES (1897610056458412056, 'milvus', 'collection', 'LocalKnowledge', '鍒嗙被鍚嶇О', 103, '2025-03-06 21:10:02', '1', '1', '2025-03-06 21:10:31', NULL, NULL, '0', NULL, 0);

--
Gitblit v1.9.3