添加llm其它接口
This commit is contained in:
parent
7614196f5e
commit
53467e8545
@ -2,6 +2,9 @@ package com.knowledge.base.infrastructure.south.llm;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.knowledge.base.infrastructure.cache.iface.UserCacheService;
|
||||
import com.knowledge.base.infrastructure.config.ConstantConfig;
|
||||
import com.knowledge.base.infrastructure.util.HttpHelper;
|
||||
@ -14,12 +17,12 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -90,4 +93,180 @@ public class AnythingLLMServiceImpl implements LLMService {
|
||||
userCacheService.cacheAnythingLLMSlugId(wsName, slug);
|
||||
return StrUtil.isBlank(slug) ? ConstantConfig.DEFAULT_SLUG_ID : slug;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void importObsidianToLLM(String llmToken, List<File> mdFiles, String vaultBasePath) {
|
||||
String url = baseUrl + "/api/ext/obsidian/vault";
|
||||
|
||||
List<Map<String, Object>> fileList = new ArrayList<>();
|
||||
for (File file : mdFiles) {
|
||||
if (!file.getName().endsWith(".md")) continue;
|
||||
try {
|
||||
String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
|
||||
String relativePath = vaultBasePath == null ? file.getName() :
|
||||
file.getAbsolutePath().replace(new File(vaultBasePath).getAbsolutePath(), "")
|
||||
.replaceFirst("^[/\\\\]", "");
|
||||
|
||||
Map<String, Object> fileMap = new HashMap<>();
|
||||
fileMap.put("name", file.getName());
|
||||
fileMap.put("path", relativePath.replace(File.separator, "/"));
|
||||
fileMap.put("content", content);
|
||||
fileList.add(fileMap);
|
||||
} catch (IOException e) {
|
||||
log.warn("读取文件失败: {}", file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + llmToken);
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("files", fileList);
|
||||
|
||||
try {
|
||||
httpHelper.exchange(
|
||||
url,
|
||||
"POST",
|
||||
headers,
|
||||
body,
|
||||
new TypeReference<Map<String, Object>>() {} // 可忽略返回内容
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("上传 Obsidian 文件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> fetchAttachments(String llmToken, String workspaceSlug) {
|
||||
String url = baseUrl + "/api/workspace/" + workspaceSlug;
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + llmToken);
|
||||
|
||||
try {
|
||||
Map<String, Object> response = httpHelper.exchange(
|
||||
url,
|
||||
"GET",
|
||||
headers,
|
||||
null,
|
||||
new TypeReference<Map<String, Object>>() {}
|
||||
);
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
if (response.containsKey("workspace")) {
|
||||
Map workspace = (Map) response.get("workspace");
|
||||
Object documents = workspace.get("documents");
|
||||
if (documents instanceof Iterable) {
|
||||
for (Object docObj : (Iterable) documents) {
|
||||
if (docObj instanceof Map) {
|
||||
Map doc = (Map) docObj;
|
||||
String docpath = (String) doc.get("docpath");
|
||||
String metadataStr = (String) doc.get("metadata");
|
||||
if (docpath != null && metadataStr != null) {
|
||||
try {
|
||||
Map<String, Object> metadata = new ObjectMapper().readValue(metadataStr, new TypeReference<Map<String, Object>>() {});
|
||||
String urlVal = (String) metadata.getOrDefault("url", "");
|
||||
result.put(docpath, urlVal);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析 metadata 失败: {}", metadataStr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("获取工作区附件失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void updatePin(String llmToken, String workspaceSlug, String docPath, boolean pinStatus) {
|
||||
String url = baseUrl + "/api/workspace/" + workspaceSlug + "/update-pin";
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + llmToken);
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("docPath", docPath);
|
||||
body.put("pinStatus", pinStatus);
|
||||
|
||||
try {
|
||||
httpHelper.exchange(
|
||||
url,
|
||||
"POST",
|
||||
headers,
|
||||
body,
|
||||
new TypeReference<Map<String, Object>>() {} // 可忽略返回值,仅验证200
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("设置 pin 状态失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getLocalFileItems(String llmToken, String workspaceSlug, String keyword) {
|
||||
String url = baseUrl + "/api/system/local-files";
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + llmToken);
|
||||
headers.put("Referer", baseUrl + "/workspace/" + workspaceSlug);
|
||||
|
||||
try {
|
||||
Map<String, Object> response = httpHelper.exchange(
|
||||
url,
|
||||
"GET",
|
||||
headers,
|
||||
null,
|
||||
new TypeReference<Map<String, Object>>() {}
|
||||
);
|
||||
|
||||
// 从 localFiles -> items 遍历,查找 name 包含关键词的一级目录
|
||||
if (response.containsKey("localFiles")) {
|
||||
Map localFiles = (Map) response.get("localFiles");
|
||||
Object items = localFiles.get("items");
|
||||
if (items instanceof List) {
|
||||
for (Object folderObj : (List) items) {
|
||||
if (folderObj instanceof Map) {
|
||||
Map folder = (Map) folderObj;
|
||||
String name = (String) folder.get("name");
|
||||
if (name != null && name.contains(keyword)) {
|
||||
Object matchedItems = folder.get("items");
|
||||
if (matchedItems instanceof List) {
|
||||
return (List<Map<String, Object>>) matchedItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Lists.newArrayList();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("获取本地文件失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void updateEmbeddings(String llmToken, String workspaceSlug, List<String> adds, List<String> deletes) {
|
||||
String url = baseUrl + "/api/workspace/" + workspaceSlug + "/update-embeddings";
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Bearer " + llmToken);
|
||||
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("adds", adds);
|
||||
body.put("deletes", deletes);
|
||||
|
||||
try {
|
||||
httpHelper.exchange(
|
||||
url,
|
||||
"POST",
|
||||
headers,
|
||||
body,
|
||||
new TypeReference<Map<String, Object>>() {} // 可忽略返回,仅校验状态
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("调用 update-embeddings 失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -10,5 +10,7 @@ public interface LLMService {
|
||||
void streamAnswer(String llmToken, String question, Map<String, Object> params, WriterAdapter writer) throws Exception;
|
||||
|
||||
boolean supports(String type);
|
||||
|
||||
Map<String, String> fetchAttachments(String llmToken, String workspaceSlug);
|
||||
}
|
||||
|
||||
|
||||
@ -101,6 +101,65 @@ public class HttpHelper {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用 HTTP 请求方法,支持任意 method、headers、请求体,自动反序列化返回
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param method 请求方法,如 GET、POST、PUT、DELETE
|
||||
* @param headers 请求头(可选,可为 null)
|
||||
* @param body 请求体对象(可选,可为 null)
|
||||
* @param responseType 返回类型(例如 Map.class)
|
||||
* @param <T> 泛型类型
|
||||
* @return 反序列化后的对象
|
||||
* @throws IOException 请求失败或 JSON 解析异常
|
||||
*/
|
||||
/**
|
||||
* 通用 HTTP 请求方法,支持任意 method、headers、请求体,支持泛型 TypeReference<T> 返回(Jackson)
|
||||
*/
|
||||
public <T> T exchange(String url, String method, Map<String, String> headers, Object body, TypeReference<T> typeRef) 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 (headers != null) {
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
builder.addHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
switch (method.toUpperCase()) {
|
||||
case "POST":
|
||||
builder.post(requestBody != null ? requestBody : RequestBody.create(new byte[0]));
|
||||
break;
|
||||
case "PUT":
|
||||
builder.put(requestBody != null ? requestBody : RequestBody.create(new byte[0]));
|
||||
break;
|
||||
case "DELETE":
|
||||
builder.delete(requestBody != null ? requestBody : null);
|
||||
break;
|
||||
case "GET":
|
||||
default:
|
||||
builder.get();
|
||||
break;
|
||||
}
|
||||
|
||||
Request request = builder.build();
|
||||
|
||||
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, typeRef);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 执行请求并解析为 Map
|
||||
*/
|
||||
|
||||
@ -2,8 +2,10 @@ package com.knowledge.base.infrastructure.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ -71,5 +73,24 @@ public class LocalFileUtil {
|
||||
|
||||
return targetFilePath;
|
||||
}
|
||||
|
||||
|
||||
public static List<File> listMarkdownFiles(File root) {
|
||||
List<File> result = new ArrayList<>();
|
||||
if (root == null || !root.exists()) return result;
|
||||
|
||||
File[] files = root.listFiles();
|
||||
if (files == null) return result;
|
||||
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
result.addAll(listMarkdownFiles(file));
|
||||
} else if (file.getName().endsWith(".md")) {
|
||||
result.add(file);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
package com.knowledge.base.infrastructure.south.llm;
|
||||
|
||||
import com.knowledge.base.infrastructure.cache.iface.UserCacheService;
|
||||
import com.knowledge.base.infrastructure.util.HttpHelper;
|
||||
import com.knowledge.base.infrastructure.util.http.WriterAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Luke.ye
|
||||
* @date 2025/7/7 06:57
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringBootTest(properties = {
|
||||
"spring.profiles.active=dev-mac"
|
||||
})
|
||||
public class AnythingLLMServiceTest {
|
||||
|
||||
@Autowired
|
||||
private AnythingLLMServiceImpl llmService;
|
||||
|
||||
@Autowired
|
||||
private UserCacheService userCacheService;
|
||||
|
||||
@Autowired
|
||||
private HttpHelper httpHelper;
|
||||
|
||||
private String token;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
try {
|
||||
this.token = llmService.fetchToken("lukeye@6");
|
||||
} catch (Exception e) {
|
||||
log.warn("获取 token 失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportObsidianToLLM() {
|
||||
try {
|
||||
File file1 = new File("src/test/resources/obsidian-test/index.md");
|
||||
File file2 = new File("src/test/resources/obsidian-test/demo.md");
|
||||
|
||||
llmService.importObsidianToLLM(token, Arrays.asList(file1, file2), "src/test/resources/obsidian-test");
|
||||
} catch (Exception e) {
|
||||
log.warn("测试 importObsidianToLLM 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateEmbeddings() {
|
||||
try {
|
||||
String slug = "your-workspace-slug";
|
||||
List<String> adds = Collections.singletonList("public/xx.md");
|
||||
List<String> deletes = Arrays.asList();
|
||||
|
||||
llmService.updateEmbeddings(token, slug, adds, deletes);
|
||||
} catch (Exception e) {
|
||||
log.warn("测试 updateEmbeddings 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdatePin() {
|
||||
try {
|
||||
String slug = "your-workspace-slug";
|
||||
String path = "public/xx.md";
|
||||
|
||||
llmService.updatePin(token, slug, path, true);
|
||||
} catch (Exception e) {
|
||||
log.warn("测试 updatePin 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetchAttachments() {
|
||||
try {
|
||||
String slug = "your-workspace-slug";
|
||||
Map<String, String> attachments = llmService.fetchAttachments(token, slug);
|
||||
attachments.forEach((k, v) -> log.info("Attachment: {} -> {}", k, v));
|
||||
} catch (Exception e) {
|
||||
log.warn("测试 fetchAttachments 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalFileItems() {
|
||||
try {
|
||||
String slug = "your-workspace-slug";
|
||||
List<Map<String, Object>> result = llmService.getLocalFileItems(token, slug, "导出");
|
||||
log.info("本地文件项:{}", result);
|
||||
} catch (Exception e) {
|
||||
log.warn("测试 getLocalFileItems 异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamAnswer() {
|
||||
try {
|
||||
WriterAdapter writer = line -> log.info("回答流:{}", line);
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("wsName", "部门知识库");
|
||||
llmService.streamAnswer(token, "介绍一下黄金圈法则", params, writer);
|
||||
} catch (Exception e) {
|
||||
log.warn("测试 streamAnswer 异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
src/test/resources/obsidian-test/demo.md
Normal file
21
src/test/resources/obsidian-test/demo.md
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
修订记录
|
||||
|
||||
| 版本 | 时间 | 修订人 | 改动点 |
|
||||
|:-----:|:----------:|:-------:|:---------------------------------:|
|
||||
| 1.0.0 | 2025-06-05 | Luke.Ye | 添加修订记录(新增token校验接口) |
|
||||
| 1.0.1 | 2025-06-06 | Luke.Ye | 重构登录Token校验逻辑, |
|
||||
| 1.0.2 | 2025-06-09 | Luke.Ye | 添加用户角色相关逻辑,重构代码 |
|
||||
| 1.0.3 | 2025-06-16 | Luke.Ye | 新增文档上传至对象存储接口 |
|
||||
| 1.0.4 | 2025-06-17 | Luke.Ye | 上传文档记录入库 |
|
||||
| 1.0.5 | 2025-06-19 | Luke.Ye | 文档导入ES逻辑优化 |
|
||||
| 1.0.6 | 2025-06-20 | Luke.Ye | 完成对象存储删除接口,优化部分代码 |
|
||||
| 1.0.7 | 2025-06-22 | Luke.Ye | 导入文档类型新增支持ppt & txt |
|
||||
| 1.1.0 | 2025-06-22 | Luke.Ye | 完成llm接口迁移至后端 |
|
||||
| 1.1.1 | 2025-06-26 | Luke.Ye | 提供删除ES数据的接口(ob仓库监听发生删除事件,则同步删除ES) |
|
||||
|
||||
|
||||
|
||||
## 开发顺序
|
||||
|
||||
PO ---> Mapper ---> iface ---> persistence
|
||||
39
src/test/resources/obsidian-test/index.md
Normal file
39
src/test/resources/obsidian-test/index.md
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
|
||||
### 提示词权重
|
||||
提示词权重,多个可以级联,权重可以低,但不能过高
|
||||
- `[]`:0.9,最多可以套3层,最多0.729倍
|
||||
- `()`:1.1,最多可以套3层,最多1.331倍。也可以直接将*权重(范围需要控制在0.3~1.5)* 放在提示词后面,例如:`(garden:1.5)`,
|
||||
- `{}`:1.05,最多可以套3层,最多1.15倍
|
||||
- `<>`:调用Lora,
|
||||
|
||||
|
||||
### 提示词顺序
|
||||
越靠前,权重越大
|
||||
|
||||
No1:画质词/画风词,比如 `4k, masterpiece,high quality, highly detailed`
|
||||
No2:主体,比如 `1girl, blue dress`
|
||||
No3:环境/场景/构图,比如 `garden, white background`
|
||||
No4:lora
|
||||
|
||||
### 提示词污染 & 融合
|
||||
污染:不同的提示词(尤其是颜色)会相互渗透,要使用break来隔开提示词,
|
||||
|
||||
融合:`1gril AND cat` 或者 `1girl_cat` 或者 `[1gril|cat]` ,猫女
|
||||
|
||||
### 画的时间
|
||||
|
||||
`{1girl:garden:0.7}`:70%的时间画girl,后面30%画花园
|
||||
|
||||
### 起手式
|
||||
|
||||
正向:4k、8k、masterpiece
|
||||
|
||||
反向:blur(模糊)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 图生图
|
||||
重绘幅度:建议 `0.4~0.7`
|
||||
Loading…
x
Reference in New Issue
Block a user