Compare commits
6 Commits
1217d114bd
...
c4d5a7b300
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4d5a7b300 | ||
|
|
4124a8a851 | ||
|
|
6d4dedc3f4 | ||
|
|
b8aea3bdf0 | ||
|
|
d36ad95a85 | ||
|
|
fdec62b56e |
@@ -4,7 +4,7 @@ jars_path=/data/datalake/jars
|
||||
jdk_path=/opt/jdk1.8.0_162/bin/java
|
||||
|
||||
arguments=$@
|
||||
# 手动上传jar包则注释掉这行,更显神通吧反正是
|
||||
# 手动上传jar包则注释掉这行,各显神通吧反正是
|
||||
curl http://AxhEbscwsJDbYMH2:cYxg3b4PtWoVD5SjFayWxtnSVsjzRsg4@132.126.207.124:36800/file/download/service-cli-runner-1.0.0-SNAPSHOT.jar -o ${jars_path}/service-cli-runner.jar
|
||||
${jdk_path} -jar ${jars_path}/service-cli-runner.jar \
|
||||
--spring.profiles.active=b12 \
|
||||
|
||||
@@ -14,6 +14,12 @@ const upload_url = 'http://132.126.207.124:36800'
|
||||
const upload_username = 'AxhEbscwsJDbYMH2'
|
||||
const upload_password = 'cYxg3b4PtWoVD5SjFayWxtnSVsjzRsg4'
|
||||
|
||||
/**
|
||||
* 时间戳转自然语言
|
||||
*
|
||||
* @param timestamp 时间戳
|
||||
* @returns {string} 自然语言描述的时间
|
||||
*/
|
||||
const millisecondToString = (timestamp) => {
|
||||
const totalSeconds = Math.floor(parseFloat(timestamp) / 1000)
|
||||
if (isNaN(totalSeconds) || totalSeconds < 0) {
|
||||
@@ -91,8 +97,8 @@ const upload = async (file_path) => {
|
||||
throw response
|
||||
}
|
||||
console.log(`✅ Finished upload ${file_path} (${millisecondToString((new Date().getTime()) - start)})`)
|
||||
console.log(`📘 Uploaded ${fileSize(fs.statSync(file_path).size)}`)
|
||||
console.log(`📘 MD5 ${md5file.sync(file_path)}`)
|
||||
console.log(`📘 Uploaded ${fileSize(fs.statSync(file_path).size)}`)
|
||||
console.log(`📘 MD5 ${md5file.sync(file_path)}`)
|
||||
fs.rmSync(file_path)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
maven_setting=/Users/lanyuanxiaoyao/.m2/settings-nas.xml
|
||||
|
||||
build_profile=b2b12
|
||||
|
||||
upload_username=AxhEbscwsJDbYMH2
|
||||
upload_password=cYxg3b4PtWoVD5SjFayWxtnSVsjzRsg4
|
||||
upload_url=http://$upload_username:$upload_password@132.126.207.124:36800
|
||||
|
||||
root_path=$(dirname $(cd $(dirname $0);pwd))
|
||||
|
||||
function upload() {
|
||||
source_file_path=$(realpath $1)
|
||||
file_name=$(basename $source_file_path)
|
||||
echo "↪ Source md5: $(md5sum $source_file_path | awk '{print $1}')"
|
||||
echo "↪ Uploading $source_file_path"
|
||||
curl $upload_url/file/upload/$file_name -T $source_file_path
|
||||
echo "↪ Upload ytp success"
|
||||
echo "↪ Download: curl $upload_url/file/download/$file_name -o $file_name"
|
||||
echo "↪ Delete source"
|
||||
rm $source_file_path
|
||||
}
|
||||
|
||||
function joining {
|
||||
local d=${1-} f=${2-}
|
||||
if shift 2; then
|
||||
printf %s "$f" "${@/#/$d}"
|
||||
fi
|
||||
}
|
||||
|
||||
function deploy_root() {
|
||||
mvn deploy -N -D skipTests -s $maven_setting
|
||||
}
|
||||
|
||||
function deploy() {
|
||||
mvn -pl $(joining , $@) clean deploy -D skipTests -s $maven_setting
|
||||
}
|
||||
|
||||
function package() {
|
||||
mvn -pl $(joining , $@) clean package -D skipTests -P $build_profile -s $maven_setting
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import {fs} from 'zx'
|
||||
|
||||
let stats = fs.statSync('/Users/lanyuanxiaoyao/Project/IdeaProjects/hudi-service/service-api');
|
||||
console.log(stats.ctime)
|
||||
console.log(stats.atime)
|
||||
console.log(stats.mtime)
|
||||
10
pom.xml
10
pom.xml
@@ -417,6 +417,16 @@
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-invoker-plugin</artifactId>
|
||||
<version>3.9.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<spring-boot.version>3.4.3</spring-boot.version>
|
||||
<spring-cloud.version>2024.0.1</spring-cloud.version>
|
||||
<spring-ai.version>1.0.0</spring-ai.version>
|
||||
<solon-ai.version>3.3.1</solon-ai.version>
|
||||
<eclipse-collections.version>11.1.0</eclipse-collections.version>
|
||||
<curator.version>5.1.0</curator.version>
|
||||
<hutool.version>5.8.27</hutool.version>
|
||||
@@ -136,6 +137,16 @@
|
||||
<artifactId>liteflow-spring-boot-starter</artifactId>
|
||||
<version>2.13.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-ai</artifactId>
|
||||
<version>${solon-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-ai-dialect-openai</artifactId>
|
||||
<version>${solon-ai.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
<groupId>com.lanyuanxiaoyao</groupId>
|
||||
<artifactId>service-ai-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||
|
||||
@@ -3,9 +3,10 @@ package com.lanyuanxiaoyao.service.ai.chat.controller;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lanyuanxiaoyao.service.ai.chat.entity.MessageVO;
|
||||
import com.lanyuanxiaoyao.service.ai.chat.tools.DatetimeTools;
|
||||
import com.lanyuanxiaoyao.service.forest.service.KnowledgeService;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.collections.api.list.ImmutableList;
|
||||
import org.slf4j.Logger;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
@RequestMapping("chat")
|
||||
public class ChatController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChatController.class);
|
||||
private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String ROLE_ASSISTANT = "assistant";
|
||||
private static final String ROLE_USER = "user";
|
||||
|
||||
@@ -48,26 +50,29 @@ public class ChatController {
|
||||
}
|
||||
|
||||
private ChatClient.ChatClientRequestSpec buildRequest(Long knowledgeId, ImmutableList<MessageVO> messages) {
|
||||
var systemPromptBuilder = new StringBuilder();
|
||||
systemPromptBuilder.append("""
|
||||
你是一名专业的AI运维助手,负责“Hudi数据同步服务平台”的运维工作;
|
||||
你将会友好地帮助用户解答关于该平台运维工作的问题,你会尽可能通过各种方式获取知识和数据来解答;
|
||||
对于无法通过已有知识回答的问题,你会提示用户你无法解答该问题,而不是虚构不存在的数据或答案;
|
||||
对于与该平台无关的问题,你会委婉地拒绝用户,并提示无法回答;
|
||||
你将始终在中文语境下进行对话。
|
||||
""");
|
||||
var builder = StrUtil.builder()
|
||||
.append(StrUtil.format("""
|
||||
你是一名专业的AI运维助手,负责“Hudi数据同步服务平台”的运维工作;
|
||||
你将会友好地帮助用户解答关于该平台运维工作的问题,你会尽可能通过各种方式获取知识和数据来解答;
|
||||
对于无法通过已有知识回答的问题,你会提示用户你无法解答该问题,而不是虚构不存在的数据或答案;
|
||||
对于与该平台无关的问题,你会委婉地拒绝用户,并提示无法回答;
|
||||
你将始终在中文语境下进行对话。
|
||||
当前时间为:{}
|
||||
|
||||
""", LocalDateTime.now().format(formatter)));
|
||||
if (ObjectUtil.isNotNull(knowledgeId)) {
|
||||
var vo = messages.select(message -> StrUtil.equals(message.getRole(), "user")).getLastOptional().orElseThrow();
|
||||
var documents = knowledgeService.query(knowledgeId, vo.getContent());
|
||||
logger.info("Knowledge id:{}, content:{}", knowledgeId, vo.getContent());
|
||||
var documents = knowledgeService.query(knowledgeId, vo.getContent(), 0.5);
|
||||
if (ObjectUtil.isNotEmpty(documents)) {
|
||||
systemPromptBuilder.append("以下是与用户问题有关的外部知识,优先利用该知识回答用户的提问:\n");
|
||||
systemPromptBuilder.append(documents.makeString("\n"));
|
||||
builder.append(StrUtil.format("""
|
||||
以下是与用户问题有关的外部知识,优先利用该知识回答用户的提问:
|
||||
{}
|
||||
|
||||
""", documents.makeString("\n")));
|
||||
}
|
||||
}
|
||||
return chatClient.prompt()
|
||||
.system(systemPromptBuilder.toString())
|
||||
.tools(new DatetimeTools())
|
||||
.system(builder.toString())
|
||||
.messages(
|
||||
messages
|
||||
.collect(message -> StrUtil.equals(message.getRole(), ROLE_ASSISTANT)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.lanyuanxiaoyao.service.ai.chat.tools;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250516
|
||||
*/
|
||||
public class DatetimeTools {
|
||||
private final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Tool(description = "获取当前日期和时间")
|
||||
public String getCurrentDateTime() {
|
||||
return LocalDateTime.now().format(formatter);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.lanyuanxiaoyao.service.ai.chat;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250514
|
||||
*/
|
||||
public class TestChat {
|
||||
public static void main(String[] args) {
|
||||
ChatClient client = ChatClient.builder(
|
||||
OpenAiChatModel.builder()
|
||||
.openAiApi(
|
||||
OpenAiApi.builder()
|
||||
.baseUrl("http://132.121.206.65:10086")
|
||||
.apiKey("*XMySqV%>hR&v>>g*NwCs3tpQ5FVMFEF2VHVTj<MYQd$&@$sY7CgqNyea4giJi4")
|
||||
.build()
|
||||
)
|
||||
.defaultOptions(
|
||||
OpenAiChatOptions.builder()
|
||||
.model("Qwen3-1.7")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
String content = client.prompt()
|
||||
.user("你好")
|
||||
.call()
|
||||
.content();
|
||||
System.out.println(content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.lanyuanxiaoyao.service.ai.chat;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.definition.ToolDefinition;
|
||||
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||
import org.springframework.http.client.reactive.JdkClientHttpConnector;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.Disposable;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250514
|
||||
*/
|
||||
@SuppressWarnings("NullableProblems")
|
||||
public class TestSpringAIToolChat {
|
||||
public static void main(String[] args) {
|
||||
ChatClient client = ChatClient.builder(
|
||||
DeepSeekChatModel.builder()
|
||||
.deepSeekApi(
|
||||
DeepSeekApi.builder()
|
||||
.baseUrl("http://132.121.206.65:10086/v1")
|
||||
.apiKey("*XMySqV%>hR&v>>g*NwCs3tpQ5FVMFEF2VHVTj<MYQd$&@$sY7CgqNyea4giJi4")
|
||||
.restClientBuilder(restClientBuilder())
|
||||
.webClientBuilder(webClientBuilder())
|
||||
.build()
|
||||
)
|
||||
.defaultOptions(
|
||||
DeepSeekChatOptions.builder()
|
||||
.model("Qwen3-1.7-vllm")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
ToolCallback datetimeTool = new ToolCallback() {
|
||||
@Override
|
||||
public ToolDefinition getToolDefinition() {
|
||||
return ToolDefinition.builder()
|
||||
.name("getCurrentTime")
|
||||
.description("获取当前日期和时间")
|
||||
// language=JSON
|
||||
.inputSchema("""
|
||||
{"type": null}
|
||||
""")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String toolInput) {
|
||||
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
};
|
||||
Disposable disposable = client.prompt()
|
||||
.user("当前时间?")
|
||||
.toolCallbacks(datetimeTool)
|
||||
.stream()
|
||||
.content()
|
||||
.subscribe(System.out::println);
|
||||
while (!disposable.isDisposed()) {
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient httpClient() {
|
||||
return HttpClient.newBuilder()
|
||||
.version(HttpClient.Version.HTTP_1_1)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static RestClient.Builder restClientBuilder() {
|
||||
return RestClient.builder()
|
||||
.requestFactory(new JdkClientHttpRequestFactory(httpClient()));
|
||||
}
|
||||
|
||||
private static WebClient.Builder webClientBuilder() {
|
||||
return WebClient.builder()
|
||||
.clientConnector(new JdkClientHttpConnector(httpClient()));
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,14 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-pdf-document-reader</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-ai-dialect-openai</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.lanyuanxiaoyao.service.ai.knowledge.configuration;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.noear.solon.ai.reranking.RerankingModel;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250604
|
||||
*/
|
||||
@Configuration
|
||||
public class SolonConfiguration {
|
||||
@Bean
|
||||
public RerankingModel rerankingModel(SolonProperties solonProperties) {
|
||||
return RerankingModel.of(StrUtil.format("{}{}", solonProperties.getBaseUrl(), solonProperties.getRerank().getEndpoint()))
|
||||
.apiKey(solonProperties.getApiKey())
|
||||
.model(solonProperties.getRerank().getModel())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.lanyuanxiaoyao.service.ai.knowledge.configuration;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250604
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "solon")
|
||||
public class SolonProperties {
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
private Rerank rerank;
|
||||
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public Rerank getRerank() {
|
||||
return rerank;
|
||||
}
|
||||
|
||||
public void setRerank(Rerank rerank) {
|
||||
this.rerank = rerank;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SolonProperties{" +
|
||||
"baseUrl='" + baseUrl + '\'' +
|
||||
", apiKey='" + apiKey + '\'' +
|
||||
", rerank=" + rerank +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static final class Rerank {
|
||||
private String model;
|
||||
private String endpoint;
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getEndpoint() {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
public void setEndpoint(String endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rerank{" +
|
||||
"model='" + model + '\'' +
|
||||
", endpoint='" + endpoint + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisMapResponse;
|
||||
import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse;
|
||||
import com.lanyuanxiaoyao.service.ai.knowledge.entity.vo.SegmentVO;
|
||||
import com.lanyuanxiaoyao.service.ai.knowledge.service.EmbeddingService;
|
||||
import com.lanyuanxiaoyao.service.ai.knowledge.service.KnowledgeService;
|
||||
import com.lanyuanxiaoyao.service.ai.knowledge.service.KnowledgeBaseService;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.eclipse.collections.api.factory.Lists;
|
||||
import org.eclipse.collections.api.list.ImmutableList;
|
||||
@@ -24,14 +25,14 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("knowledge")
|
||||
public class KnowledgeController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeController.class);
|
||||
public class KnowledgeBaseController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeBaseController.class);
|
||||
|
||||
private final KnowledgeService knowledgeService;
|
||||
private final KnowledgeBaseService knowledgeBaseService;
|
||||
private final EmbeddingService embeddingService;
|
||||
|
||||
public KnowledgeController(KnowledgeService knowledgeService, EmbeddingService embeddingService) {
|
||||
this.knowledgeService = knowledgeService;
|
||||
public KnowledgeBaseController(KnowledgeBaseService knowledgeBaseService, EmbeddingService embeddingService) {
|
||||
this.knowledgeBaseService = knowledgeBaseService;
|
||||
this.embeddingService = embeddingService;
|
||||
}
|
||||
|
||||
@@ -40,23 +41,23 @@ public class KnowledgeController {
|
||||
@RequestParam("name") String name,
|
||||
@RequestParam("strategy") String strategy
|
||||
) throws ExecutionException, InterruptedException {
|
||||
knowledgeService.add(name, strategy);
|
||||
knowledgeBaseService.add(name, strategy);
|
||||
}
|
||||
|
||||
@GetMapping("name")
|
||||
public AmisMapResponse name(@RequestParam("id") Long id) {
|
||||
return AmisResponse.responseMapData()
|
||||
.setData("name", knowledgeService.getName(id));
|
||||
.setData("name", knowledgeBaseService.getName(id));
|
||||
}
|
||||
|
||||
@GetMapping("list")
|
||||
public AmisResponse<?> list() {
|
||||
return AmisResponse.responseCrudData(knowledgeService.list());
|
||||
return AmisResponse.responseCrudData(knowledgeBaseService.list());
|
||||
}
|
||||
|
||||
@GetMapping("delete")
|
||||
public void delete(@RequestParam("id") Long id) throws ExecutionException, InterruptedException {
|
||||
knowledgeService.remove(id);
|
||||
knowledgeBaseService.remove(id);
|
||||
}
|
||||
|
||||
@PostMapping("preview_text")
|
||||
@@ -114,7 +115,7 @@ public class KnowledgeController {
|
||||
@RequestParam(value = "limit", defaultValue = "5") Integer limit,
|
||||
@RequestParam(value = "threshold", defaultValue = "0.6") Double threshold,
|
||||
@RequestBody String text
|
||||
) throws ExecutionException, InterruptedException {
|
||||
return knowledgeService.query(id, text, limit, threshold);
|
||||
) throws ExecutionException, InterruptedException, IOException {
|
||||
return knowledgeBaseService.query(id, text, limit, threshold);
|
||||
}
|
||||
}
|
||||
@@ -29,15 +29,15 @@ public class EmbeddingService {
|
||||
|
||||
private final DataFileService dataFileService;
|
||||
private final FlowExecutor executor;
|
||||
private final KnowledgeService knowledgeService;
|
||||
private final KnowledgeBaseService knowledgeBaseService;
|
||||
private final GroupService groupService;
|
||||
private final ExecutorService executors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
public EmbeddingService(DataFileService dataFileService, FlowExecutor executor, KnowledgeService knowledgeService, GroupService groupService) {
|
||||
public EmbeddingService(DataFileService dataFileService, FlowExecutor executor, KnowledgeBaseService knowledgeBaseService, GroupService groupService) {
|
||||
this.dataFileService = dataFileService;
|
||||
this.executor = executor;
|
||||
this.knowledgeService = knowledgeService;
|
||||
this.knowledgeBaseService = knowledgeBaseService;
|
||||
this.groupService = groupService;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class EmbeddingService {
|
||||
|
||||
public void submit(Long id, String mode, String content) {
|
||||
executors.submit(() -> {
|
||||
Knowledge knowledge = knowledgeService.get(id);
|
||||
Knowledge knowledge = knowledgeBaseService.get(id);
|
||||
Long groupId = groupService.add(knowledge.getId(), StrUtil.format("文本-{}", IdUtil.nanoId(10)));
|
||||
EmbeddingContext context = EmbeddingContext.builder()
|
||||
.vectorSourceId(knowledge.getVectorSourceId())
|
||||
@@ -80,7 +80,7 @@ public class EmbeddingService {
|
||||
|
||||
public void submit(Long id, String mode, ImmutableList<String> ids) {
|
||||
executors.submit(() -> {
|
||||
Knowledge knowledge = knowledgeService.get(id);
|
||||
Knowledge knowledge = knowledgeBaseService.get(id);
|
||||
List<Pair<Long, DataFileVO>> vos = Lists.mutable.empty();
|
||||
for (String fileId : ids) {
|
||||
DataFileVO vo = dataFileService.downloadFile(Long.parseLong(fileId));
|
||||
|
||||
@@ -102,7 +102,7 @@ public class GroupService {
|
||||
public void remove(Long groupId) throws ExecutionException, InterruptedException {
|
||||
Long vectorSourceId = template.queryForObject(
|
||||
SqlBuilder.select("k.vector_source_id")
|
||||
.from(Alias.of(GROUP_TABLE_NAME, "g"), Alias.of(KnowledgeService.KNOWLEDGE_TABLE_NAME, "k"))
|
||||
.from(Alias.of(GROUP_TABLE_NAME, "g"), Alias.of(KnowledgeBaseService.KNOWLEDGE_TABLE_NAME, "k"))
|
||||
.whereEq("g.knowledge_id", Column.as("k.id"))
|
||||
.andEq("g.id", groupId)
|
||||
.precompileSql(),
|
||||
|
||||
@@ -9,11 +9,13 @@ import com.lanyuanxiaoyao.service.ai.knowledge.entity.vo.KnowledgeVO;
|
||||
import com.lanyuanxiaoyao.service.common.Constants;
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.grpc.Collections;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.collections.api.factory.Lists;
|
||||
import org.eclipse.collections.api.list.ImmutableList;
|
||||
import org.noear.solon.ai.reranking.RerankingModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.document.Document;
|
||||
@@ -31,9 +33,9 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @version 20250522
|
||||
*/
|
||||
@Service
|
||||
public class KnowledgeService {
|
||||
public class KnowledgeBaseService {
|
||||
public static final String KNOWLEDGE_TABLE_NAME = Constants.DATABASE_NAME + ".service_ai_knowledge";
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeService.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeBaseService.class);
|
||||
private static final RowMapper<Knowledge> knowledgeMapper = (rs, row) -> {
|
||||
Knowledge knowledge = new Knowledge();
|
||||
knowledge.setId(rs.getLong(1));
|
||||
@@ -48,12 +50,14 @@ public class KnowledgeService {
|
||||
private final EmbeddingModel model;
|
||||
private final QdrantClient client;
|
||||
private final GroupService groupService;
|
||||
private final RerankingModel rerankingModel;
|
||||
|
||||
public KnowledgeService(JdbcTemplate template, EmbeddingModel model, VectorStore vectorStore, GroupService groupService) {
|
||||
public KnowledgeBaseService(JdbcTemplate template, EmbeddingModel model, VectorStore vectorStore, GroupService groupService, RerankingModel rerankingModel) {
|
||||
this.template = template;
|
||||
this.model = model;
|
||||
this.client = (QdrantClient) vectorStore.getNativeClient().orElseThrow();
|
||||
this.groupService = groupService;
|
||||
this.rerankingModel = rerankingModel;
|
||||
}
|
||||
|
||||
public Knowledge get(Long id) {
|
||||
@@ -166,7 +170,8 @@ public class KnowledgeService {
|
||||
Long id,
|
||||
String text,
|
||||
Integer limit,
|
||||
Double threshold) throws ExecutionException, InterruptedException {
|
||||
Double threshold
|
||||
) throws ExecutionException, InterruptedException, IOException {
|
||||
Knowledge knowledge = get(id);
|
||||
Boolean exists = client.collectionExistsAsync(String.valueOf(knowledge.getVectorSourceId())).get();
|
||||
if (!exists) {
|
||||
@@ -183,7 +188,13 @@ public class KnowledgeService {
|
||||
.similarityThreshold(threshold)
|
||||
.build()
|
||||
);
|
||||
return Lists.immutable.ofAll(documents)
|
||||
.collect(Document::getText);
|
||||
List<org.noear.solon.ai.rag.Document> rerankDocuments = rerankingModel.rerank(
|
||||
text,
|
||||
documents.stream()
|
||||
.map(doc -> new org.noear.solon.ai.rag.Document(doc.getId(), doc.getText(), doc.getMetadata(), doc.getScore()))
|
||||
.toList()
|
||||
);
|
||||
return Lists.immutable.ofAll(rerankDocuments)
|
||||
.collect(org.noear.solon.ai.rag.Document::getContent);
|
||||
}
|
||||
}
|
||||
@@ -23,16 +23,16 @@ import org.springframework.stereotype.Service;
|
||||
public class SegmentService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SegmentService.class);
|
||||
|
||||
private final KnowledgeService knowledgeService;
|
||||
private final KnowledgeBaseService knowledgeBaseService;
|
||||
private final QdrantClient client;
|
||||
|
||||
public SegmentService(KnowledgeService knowledgeService, VectorStore vectorStore) {
|
||||
this.knowledgeService = knowledgeService;
|
||||
public SegmentService(KnowledgeBaseService knowledgeBaseService, VectorStore vectorStore) {
|
||||
this.knowledgeBaseService = knowledgeBaseService;
|
||||
this.client = (QdrantClient) vectorStore.getNativeClient().orElseThrow();
|
||||
}
|
||||
|
||||
public ImmutableList<SegmentVO> list(Long id, Long groupId) throws ExecutionException, InterruptedException {
|
||||
Knowledge knowledge = knowledgeService.get(id);
|
||||
Knowledge knowledge = knowledgeBaseService.get(id);
|
||||
Points.ScrollResponse response = client.scrollAsync(
|
||||
Points.ScrollPoints.newBuilder()
|
||||
.setCollectionName(String.valueOf(knowledge.getVectorSourceId()))
|
||||
@@ -59,7 +59,7 @@ public class SegmentService {
|
||||
}
|
||||
|
||||
public void remove(Long knowledgeId, Long segmentId) throws ExecutionException, InterruptedException {
|
||||
Knowledge knowledge = knowledgeService.get(knowledgeId);
|
||||
Knowledge knowledge = knowledgeBaseService.get(knowledgeId);
|
||||
client.deletePayloadAsync(
|
||||
String.valueOf(knowledgeId),
|
||||
List.of(String.valueOf(segmentId)),
|
||||
|
||||
@@ -22,3 +22,9 @@ liteflow:
|
||||
rule-source: config/flow.xml
|
||||
print-banner: false
|
||||
check-node-exists: false
|
||||
solon:
|
||||
base-url: http://132.121.206.65:10086
|
||||
api-key: ENC(K+Hff9QGC+fcyi510VIDd9CaeK/IN5WBJ9rlkUsHEdDgIidW+stHHJlsK0lLPUXXREha+ToQZqqDXJrqSE+GUKCXklFhelD8bRHFXBIeP/ZzT2cxhzgKUXgjw3S0Qw2R)
|
||||
rerank:
|
||||
model: 'Bge-reranker-v2-vllm'
|
||||
endpoint: '/v1/rerank'
|
||||
@@ -18,5 +18,8 @@ public interface KnowledgeService {
|
||||
ImmutableList<String> query(@Query("id") Long id, @Body String text);
|
||||
|
||||
@Post(value = "/query", contentType = "plain/text")
|
||||
ImmutableList<String> query(@Query("id") Long id, @Query("limit") Integer limit, @Query("threshold") Double threshold, @Body String text);
|
||||
ImmutableList<String> query(@Query("id") Long id, @Body String text, @Query("threshold") Double threshold);
|
||||
|
||||
@Post(value = "/query", contentType = "plain/text")
|
||||
ImmutableList<String> query(@Query("id") Long id, @Body String text, @Query("limit") Integer limit, @Query("threshold") Double threshold);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
forest:
|
||||
backend: httpclient
|
||||
timeout: 60000
|
||||
timeout: 300000
|
||||
log-enabled: false
|
||||
interceptors:
|
||||
- com.lanyuanxiaoyao.service.forest.configuration.SpringCloudDiscoveryInterceptor
|
||||
|
||||
@@ -2,10 +2,10 @@ import {ClearOutlined, FileOutlined, UserOutlined} from '@ant-design/icons'
|
||||
import {Bubble, Sender, useXAgent, useXChat, Welcome} from '@ant-design/x'
|
||||
import {fetchEventSource} from '@echofly/fetch-event-source'
|
||||
import {useMount} from 'ahooks'
|
||||
import {Button, Collapse, Divider, Flex, Popover, Radio, Switch, Tooltip, Typography} from 'antd'
|
||||
import {Button, Collapse, Flex, Popover, Radio, Typography} from 'antd'
|
||||
import {isEqual, isStrBlank, trim} from 'licia'
|
||||
import markdownIt from 'markdown-it'
|
||||
import {useMemo, useRef, useState} from 'react'
|
||||
import {useRef, useState} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {commonInfo} from '../../util/amis.tsx'
|
||||
|
||||
@@ -49,16 +49,8 @@ type ChatMessage = { role: string, content?: string, reason?: string }
|
||||
function Conversation() {
|
||||
const abortController = useRef<AbortController | null>(null)
|
||||
const [input, setInput] = useState<string>('')
|
||||
const [think, setThink] = useState<boolean>(true)
|
||||
const [knowledge, setKnowledge] = useState<string>('0')
|
||||
const [knowledgeList, setKnowledgeList] = useState<{ id: string, name: string }[]>([])
|
||||
const requestUrl = useMemo(() => {
|
||||
let url = `${commonInfo.baseAiChatUrl}/chat/async`
|
||||
if (!isEqual('0', knowledge)) {
|
||||
url = `${url}?knowledge_id=${knowledge}`
|
||||
}
|
||||
return url
|
||||
}, [knowledge])
|
||||
|
||||
useMount(async () => {
|
||||
let response = await fetch(`${commonInfo.baseAiKnowledgeUrl}/knowledge/list`, {
|
||||
@@ -70,6 +62,10 @@ function Conversation() {
|
||||
|
||||
const [agent] = useXAgent<ChatMessage>({
|
||||
request: async (info, callbacks) => {
|
||||
let requestUrl = `${commonInfo.baseAiChatUrl}/chat/async`
|
||||
if (!isEqual('0', info.knowledge)) {
|
||||
requestUrl = `${requestUrl}?knowledge_id=${info.knowledge}`
|
||||
}
|
||||
await fetchEventSource(requestUrl, {
|
||||
method: 'POST',
|
||||
headers: commonInfo.authorizationHeaders,
|
||||
@@ -193,9 +189,10 @@ function Conversation() {
|
||||
onRequest({
|
||||
message: {
|
||||
role: 'user',
|
||||
content: (!think && messages.length === 0) ? `/no_think ${message}` : message,
|
||||
content: message,
|
||||
},
|
||||
stream: true,
|
||||
knowledge: knowledge,
|
||||
})
|
||||
setInput('')
|
||||
}}
|
||||
@@ -205,14 +202,6 @@ function Conversation() {
|
||||
return (
|
||||
<Flex justify="space-between" align="center">
|
||||
<Flex gap="small" align="center">
|
||||
深度思考
|
||||
<Switch
|
||||
size="small"
|
||||
value={think}
|
||||
onChange={setThink}
|
||||
disabled={messages.length > 0}
|
||||
/>
|
||||
<Divider type="vertical"/>
|
||||
<Popover
|
||||
title="选择知识库"
|
||||
trigger="hover"
|
||||
@@ -235,17 +224,18 @@ function Conversation() {
|
||||
icon={<FileOutlined/>}
|
||||
type="text"
|
||||
size="small"
|
||||
/>
|
||||
>
|
||||
知识库
|
||||
</Button>
|
||||
</Popover>
|
||||
<Tooltip title="清空对话">
|
||||
<Button
|
||||
icon={<ClearOutlined/>}
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() => setMessages([])}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider type="vertical"/>
|
||||
<Button
|
||||
icon={<ClearOutlined/>}
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={() => setMessages([])}
|
||||
>
|
||||
清空对话
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex align="center">
|
||||
{agent.isRequesting() ? (
|
||||
|
||||
Reference in New Issue
Block a user