tsurumure 5 meses atrás
pai
commit
46081353cb

+ 22 - 0
src/main/java/com/backendsys/modules/sdk/bocha/controller/BochaController.java

@@ -0,0 +1,22 @@
+package com.backendsys.modules.sdk.bocha.controller;
+
+import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.sdk.bocha.entity.BochaParam;
+import com.backendsys.modules.sdk.bocha.service.BochaService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class BochaController {
+
+    @Autowired
+    private BochaService bochaService;
+
+    @PostMapping("/api/bochat/web-search")
+    public Result webSearch(@Validated @RequestBody BochaParam bochaParam) {
+        return Result.success().put("data", bochaService.WebSearch(bochaParam));
+    }
+}

+ 17 - 0
src/main/java/com/backendsys/modules/sdk/bocha/entity/BochaParam.java

@@ -0,0 +1,17 @@
+package com.backendsys.modules.sdk.bocha.entity;
+
+import lombok.Data;
+
+@Data
+public class BochaParam {
+    private String query;
+    private String freshness = "noLimit";   // (oneDay 一天内, oneWeek 一周内, oneMonth 一个月内, oneYear 一年内, noLimit 不限(默认))
+    private Boolean summary = true;         // 是否显示文本摘要,默认值为 false
+    private Integer count = 10;             // 返回结果的条数,默认值为 10
+    private Integer page = 1;               // 页码,默认值为 1
+
+    public BochaParam(){}
+    public BochaParam(String query) {
+        this.query = query;
+    }
+}

+ 14 - 0
src/main/java/com/backendsys/modules/sdk/bocha/entity/BochaResult.java

@@ -0,0 +1,14 @@
+package com.backendsys.modules.sdk.bocha.entity;
+
+import lombok.Data;
+
+@Data
+public class BochaResult {
+
+    private String name;
+    private String url;
+    private String summary;
+    private String siteIcon;
+    private String siteName;
+
+}

+ 17 - 0
src/main/java/com/backendsys/modules/sdk/bocha/service/BochaService.java

@@ -0,0 +1,17 @@
+package com.backendsys.modules.sdk.bocha.service;
+
+import cn.hutool.json.JSONObject;
+import com.backendsys.modules.sdk.bocha.entity.BochaParam;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.Map;
+
+/**
+ * 博查 (https://open.bochaai.com)
+ */
+public interface BochaService {
+
+    // [博查] Web Search API
+    JsonNode WebSearch(BochaParam bochaParam);
+
+}

+ 103 - 0
src/main/java/com/backendsys/modules/sdk/bocha/service/impl/BochaServiceImpl.java

@@ -0,0 +1,103 @@
+package com.backendsys.modules.sdk.bocha.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONNull;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.backendsys.modules.sdk.bocha.entity.BochaParam;
+import com.backendsys.modules.sdk.bocha.service.BochaService;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.nio.charset.StandardCharsets;
+
+@Service
+public class BochaServiceImpl implements BochaService {
+
+    @Value("${bocha.url}")
+    private String API_URL;
+    @Value("${bocha.api-key}")
+    private String API_KEY;
+
+    /**
+     * [博查] Web Search API
+     * https://bocha-ai.feishu.cn/wiki/RXEOw02rFiwzGSkd9mUcqoeAnNK
+     */
+    @Override
+    public JsonNode WebSearch(BochaParam bochaParam) {
+
+        if (StrUtil.isEmpty(bochaParam.getQuery())) {
+            System.out.println("BochaService WebSearch: query 不能为空");
+            return null;
+        }
+
+        try {
+
+            HttpPost request = new HttpPost(API_URL + "/v1/web-search");
+            request.setHeader("Content-Type", "application/json");
+            request.setHeader("Authorization", "Bearer " + API_KEY);
+
+            ObjectMapper objectMapper = new ObjectMapper();
+            String requestBody = objectMapper.writeValueAsString(bochaParam);
+            request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
+
+            try (
+                CloseableHttpClient client = HttpClients.createDefault();
+                CloseableHttpResponse response = client.execute(request)
+            ) {
+
+                // 获取响应体
+                String responseBody = EntityUtils.toString(response.getEntity());
+                // JSONObject jsonResponse = JSONUtil.parseObj(responseBody);
+
+                // 使用 Jackson 解析 JSON (如果出现 null 返回值,则不能使用 JSONObject)
+                JsonNode rootNode = objectMapper.readTree(responseBody);
+                Integer code = Convert.toInt(rootNode.get("code"));
+                JsonNode dataNode = rootNode.path("data");
+                if (code == 200) {
+                    /*
+                    {
+                        "webPages": {
+                            // 返回值:
+                            "value": [
+                                { name, url,
+                                  snippet,    // 片段
+                                  summary,    // 摘要/总结
+                                  siteIcon,   // 网站图标
+                                  siteName    // 网站名称
+                                }
+                            ]
+                        }
+                    }
+                     */
+                    JsonNode webPagesNode = dataNode.get("webPages");
+                    JsonNode valueNode = webPagesNode.get("value");
+                    return valueNode;
+
+                } else {
+                    return dataNode;
+                }
+
+            } catch (Exception e) {
+                System.out.println("Exception(1): " + e.getMessage());
+                return null;
+            }
+
+        } catch (Exception e) {
+            System.out.println("Exception(2): " + e.getMessage());
+            return null;
+        }
+
+    }
+}

