完成llm接口迁移至后端
This commit is contained in:
parent
ff8a2c5089
commit
647ef43655
@ -10,7 +10,8 @@
|
|||||||
| 1.0.4 | 2025-06-17 | Luke.Ye | 上传文档记录入库 |
|
| 1.0.4 | 2025-06-17 | Luke.Ye | 上传文档记录入库 |
|
||||||
| 1.0.5 | 2025-06-19 | Luke.Ye | 文档导入ES逻辑优化 |
|
| 1.0.5 | 2025-06-19 | Luke.Ye | 文档导入ES逻辑优化 |
|
||||||
| 1.0.6 | 2025-06-20 | Luke.Ye | 完成对象存储删除接口,优化部分代码 |
|
| 1.0.6 | 2025-06-20 | Luke.Ye | 完成对象存储删除接口,优化部分代码 |
|
||||||
| 1.1.0 | 2025-06-22 | Luke.Ye | 导入文档类型新增支持ppt & txt |
|
| 1.0.7 | 2025-06-22 | Luke.Ye | 导入文档类型新增支持ppt & txt |
|
||||||
|
| 1.1.0 | 2025-06-22 | Luke.Ye | 完成llm接口迁移至后端 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
pom.xml
19
pom.xml
@ -47,6 +47,7 @@
|
|||||||
<org.bouncycastle.version>1.70</org.bouncycastle.version>
|
<org.bouncycastle.version>1.70</org.bouncycastle.version>
|
||||||
|
|
||||||
<minio.version>8.5.3</minio.version>
|
<minio.version>8.5.3</minio.version>
|
||||||
|
<okhttp.version>4.12.0</okhttp.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -225,6 +226,19 @@
|
|||||||
<groupId>io.minio</groupId>
|
<groupId>io.minio</groupId>
|
||||||
<artifactId>minio</artifactId>
|
<artifactId>minio</artifactId>
|
||||||
<version>${minio.version}</version>
|
<version>${minio.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>${okhttp.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
@ -381,6 +395,11 @@
|
|||||||
<artifactId>minio</artifactId>
|
<artifactId>minio</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.knowledge.base.infrastructure.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke.ye
|
||||||
|
* @date 2025/6/22 08:15
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "llm.remote")
|
||||||
|
public class LLmConfig {
|
||||||
|
/**
|
||||||
|
* 远程LLM服务基础地址,如:https://llm.wisdompulse.cn
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
package com.knowledge.base.infrastructure.north.controller;
|
||||||
|
|
||||||
|
import com.knowledge.base.infrastructure.util.HttpHelper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.knowledge.base.infrastructure.config.LLmConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luke.ye
|
||||||
|
* @date 2025/6/22 08:08
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/llm")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LLMController {
|
||||||
|
|
||||||
|
private final HttpHelper okHttpHelper;
|
||||||
|
private final LLmConfig llmConfig;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /token
|
||||||
|
* 请求:{ password: xxx }
|
||||||
|
* 返回:{ token: xxx }
|
||||||
|
*/
|
||||||
|
@PostMapping("/token")
|
||||||
|
public Map<String, Object> getToken(@RequestBody Map<String, Object> body) throws Exception {
|
||||||
|
String url = llmConfig.getBaseUrl() + "/api/token";
|
||||||
|
return okHttpHelper.postJson(url, body, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /workspaces
|
||||||
|
* Header: Authorization: Bearer xxx
|
||||||
|
* 返回:{ workspaces: [...] }
|
||||||
|
*/
|
||||||
|
@GetMapping("/workspaces")
|
||||||
|
public Map<String, Object> getWorkspaces(@RequestHeader("Authorization") String token) throws Exception {
|
||||||
|
String url = llmConfig.getBaseUrl() + "/api/workspaces";
|
||||||
|
return okHttpHelper.get(url, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /workspace/{slug}/stream-chat
|
||||||
|
* Header: Authorization
|
||||||
|
* Body: { message: "...", attachments: [...] }
|
||||||
|
* 返回:text/event-stream
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/workspace/{slug}/stream-chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
|
public ResponseBodyEmitter chatStream(@PathVariable String slug,
|
||||||
|
@RequestHeader("Authorization") String token,
|
||||||
|
@RequestBody Map<String, Object> body) {
|
||||||
|
String url = llmConfig.getBaseUrl() + "/api/workspace/" + slug + "/stream-chat";
|
||||||
|
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try (Response resp = okHttpHelper.postStream(url, body, token);
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(resp.body().byteStream(), StandardCharsets.UTF_8))) {
|
||||||
|
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
emitter.send(line + "\n");
|
||||||
|
}
|
||||||
|
emitter.complete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("LLM 流式回答异常", e);
|
||||||
|
try {
|
||||||
|
emitter.send("data: {\"textResponse\":\"LLM服务异常\"}\n\n");
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
emitter.completeWithError(e);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
return emitter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package com.knowledge.base.infrastructure.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import okhttp3.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OkHttp封装的 HTTP 客户端工具类(支持 JSON POST、GET、Token注入、泛型反序列化、流式请求)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class HttpHelper {
|
||||||
|
|
||||||
|
private final OkHttpClient client;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public HttpHelper() {
|
||||||
|
this.client = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(20, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
this.objectMapper = new ObjectMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 POST JSON 请求,自动反序列化为 Map
|
||||||
|
*/
|
||||||
|
public Map<String, Object> postJson(String url, Map<String, Object> body, String token) throws IOException {
|
||||||
|
Request request = buildJsonRequest(url, body, token, "POST");
|
||||||
|
return executeJson(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送 GET 请求,自动反序列化为 Map
|
||||||
|
*/
|
||||||
|
public Map<String, Object> get(String url, String token) throws IOException {
|
||||||
|
Request request = buildJsonRequest(url, null, token, "GET");
|
||||||
|
return executeJson(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式 POST 请求,返回 Response 实例,调用方负责关闭 response.body().close()
|
||||||
|
*/
|
||||||
|
public Response postStream(String url, Map<String, Object> body, String token) throws IOException {
|
||||||
|
Request request = buildJsonRequest(url, body, token, "POST");
|
||||||
|
return client.newCall(request).execute(); // 返回后调用方处理 body()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 JSON 类型的 Request 请求
|
||||||
|
*/
|
||||||
|
private Request buildJsonRequest(String url, Map<String, Object> body, String token, String method) throws IOException {
|
||||||
|
RequestBody requestBody = null;
|
||||||
|
if (body != null) {
|
||||||
|
String json = objectMapper.writeValueAsString(body);
|
||||||
|
requestBody = RequestBody.create(json, MediaType.get("application/json; charset=utf-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Request.Builder builder = new Request.Builder().url(url);
|
||||||
|
if (token != null && !token.isBlank()) {
|
||||||
|
builder.header("Authorization", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (method.toUpperCase()) {
|
||||||
|
case "POST":
|
||||||
|
builder.post(requestBody != null ? requestBody : RequestBody.create(new byte[0]));
|
||||||
|
break;
|
||||||
|
case "GET":
|
||||||
|
builder.get();
|
||||||
|
break;
|
||||||
|
case "PUT":
|
||||||
|
builder.put(requestBody);
|
||||||
|
break;
|
||||||
|
case "DELETE":
|
||||||
|
if (requestBody != null) {
|
||||||
|
builder.delete(requestBody);
|
||||||
|
} else {
|
||||||
|
builder.delete();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的请求方法:" + method);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行请求并解析为 Map
|
||||||
|
*/
|
||||||
|
private Map<String, Object> executeJson(Request request) throws IOException {
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("请求失败: " + response.code() + " - " + response.message());
|
||||||
|
}
|
||||||
|
String json = response.body().string();
|
||||||
|
return objectMapper.readValue(json, new TypeReference<>() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -57,3 +57,5 @@ knowledge.base.redis.password= # 可留空
|
|||||||
# Redis 默认缓存时间(分钟)
|
# Redis 默认缓存时间(分钟)
|
||||||
knowledge.base.redis.default-expire-minutes=1440
|
knowledge.base.redis.default-expire-minutes=1440
|
||||||
|
|
||||||
|
# 大模型相关
|
||||||
|
llm.remote.base-url=http://localhost:3001
|
||||||
@ -56,3 +56,6 @@ knowledge.base.redis.default-expire-minutes=1440
|
|||||||
# Spring Boot Redis 自动配置(用于 RedisTemplate 和 Lettuce)
|
# Spring Boot Redis 自动配置(用于 RedisTemplate 和 Lettuce)
|
||||||
spring.redis.host=host.docker.internal
|
spring.redis.host=host.docker.internal
|
||||||
spring.redis.port=6379
|
spring.redis.port=6379
|
||||||
|
|
||||||
|
# 大模型相关
|
||||||
|
llm.remote.base-url=http://host.docker.internal:3001
|
||||||
@ -32,3 +32,6 @@ knowledge.base.redis.enable=false
|
|||||||
object.storage.use-uuid-prefix=true
|
object.storage.use-uuid-prefix=true
|
||||||
object.storage.public-buckets[0]=kbase
|
object.storage.public-buckets[0]=kbase
|
||||||
object.storage.public-buckets[1]=public-media
|
object.storage.public-buckets[1]=public-media
|
||||||
|
|
||||||
|
# 大模型相关
|
||||||
|
llm.remote.base-url=http://llm.wisdompulse.cn
|
||||||
@ -22,5 +22,8 @@ import.schedule.enabled=true
|
|||||||
# 每小时执行一次(可改)
|
# 每小时执行一次(可改)
|
||||||
import.schedule.cron=0 0 * * * *
|
import.schedule.cron=0 0 * * * *
|
||||||
|
|
||||||
|
# 大模型相关
|
||||||
|
llm.remote.base-url=http://llm.wisdompulse.cn
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user