添加llm其它接口

This commit is contained in:
luke 2025-07-07 07:08:01 +08:00
parent 7614196f5e
commit 53467e8545
7 changed files with 442 additions and 4 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -101,6 +101,65 @@ public class HttpHelper {
return builder.build();
}
/**
* 通用 HTTP 请求方法支持任意 methodheaders请求体自动反序列化返回
*
* @param url 请求地址
* @param method 请求方法 GETPOSTPUTDELETE
* @param headers 请求头可选可为 null
* @param body 请求体对象可选可为 null
* @param responseType 返回类型例如 Map.class
* @param <T> 泛型类型
* @return 反序列化后的对象
* @throws IOException 请求失败或 JSON 解析异常
*/
/**
* 通用 HTTP 请求方法支持任意 methodheaders请求体支持泛型 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
*/

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View 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

View 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`
No4lora
### 提示词污染 & 融合
污染不同的提示词尤其是颜色会相互渗透要使用break来隔开提示词
融合:`1gril AND cat` 或者 `1girl_cat` 或者 `[1gril|cat]` ,猫女
### 画的时间
`{1girl:garden:0.7}`70%的时间画girl后面30%画花园
### 起手式
正向4k、8k、masterpiece
反向blur模糊
## 图生图
重绘幅度:建议 `0.4~0.7`