+ 69 - 69
src/main/java/com/backendsys/modules/sdk/deepseek/controller/DeepSeekController.java

@@ -1,69 +1,69 @@
-package com.backendsys.modules.sdk.deepseek.controller;
-
-import com.backendsys.modules.sdk.deepseek.entity.DSParam;
-import com.backendsys.modules.sdk.deepseek.service.DeepSeekClient;
-import com.backendsys.modules.sdk.deepseek.service.impl.DeepSeekClientImpl;
-import com.backendsys.modules.sdk.deepseek.utils.OllamaUtil;
-import com.backendsys.modules.common.config.security.utils.SecurityUtil;
-import com.backendsys.modules.common.utils.Result;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.concurrent.CompletableFuture;
-
-@Validated
-@RestController
-@Tag(name = "DeepSeek")
-public class DeepSeekController {
-
-    @Autowired
-    private OllamaUtil ollamaUtil;
-
-    @Autowired
-    private DeepSeekClient deepSeekClient;
-
-    /**
-     * Deepseek API 开放平台 (https://platform.deepseek.com)
-     * 现有模型:
-     * - deepseek-chat      对话模型
-     * - deepseek-reasoner  推理模型
-     */
-    @Operation(summary = "DS-提问 (API)")
-    @PreAuthorize("@sr.hasPermission('101')")
-    @PostMapping("/api/deepSeek/chat")
-    public Result chat(@Validated @RequestBody DSParam param) {
-
-//        // 创建一个 CompletableFuture 来执行异步任务
-//        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
-//            deepSeekClient.chatCompletion(param.getModel(), param.getPrompt());
-//        });
-        return Result.success().put("data", deepSeekClient.chatCompletion(SecurityUtil.getUserId(), param.getModel(), param.getPrompt(), null, null));
-    }
-
-    /**
-     * Deepseek 本地部署模型:
-     * - deepseek-r1:1.5b
-     * - deepseek-r1:7b
-     */
-    @Operation(summary = "DS-提问 (本地部署)")
-    @PreAuthorize("@sr.hasPermission('101')")
-    @PostMapping("/api/deepSeek/chatLocal")
-    public Result chatLocal(@Validated @RequestBody DSParam param) {
-        return Result.success().put("data", ollamaUtil.chatCompletion(SecurityUtil.getUserId(), param.getModel(), param.getPrompt(), null, null));
-    }
-
-    @Operation(summary = "DS-获得模型列表")
-    @PreAuthorize("@sr.hasPermission('101')")
-    @GetMapping("/api/deepSeek/getModels")
-    public Result getModels() {
-        return Result.success().put("data", deepSeekClient.getModels(SecurityUtil.getUserId()));
-    }
-
-}
+//package com.backendsys.modules.sdk.deepseek.controller;
+//
+//import com.backendsys.modules.sdk.deepseek.entity.DSParam;
+//import com.backendsys.modules.sdk.deepseek.service.DeepSeekClient;
+//import com.backendsys.modules.sdk.deepseek.service.impl.DeepSeekClientImpl;
+//import com.backendsys.modules.sdk.deepseek.utils.OllamaUtil;
+//import com.backendsys.modules.common.config.security.utils.SecurityUtil;
+//import com.backendsys.modules.common.utils.Result;
+//import io.swagger.v3.oas.annotations.Operation;
+//import io.swagger.v3.oas.annotations.tags.Tag;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.security.access.prepost.PreAuthorize;
+//import org.springframework.validation.annotation.Validated;
+//import org.springframework.web.bind.annotation.GetMapping;
+//import org.springframework.web.bind.annotation.PostMapping;
+//import org.springframework.web.bind.annotation.RequestBody;
+//import org.springframework.web.bind.annotation.RestController;
+//
+//import java.util.concurrent.CompletableFuture;
+//
+//@Validated
+//@RestController
+//@Tag(name = "DeepSeek")
+//public class DeepSeekController {
+//
+//    @Autowired
+//    private OllamaUtil ollamaUtil;
+//
+//    @Autowired
+//    private DeepSeekClient deepSeekClient;
+//
+//    /**
+//     * Deepseek API 开放平台 (https://platform.deepseek.com)
+//     * 现有模型:
+//     * - deepseek-chat      对话模型
+//     * - deepseek-reasoner  推理模型
+//     */
+//    @Operation(summary = "DS-提问 (API)")
+//    @PreAuthorize("@sr.hasPermission('101')")
+//    @PostMapping("/api/deepSeek/chat")
+//    public Result chat(@Validated @RequestBody DSParam param) {
+//
+////        // 创建一个 CompletableFuture 来执行异步任务
+////        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+////            deepSeekClient.chatCompletion(param.getModel(), param.getPrompt());
+////        });
+//        return Result.success().put("data", deepSeekClient.chatCompletion(SecurityUtil.getUserId(), param.getModel(), param.getPrompt(), null, null));
+//    }
+//
+//    /**
+//     * Deepseek 本地部署模型:
+//     * - deepseek-r1:1.5b
+//     * - deepseek-r1:7b
+//     */
+//    @Operation(summary = "DS-提问 (本地部署)")
+//    @PreAuthorize("@sr.hasPermission('101')")
+//    @PostMapping("/api/deepSeek/chatLocal")
+//    public Result chatLocal(@Validated @RequestBody DSParam param) {
+//        return Result.success().put("data", ollamaUtil.chatCompletion(SecurityUtil.getUserId(), param.getModel(), param.getPrompt(), null, null));
+//    }
+//
+//    @Operation(summary = "DS-获得模型列表")
+//    @PreAuthorize("@sr.hasPermission('101')")
+//    @GetMapping("/api/deepSeek/getModels")
+//    public Result getModels() {
+//        return Result.success().put("data", deepSeekClient.getModels(SecurityUtil.getUserId()));
+//    }
+//
+//}

+ 0 - 28
src/main/java/com/backendsys/modules/sdk/deepseek/entity/DSChat.java

@@ -1,28 +0,0 @@
-//package com.backendsys.modules.sdk.deepseek.entity;
-//
-//import com.baomidou.mybatisplus.annotation.IdType;
-//import com.baomidou.mybatisplus.annotation.TableId;
-//import com.baomidou.mybatisplus.annotation.TableName;
-//import lombok.Data;
-//
-//@Data
-//@TableName("ds_chat")
-//public class DSChat {
-//
-//    public static interface Detail{}
-//    public static interface Create{}
-//    public static interface Update{}
-//    public static interface Delete{}
-//
-//    @TableId(type = IdType.AUTO)
-//    private Long id;
-//    private String model;
-//    private String history_code;
-//    private Long user_id;
-//    private String user_nickname;
-//    private String role;
-//    private String content;
-//    private String content_type;
-//    private String create_time;
-//
-//}

+ 0 - 25
src/main/java/com/backendsys/modules/sdk/deepseek/entity/DSChatHistory.java

@@ -1,25 +0,0 @@
-//package com.backendsys.modules.sdk.deepseek.entity;
-//
-//import com.baomidou.mybatisplus.annotation.IdType;
-//import com.baomidou.mybatisplus.annotation.TableId;
-//import com.baomidou.mybatisplus.annotation.TableName;
-//import lombok.Data;
-//
-//@Data
-//@TableName("ds_chat_history")
-//public class DSChatHistory {
-//
-//    public static interface Detail{}
-//    public static interface Create{}
-//    public static interface Update{}
-//    public static interface Delete{}
-//
-//    @TableId(type = IdType.AUTO)
-//    private Long id;
-//    private String history_code;
-//    private Long user_id;
-//    private String last_content;
-//    private String create_time;
-//    private String update_time;
-//
-//}

+ 1 - 0
src/main/java/com/backendsys/modules/sdk/deepseek/entity/DSRequest.java

@@ -10,5 +10,6 @@ public class DSRequest {
     private String model;
     private List<DSRequestMessage> messages;
     private Boolean stream;
+    private List<String> context;
 
 }

+ 32 - 2
src/main/java/com/backendsys/modules/sdk/deepseek/utils/OllamaUtil.java

@@ -4,16 +4,21 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.backendsys.modules.ai.chat.entity.Chat;
 import com.backendsys.modules.ai.chat.entity.ChatResult;
 import com.backendsys.modules.ai.chat.entity.ChatSseMessage;
 import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.modules.sdk.bocha.entity.BochaParam;
+import com.backendsys.modules.sdk.bocha.entity.BochaResult;
+import com.backendsys.modules.sdk.bocha.service.BochaService;
 import com.backendsys.modules.sdk.deepseek.entity.DSRequest;
 import com.backendsys.modules.sdk.deepseek.entity.DSRequestMessage;
 import com.backendsys.modules.sse.entity.SseResponse;
 import com.backendsys.modules.sse.entity.SseResponseEnum;
 import com.backendsys.modules.sse.utils.SseUtil;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
@@ -43,10 +48,11 @@ public class OllamaUtil {
     private SseUtil sseUtil;
     @Autowired
     private RedisUtil redisUtil;
+    @Autowired
+    private BochaService bochaService;
 
     @Value("${spring.application.name}")
     private String APPLICATION_NAME;
-
     @Value("${deepseek-r1.domain}")
     private String DOMAIN;
 
@@ -119,8 +125,32 @@ public class OllamaUtil {
                             "content": "写一个简单的 Python 函数,用于计算两个数的和"
                         }
                     ],
-                    "stream": false
+                    "stream": false,
+
+                    // 新增
+                    "context": ["引用1","引用2"]
+                }
+                 */
+
+                JsonNode bochaResult = bochaService.WebSearch(new BochaParam(prompt));
+                List<BochaResult> searchResults = JSONUtil.toList(JSONUtil.parseArray(bochaResult), BochaResult.class);
+
+                /*
+                StringBuilder context = new StringBuilder();
+                for (SearchResult result : searchResults) {
+                    context.append("标题: ").append(result.getTitle()).append("\n");
+                    context.append("摘要: ").append(result.getSnippet()).append("\n");
+                    context.append("链接: ").append(result.getUrl()).append("\n\n");
                 }
+
+                // 将搜索结果作为上下文添加到消息中
+                ChatRequest.Context contextMessage = new ChatRequest.Context("system", context.toString());
+                request.getMessages().add(contextMessage);
+
+                // 调用 Ollama 的 /api/chat 接口
+                RestTemplate restTemplate = new RestTemplate();
+                String ollamaUrl = "http://localhost:11434/api/chat";
+                ChatResponse response = restTemplate.postForObject(ollamaUrl, request, ChatResponse.class);
                  */
 
                 // [Chat] 构建请求体

+ 6 - 0
src/main/resources/application-local.yml

@@ -178,6 +178,12 @@ deepseek-api:
   url: https://api.deepseek.com
   api-key: sk-6078a4ceee5443128ce74a23538dd54e
 
+# 博查
+bocha:
+  url: https://api.bochaai.com
+  api-key: sk-031dc43fb26b41b28414f1c7af8ae707
+
+
 # 微信
 wechat:
   miniprogram:

+ 5 - 0
src/main/resources/application-prod.yml

@@ -167,6 +167,11 @@ deepseek-api:
   url: https://api.deepseek.com
   api-key: sk-6078a4ceee5443128ce74a23538dd54e
 
+# 博查
+bocha:
+  url: https://api.bochaai.com
+  api-key: sk-031dc43fb26b41b28414f1c7af8ae707
+
 # 微信
 wechat:
   miniprogram: