fix: chatai代码迁移及修改

This commit is contained in:
linhuan1357 2025-03-03 09:28:33 +08:00
parent 27a28c6817
commit dab9fa382a
91 changed files with 3991 additions and 303 deletions

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-langchat</artifactId>
<version>${revision}</version>
</parent>
<artifactId>yudao-module-langchat-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
langchat 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.enums;
/**
* @author tycoding
* @since 2024/1/15
*/
public interface CacheConst {
/**
* 系统所有Redis缓存Key前缀 prefix
*/
String REDIS_KEY_PREFIX = "langchat:";
/**
* Auth缓存前缀
*/
String AUTH_PREFIX = REDIS_KEY_PREFIX + "auth:";
/**
* Auth Session缓存前缀
*/
String AUTH_SESSION_PREFIX = AUTH_PREFIX + "session:";
/**
* Auth Session缓存变量前缀
*/
String AUTH_USER_INFO_KEY = "USER_INFO";
/**
* Auth Token缓存变量前缀
*/
String AUTH_TOKEN_INFO_KEY = "TOKEN_INFO";
/**
* 用户信息缓存
*/
String USER_DETAIL_KEY = REDIS_KEY_PREFIX + "user_details";
/**
* 验证码缓存前缀
*/
String CAPTCHA_PREFIX = REDIS_KEY_PREFIX + "auth:captcha:";
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.langchat.enums;
import lombok.AllArgsConstructor;
/**
* @author GB
* @desc
* @since 2024-08-21
*/
@AllArgsConstructor
public enum ChatErrorEnum {
API_KEY_IS_NULL(1000, "模型 %s %s api key 为空,请检查配置"),
BASE_URL_IS_NULL(1003, "模型 %s %s base url 为空,请检查配置"),
SECRET_KEY_IS_NULL(1005, "模型 %s %s base secret Key 为空,请检查配置"),
;
/**
* 错误码
*/
private int errorCode;
/**
* 错误描述用于展示给用户
*/
private String errorDesc;
public int getErrorCode() {
return this.errorCode;
}
public String getErrorDesc(String modelName, String type) {
return this.errorDesc.formatted(modelName, type);
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.enums;
/**
* @author tycoding
* @since 2024/6/6
*/
public interface EmbedConst {
String ORIGIN_TYPE_INPUT = "INPUT";
String ORIGIN_TYPE_UPLOAD = "UPLOAD";
String KNOWLEDGE = "knowledgeId";
String FILENAME = "docsName";
String CLAZZ_NAME_OPENAI = "OpenAiEmbeddingModel";
String CLAZZ_NAME_QIANFAN = "QianfanEmbeddingModel";
String CLAZZ_NAME_QIANWEN = "QwenEmbeddingModel";
String CLAZZ_NAME_ZHIPU = "ZhipuAiEmbeddingModel";
String CLAZZ_NAME_OLLAMA = "OllamaEmbeddingModel";
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.enums;
import lombok.Getter;
/**
* @author tycoding
* @since 2024/10/28
*/
@Getter
public enum EmbedStoreEnum {
REDIS,
PGVECTOR,
MILVUS,
;
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.enums;
/**
* @author tycoding
* @since 2024/1/6
*/
public interface ModelConst {
String TEXT_SUFFIX = "_text";
String IMAGE_SUFFIX = "_image";
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.enums;
import lombok.Getter;
/**
* @author tycoding
* @since 2024/6/16
*/
@Getter
public enum ProviderEnum {
OPENAI,
AZURE_OPENAI,
GEMINI,
OLLAMA,
CLAUDE,
Q_FAN,
Q_WEN,
ZHIPU,
YI,
DOUYIN,
DEEPSEEK,
SILICON,
SPARK,
;
}

View File

@ -12,11 +12,100 @@
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<properties>
<langchain4j.version>1.0.0-beta1</langchain4j.version>
</properties>
<description>
</description>
<dependencies>
<!--langchat4j-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-core</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-redis</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pgvector</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-milvus</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-tika</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-qianfan</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope</artifactId>
<version>${langchain4j.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-zhipu-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- OSS -->
<dependency>
<groupId>org.dromara.x-file-storage</groupId>
<artifactId>x-file-storage-core</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.12.1</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.16.1</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.137</version>
</dependency>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
@ -24,13 +113,15 @@
</dependency>
<!-- 依赖服务 -->
<!-- <dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-infra-api</artifactId>
<version>${revision}</version>
</dependency>-->
<artifactId>yudao-common</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-langchat-api</artifactId>
<version>2.3.0-jdk8-SNAPSHOT</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>

View File

@ -16,17 +16,21 @@
package cn.iocoder.yudao.module.langchat;
import org.mybatis.spring.annotation.MapperScan;
import cn.iocoder.yudao.module.langchat.config.EnableFileStorage;
import cn.iocoder.yudao.module.langchat.config.SpringFileStorageProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author tycoding
* @since 2024/2/4
*/
//@EnableFileStorage
@EnableFileStorage
@SpringBootApplication
@MapperScan("cn.iocoder.yudao.module.langchat.server.mapper")
@EnableConfigurationProperties({
SpringFileStorageProperties.class,
})
public class LangChatApplication {
public static void main(String[] args) {

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.annotation;
package cn.iocoder.yudao.module.langchat.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.component;
package cn.iocoder.yudao.module.langchat.component;
import org.springframework.context.ApplicationEvent;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.component;
package cn.iocoder.yudao.module.langchat.component;
import lombok.Getter;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.component;
package cn.iocoder.yudao.module.langchat.component;
import org.springframework.context.ApplicationEvent;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.component;
package cn.iocoder.yudao.module.langchat.component;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.config;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* 启用文件存储会自动根据配置文件进行加载
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FileStorageAutoConfiguration.class, SpringFileStorageProperties.class})
public @interface EnableFileStorage {}

View File

@ -0,0 +1,203 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.config;
import cn.iocoder.yudao.module.langchat.wrapper.MultipartFileWrapperAdapter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.FileStorageServiceBuilder;
import org.dromara.x.file.storage.core.aspect.FileStorageAspect;
import org.dromara.x.file.storage.core.file.FileWrapperAdapter;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.dromara.x.file.storage.core.platform.FileStorageClientFactory;
import org.dromara.x.file.storage.core.recorder.DefaultFileRecorder;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.dromara.x.file.storage.core.tika.ContentTypeDetect;
import org.dromara.x.file.storage.core.tika.DefaultTikaFactory;
import org.dromara.x.file.storage.core.tika.TikaContentTypeDetect;
import org.dromara.x.file.storage.core.tika.TikaFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Configuration
@ConditionalOnMissingBean(FileStorageService.class)
public class FileStorageAutoConfiguration {
@Autowired
private SpringFileStorageProperties properties;
@Autowired
private ApplicationContext applicationContext;
/**
* 当没有找到 FileRecorder 时使用默认的 FileRecorder
*/
@Bean
@ConditionalOnMissingBean(FileRecorder.class)
public FileRecorder fileRecorder() {
log.warn("没有找到 FileRecorder 的实现类,文件上传之外的部分功能无法正常使用,必须实现该接口才能使用完整功能!");
return new DefaultFileRecorder();
}
/**
* Tika 工厂类型用于识别上传的文件的 MINE
*/
@Bean
@ConditionalOnMissingBean(TikaFactory.class)
public TikaFactory tikaFactory() {
return new DefaultTikaFactory();
}
/**
* 识别文件的 MIME 类型
*/
@Bean
@ConditionalOnMissingBean(ContentTypeDetect.class)
public ContentTypeDetect contentTypeDetect(TikaFactory tikaFactory) {
return new TikaContentTypeDetect(tikaFactory);
}
/**
* 文件存储服务
*/
@Bean(destroyMethod = "destroy")
public FileStorageService fileStorageService(
FileRecorder fileRecorder,
@Autowired(required = false) List<List<? extends FileStorage>> fileStorageLists,
@Autowired(required = false) List<FileStorageAspect> aspectList,
@Autowired(required = false) List<FileWrapperAdapter> fileWrapperAdapterList,
ContentTypeDetect contentTypeDetect,
@Autowired(required = false) List<List<FileStorageClientFactory<?>>> clientFactoryList) {
if (fileStorageLists == null) fileStorageLists = new ArrayList<>();
if (aspectList == null) aspectList = new ArrayList<>();
if (fileWrapperAdapterList == null) fileWrapperAdapterList = new ArrayList<>();
if (clientFactoryList == null) clientFactoryList = new ArrayList<>();
FileStorageServiceBuilder builder = FileStorageServiceBuilder.create(properties.toFileStorageProperties())
.setFileRecorder(fileRecorder)
.setAspectList(aspectList)
.setContentTypeDetect(contentTypeDetect)
.setFileWrapperAdapterList(fileWrapperAdapterList)
.setClientFactoryList(clientFactoryList);
fileStorageLists.forEach(builder::addFileStorage);
if (properties.getEnableByteFileWrapper()) {
builder.addByteFileWrapperAdapter();
}
if (properties.getEnableUriFileWrapper()) {
builder.addUriFileWrapperAdapter();
}
if (properties.getEnableInputStreamFileWrapper()) {
builder.addInputStreamFileWrapperAdapter();
}
if (properties.getEnableLocalFileWrapper()) {
builder.addLocalFileWrapperAdapter();
}
if (properties.getEnableHttpServletRequestFileWrapper()) {
if (FileStorageServiceBuilder.doesNotExistClass("javax.servlet.http.HttpServletRequest")
&& FileStorageServiceBuilder.doesNotExistClass("jakarta.servlet.http.HttpServletRequest")) {
log.warn(
"当前未检测到 Servlet 环境,无法加载 HttpServletRequest 的文件包装适配器请将参数【dromara.x-file-storage.enable-http-servlet-request-file-wrapper】设置为 【false】来消除此警告");
} else {
builder.addHttpServletRequestFileWrapperAdapter();
}
}
if (properties.getEnableMultipartFileWrapper()) {
if (FileStorageServiceBuilder.doesNotExistClass("org.springframework.web.multipart.MultipartFile")) {
log.warn(
"当前未检测到 SpringWeb 环境,无法加载 MultipartFile 的文件包装适配器请将参数【dromara.x-file-storage.enable-multipart-file-wrapper】设置为 【false】来消除此警告");
} else {
builder.addFileWrapperAdapter(new MultipartFileWrapperAdapter());
}
}
if (FileStorageServiceBuilder.doesNotExistClass("org.springframework.web.servlet.config.annotation.WebMvcConfigurer")) {
long localAccessNum = properties.getLocal().stream()
.filter(SpringFileStorageProperties.SpringLocalConfig::getEnableStorage)
.filter(SpringFileStorageProperties.SpringLocalConfig::getEnableAccess)
.count();
long localPlusAccessNum = properties.getLocalPlus().stream()
.filter(SpringFileStorageProperties.SpringLocalPlusConfig::getEnableStorage)
.filter(SpringFileStorageProperties.SpringLocalPlusConfig::getEnableAccess)
.count();
if (localAccessNum + localPlusAccessNum > 0) {
log.warn("当前未检测到 SpringWeb 环境,无法开启本地存储平台的本地访问功能,请将关闭本地访问来消除此警告");
}
}
return builder.build();
}
/**
* FileStorageService 注入自己的代理对象不然会导致针对 FileStorageService 的代理方法不生效
*/
@EventListener(ContextRefreshedEvent.class)
public void onContextRefreshedEvent() {
FileStorageService service = applicationContext.getBean(FileStorageService.class);
service.setSelf(service);
}
/**
* 本地存储文件访问自动配置类
*/
@Configuration
@ConditionalOnClass(name = "org.springframework.web.servlet.config.annotation.WebMvcConfigurer")
public static class FileStorageLocalFileAccessAutoConfiguration {
@Autowired
private SpringFileStorageProperties properties;
/**
* 配置本地存储的访问地址
*/
@Bean
public WebMvcConfigurer fileStorageWebMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
for (SpringFileStorageProperties.SpringLocalConfig local : properties.getLocal()) {
if (local.getEnableStorage() && local.getEnableAccess()) {
registry.addResourceHandler(local.getPathPatterns())
.addResourceLocations("file:" + local.getBasePath());
}
}
for (SpringFileStorageProperties.SpringLocalPlusConfig local : properties.getLocalPlus()) {
if (local.getEnableStorage() && local.getEnableAccess()) {
registry.addResourceHandler(local.getPathPatterns())
.addResourceLocations("file:" + local.getStoragePath());
}
}
}
};
}
}
}

View File

@ -0,0 +1,449 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.config;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.x.file.storage.core.FileStorageProperties;
import org.dromara.x.file.storage.core.FileStorageProperties.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Data
@Accessors(chain = true)
@Component
@ConditionalOnMissingBean(SpringFileStorageProperties.class)
@ConfigurationProperties(prefix = "langchat.oss")
public class SpringFileStorageProperties {
/**
* 默认存储平台
*/
private String defaultPlatform = "local";
/**
* 缩略图后缀例如.min.jpg.png
*/
private String thumbnailSuffix = ".min.jpg";
/**
* 上传时不支持元数据时抛出异常
*/
private Boolean uploadNotSupportMetadataThrowException = true;
/**
* 上传时不支持 ACL 时抛出异常
*/
private Boolean uploadNotSupportAclThrowException = true;
/**
* 复制时不支持元数据时抛出异常
*/
private Boolean copyNotSupportMetadataThrowException = true;
/**
* 复制时不支持 ACL 时抛出异常
*/
private Boolean copyNotSupportAclThrowException = true;
/**
* 移动时不支持元数据时抛出异常
*/
private Boolean moveNotSupportMetadataThrowException = true;
/**
* 移动时不支持 ACL 时抛出异常
*/
private Boolean moveNotSupportAclThrowException = true;
/**
* 启用 byte[] 文件包装适配器
*/
private Boolean enableByteFileWrapper = true;
/**
* 启用 URI 文件包装适配器包含 URL String
*/
private Boolean enableUriFileWrapper = true;
/**
* 启用 InputStream 文件包装适配器
*/
private Boolean enableInputStreamFileWrapper = true;
/**
* 启用本地文件包装适配器
*/
private Boolean enableLocalFileWrapper = true;
/**
* 启用 HttpServletRequest 文件包装适配器
*/
private Boolean enableHttpServletRequestFileWrapper = true;
/**
* 启用 MultipartFile 文件包装适配器
*/
private Boolean enableMultipartFileWrapper = true;
/**
* 本地存储
*/
@Deprecated
private List<? extends SpringLocalConfig> local = new ArrayList<>();
/**
* 本地存储
*/
private List<? extends SpringLocalPlusConfig> localPlus = new ArrayList<>();
/**
* 华为云 OBS
*/
private List<? extends SpringHuaweiObsConfig> huaweiObs = new ArrayList<>();
/**
* 阿里云 OSS
*/
private List<? extends SpringAliyunOssConfig> aliyunOss = new ArrayList<>();
/**
* 七牛云 Kodo
*/
private List<? extends SpringQiniuKodoConfig> qiniuKodo = new ArrayList<>();
/**
* 腾讯云 COS
*/
private List<? extends SpringTencentCosConfig> tencentCos = new ArrayList<>();
/**
* 百度云 BOS
*/
private List<? extends SpringBaiduBosConfig> baiduBos = new ArrayList<>();
/**
* 又拍云 USS
*/
private List<? extends SpringUpyunUssConfig> upyunUss = new ArrayList<>();
/**
* MinIO USS
*/
private List<? extends SpringMinioConfig> minio = new ArrayList<>();
/**
* Amazon S3
*/
private List<? extends SpringAmazonS3Config> amazonS3 = new ArrayList<>();
/**
* FTP
*/
private List<? extends SpringFtpConfig> ftp = new ArrayList<>();
/**
* FTP
*/
private List<? extends SpringSftpConfig> sftp = new ArrayList<>();
/**
* WebDAV
*/
private List<? extends SpringWebDavConfig> webdav = new ArrayList<>();
/**
* GoogleCloud Storage
*/
private List<? extends SpringGoogleCloudStorageConfig> googleCloudStorage = new ArrayList<>();
/**
* FastDFS
*/
private List<? extends SpringFastDfsConfig> fastdfs = new ArrayList<>();
/**
* Azure Blob Storage
*/
private List<? extends SpringAzureBlobStorageConfig> azureBlob = new ArrayList<>();
/**
* 转换成 FileStorageProperties 并过滤掉没有启用的存储平台
*/
public FileStorageProperties toFileStorageProperties() {
FileStorageProperties properties = new FileStorageProperties();
properties.setDefaultPlatform(defaultPlatform);
properties.setThumbnailSuffix(thumbnailSuffix);
properties.setUploadNotSupportMetadataThrowException(uploadNotSupportMetadataThrowException);
properties.setUploadNotSupportAclThrowException(uploadNotSupportAclThrowException);
properties.setCopyNotSupportMetadataThrowException(copyNotSupportMetadataThrowException);
properties.setCopyNotSupportAclThrowException(copyNotSupportAclThrowException);
properties.setMoveNotSupportMetadataThrowException(moveNotSupportMetadataThrowException);
properties.setMoveNotSupportAclThrowException(moveNotSupportAclThrowException);
properties.setLocal(
local.stream().filter(SpringLocalConfig::getEnableStorage).collect(Collectors.toList()));
properties.setLocalPlus(localPlus.stream()
.filter(SpringLocalPlusConfig::getEnableStorage)
.collect(Collectors.toList()));
properties.setHuaweiObs(huaweiObs.stream()
.filter(SpringHuaweiObsConfig::getEnableStorage)
.collect(Collectors.toList()));
properties.setAliyunOss(aliyunOss.stream()
.filter(SpringAliyunOssConfig::getEnableStorage)
.collect(Collectors.toList()));
properties.setQiniuKodo(qiniuKodo.stream()
.filter(SpringQiniuKodoConfig::getEnableStorage)
.collect(Collectors.toList()));
properties.setTencentCos(tencentCos.stream()
.filter(SpringTencentCosConfig::getEnableStorage)
.collect(Collectors.toList()));
properties.setBaiduBos(
baiduBos.stream().filter(SpringBaiduBosConfig::getEnableStorage).collect(Collectors.toList()));
properties.setUpyunUss(
upyunUss.stream().filter(SpringUpyunUssConfig::getEnableStorage).collect(Collectors.toList()));
properties.setMinio(
minio.stream().filter(SpringMinioConfig::getEnableStorage).collect(Collectors.toList()));
properties.setAmazonS3(
amazonS3.stream().filter(SpringAmazonS3Config::getEnableStorage).collect(Collectors.toList()));
properties.setFtp(ftp.stream().filter(SpringFtpConfig::getEnableStorage).collect(Collectors.toList()));
properties.setSftp(
sftp.stream().filter(SpringSftpConfig::getEnableStorage).collect(Collectors.toList()));
properties.setWebdav(
webdav.stream().filter(SpringWebDavConfig::getEnableStorage).collect(Collectors.toList()));
properties.setGoogleCloudStorage(googleCloudStorage.stream()
.filter(SpringGoogleCloudStorageConfig::getEnableStorage)
.collect(Collectors.toList()));
properties.setFastdfs(
fastdfs.stream().filter(SpringFastDfsConfig::getEnableStorage).collect(Collectors.toList()));
properties.setAzureBlob(azureBlob.stream()
.filter(SpringAzureBlobStorageConfig::getEnableStorage)
.collect(Collectors.toList()));
return properties;
}
/**
* 本地存储
*/
@Deprecated
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringLocalConfig extends LocalConfig {
/**
* 本地存储访问路径
*/
private String[] pathPatterns = new String[0];
/**
* 启用本地存储
*/
private Boolean enableStorage = false;
/**
* 启用本地访问
*/
private Boolean enableAccess = false;
}
/**
* 本地存储升级版
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringLocalPlusConfig extends LocalPlusConfig {
/**
* 本地存储访问路径
*/
private String[] pathPatterns = new String[0];
/**
* 启用本地存储
*/
private Boolean enableStorage = false;
/**
* 启用本地访问
*/
private Boolean enableAccess = false;
}
/**
* 华为云 OBS
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringHuaweiObsConfig extends HuaweiObsConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* 阿里云 OSS
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringAliyunOssConfig extends AliyunOssConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* 七牛云 Kodo
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringQiniuKodoConfig extends QiniuKodoConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* 腾讯云 COS
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringTencentCosConfig extends TencentCosConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* 百度云 BOS
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringBaiduBosConfig extends BaiduBosConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* 又拍云 USS
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringUpyunUssConfig extends UpyunUssConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* MinIO
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringMinioConfig extends MinioConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* Amazon S3
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringAmazonS3Config extends AmazonS3Config {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* FTP
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringFtpConfig extends FtpConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* SFTP
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringSftpConfig extends SftpConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* WebDAV
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringWebDavConfig extends WebDavConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* GoogleCloud Storage
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringGoogleCloudStorageConfig extends GoogleCloudStorageConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* FastDFS Storage
* @author XS <wanghaiqi@beeplay123.com>
* @date 2023/10/23
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringFastDfsConfig extends FastDfsConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
/**
* AzureBlob Storage
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public static class SpringAzureBlobStorageConfig extends AzureBlobStorageConfig {
/**
* 启用存储
*/
private Boolean enableStorage = false;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.service.core.EmbeddingService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author tycoding
* @since 2024/4/15
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/chat/aigc/docs")
public class AigcDocsController {
private final AigcDocsMapper docsMapper;
private final EmbeddingService embeddingService;
@GetMapping("/list")
@TenantIgnore
public CommonResult<List<AigcDocs>> list(AigcDocs data) {
return CommonResult.success(docsMapper.selectList(Wrappers.<AigcDocs>lambdaQuery().orderByDesc(AigcDocs::getCreateTime)));
}
@GetMapping("/page")
@TenantIgnore
public CommonResult list(AigcDocs data, QueryPage queryPage) {
Page<AigcDocs> page = new Page<>(queryPage.getPageNo(), queryPage.getPageSize());
return CommonResult.success(MybatisUtil.getData(docsMapper.selectPage(page, Wrappers.<AigcDocs>lambdaQuery()
.eq(data.getKnowledgeId() != null, AigcDocs::getKnowledgeId, data.getKnowledgeId())
.eq(data.getSliceStatus() != null, AigcDocs::getSliceStatus, data.getSliceStatus())
.orderByDesc(AigcDocs::getCreateTime)
)));
}
@GetMapping("/{id}")
@TenantIgnore
public CommonResult<AigcDocs> findById(@PathVariable String id) {
return CommonResult.success(docsMapper.selectById(id));
}
@PostMapping
@ApiLog("新增文档")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:docs:add')")
public CommonResult add(@RequestBody AigcDocs data) {
docsMapper.insert(data);
return CommonResult.success("success");
}
@PutMapping
@ApiLog("修改文档")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:docs:update')")
public CommonResult update(@RequestBody AigcDocs data) {
docsMapper.updateById(data);
return CommonResult.success("success");
}
@DeleteMapping("/{id}")
@ApiLog("删除文档")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:docs:delete')")
@Transactional
public CommonResult delete(@PathVariable String id) {
// 删除切面数据
embeddingService.clearDocSlices(id);
// 删除文档
docsMapper.deleteById(id);
return CommonResult.success("success");
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsSliceMapper;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
/**
* @author tycoding
* @since 2024/4/15
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/chat/aigc/docs/slice")
public class AigcDocsSliceController {
private final AigcDocsSliceMapper docsSliceMapper;
@GetMapping("/list")
@TenantIgnore
public CommonResult<List<AigcDocsSlice>> list(AigcDocsSlice data) {
return CommonResult.success(docsSliceMapper.selectList(Wrappers.<AigcDocsSlice>lambdaQuery().orderByDesc(AigcDocsSlice::getCreateTime)));
}
@GetMapping("/page")
@TenantIgnore
public CommonResult list(AigcDocsSlice data, QueryPage queryPage) {
Page<AigcDocsSlice> page = new Page<>(queryPage.getPageNo(), queryPage.getPageSize());
return CommonResult.success(MybatisUtil.getData(docsSliceMapper.selectPage(page, Wrappers.<AigcDocsSlice>lambdaQuery()
.eq(data.getKnowledgeId() != null, AigcDocsSlice::getKnowledgeId, data.getKnowledgeId())
.eq(data.getDocsId() != null, AigcDocsSlice::getDocsId, data.getDocsId())
.orderByDesc(AigcDocsSlice::getCreateTime)
)));
}
@GetMapping("/{id}")
@TenantIgnore
public CommonResult<AigcDocsSlice> findById(@PathVariable String id) {
return CommonResult.success(docsSliceMapper.selectById(id));
}
@PostMapping
@ApiLog("新增切片数据")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:docs:slice:add')")
public CommonResult add(@RequestBody AigcDocsSlice data) {
data.setCreateTime(new Date());
docsSliceMapper.insert(data);
return CommonResult.success("success");
}
@PutMapping
@ApiLog("修改切片数据")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:docs:slice:update')")
public CommonResult update(@RequestBody AigcDocsSlice data) {
docsSliceMapper.updateById(data);
return CommonResult.success("success");
}
@DeleteMapping("/{id}")
@ApiLog("删除切片数据")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:docs:slice:delete')")
public CommonResult delete(@PathVariable String id) {
docsSliceMapper.deleteById(id);
return CommonResult.success("success");
}
}

View File

@ -14,20 +14,21 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.controller;
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.server.component.EmbeddingRefreshEvent;
import cn.iocoder.yudao.module.langchat.server.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.server.service.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.server.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.server.component.SpringContextHolder;
import cn.iocoder.yudao.module.langchat.server.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.component.EmbeddingRefreshEvent;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.component.SpringContextHolder;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.server.utils.R;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
@ -42,68 +43,74 @@ import java.util.List;
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/aigc/embed-store")
@RequestMapping("/chat/aigc/embed-store")
public class AigcEmbedStoreController {
private final AigcEmbedStoreService embedStoreService;
private final SpringContextHolder contextHolder;
@GetMapping("/list")
public R<List<AigcEmbedStore>> list(AigcEmbedStore data) {
@TenantIgnore
public CommonResult<List<AigcEmbedStore>> list(AigcEmbedStore data) {
List<AigcEmbedStore> list = embedStoreService.list(Wrappers.lambdaQuery());
list.forEach(this::hide);
return R.ok(list);
return CommonResult.success(list);
}
@GetMapping("/page")
public R<Dict> page(AigcEmbedStore embedStore, QueryPage queryPage) {
@TenantIgnore
public CommonResult<Dict> page(AigcEmbedStore embedStore, QueryPage queryPage) {
IPage<AigcEmbedStore> page = embedStoreService.page(MybatisUtil.wrap(embedStore, queryPage),
Wrappers.lambdaQuery());
page.getRecords().forEach(this::hide);
return R.ok(MybatisUtil.getData(page));
return CommonResult.success(MybatisUtil.getData(page));
}
@GetMapping("/{id}")
public R<AigcEmbedStore> findById(@PathVariable String id) {
@TenantIgnore
public CommonResult<AigcEmbedStore> findById(@PathVariable String id) {
AigcEmbedStore store = embedStoreService.getById(id);
hide(store);
return R.ok(store);
return CommonResult.success(store);
}
@PostMapping
@ApiLog("新增向量库")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:embed-store:add')")
public R<AigcEmbedStore> add(@RequestBody AigcEmbedStore data) {
public CommonResult<AigcEmbedStore> add(@RequestBody AigcEmbedStore data) {
if (StrUtil.isNotBlank(data.getPassword()) && data.getPassword().contains("*")) {
data.setPassword(null);
}
embedStoreService.save(data);
SpringContextHolder.publishEvent(new EmbeddingRefreshEvent(data));
return R.ok();
return CommonResult.success(data);
}
@PutMapping
@ApiLog("修改向量库")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:embed-store:update')")
public R update(@RequestBody AigcEmbedStore data) {
public CommonResult update(@RequestBody AigcEmbedStore data) {
if (StrUtil.isNotBlank(data.getPassword()) && data.getPassword().contains("*")) {
data.setPassword(null);
}
embedStoreService.updateById(data);
SpringContextHolder.publishEvent(new EmbeddingRefreshEvent(data));
return R.ok();
return CommonResult.success("success");
}
@DeleteMapping("/{id}")
@ApiLog("删除向量库")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:embed-store:delete')")
public R delete(@PathVariable String id) {
public CommonResult delete(@PathVariable String id) {
AigcEmbedStore store = embedStoreService.getById(id);
if (store != null) {
embedStoreService.removeById(id);
contextHolder.unregisterBean(id);
}
return R.ok();
return CommonResult.success("success");
}
private void hide(AigcEmbedStore data) {

View File

@ -14,24 +14,25 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.controller;
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.server.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.server.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.server.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.server.provider.KnowledgeStoreFactory;
import cn.iocoder.yudao.module.langchat.server.service.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.server.service.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.server.service.AigcModelService;
import cn.iocoder.yudao.module.langchat.server.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.server.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.provider.KnowledgeStoreFactory;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import cn.iocoder.yudao.module.langchat.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.server.utils.R;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -49,7 +50,7 @@ import java.util.stream.Collectors;
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/aigc/knowledge")
@RequestMapping("/chat/aigc/knowledge")
public class AigcKnowledgeController {
private final AigcKnowledgeService kbService;
@ -59,10 +60,11 @@ public class AigcKnowledgeController {
private final KnowledgeStoreFactory knowledgeStore;
@GetMapping("/list")
public R<List<AigcKnowledge>> list(AigcKnowledge data) {
@TenantIgnore
public CommonResult<List<AigcKnowledge>> list(AigcKnowledge data) {
List<AigcKnowledge> list = kbService.list(Wrappers.<AigcKnowledge>lambdaQuery().orderByDesc(AigcKnowledge::getCreateTime));
build(list);
return R.ok(list);
return CommonResult.success(list);
}
private void build(List<AigcKnowledge> records) {
@ -87,8 +89,9 @@ public class AigcKnowledgeController {
}
@GetMapping("/page")
public R list(AigcKnowledge data, QueryPage queryPage) {
Page<AigcKnowledge> page = new Page<>(queryPage.getPage(), queryPage.getLimit());
@TenantIgnore
public CommonResult list(AigcKnowledge data, QueryPage queryPage) {
Page<AigcKnowledge> page = new Page<>(queryPage.getPageNo(), queryPage.getPageSize());
LambdaQueryWrapper<AigcKnowledge> queryWrapper = Wrappers.<AigcKnowledge>lambdaQuery()
.like(!StrUtil.isBlank(data.getName()), AigcKnowledge::getName, data.getName())
.orderByDesc(AigcKnowledge::getCreateTime);
@ -96,11 +99,12 @@ public class AigcKnowledgeController {
build(iPage.getRecords());
return R.ok(MybatisUtil.getData(iPage));
return CommonResult.success(MybatisUtil.getData(iPage));
}
@GetMapping("/{id}")
public R<AigcKnowledge> findById(@PathVariable String id) {
@TenantIgnore
public CommonResult<AigcKnowledge> findById(@PathVariable String id) {
AigcKnowledge knowledge = kbService.getById(id);
if (knowledge.getEmbedStoreId() != null) {
knowledge.setEmbedStore(embedStoreService.getById(knowledge.getEmbedStoreId()));
@ -108,35 +112,38 @@ public class AigcKnowledgeController {
if (knowledge.getEmbedModelId() != null) {
knowledge.setEmbedModel(modelService.getById(knowledge.getEmbedModelId()));
}
return R.ok(knowledge);
return CommonResult.success(knowledge);
}
@PostMapping
@ApiLog("新增知识库")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:knowledge:add')")
public R add(@RequestBody AigcKnowledge data) {
public CommonResult add(@RequestBody AigcKnowledge data) {
data.setCreateTime(String.valueOf(System.currentTimeMillis()));
kbService.save(data);
knowledgeStore.init();
return R.ok();
return CommonResult.success("success");
}
@PutMapping
@ApiLog("更新知识库")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:knowledge:update')")
public R update(@RequestBody AigcKnowledge data) {
public CommonResult update(@RequestBody AigcKnowledge data) {
kbService.updateById(data);
knowledgeStore.init();
return R.ok();
return CommonResult.success("success");
}
@DeleteMapping("/{id}")
@ApiLog("删除知识库")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:knowledge:delete')")
public R delete(@PathVariable String id) {
public CommonResult delete(@PathVariable String id) {
kbService.removeKnowledge(id);
knowledgeStore.init();
return R.ok();
return CommonResult.success("success");
}
}

View File

@ -14,17 +14,18 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.controller;
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.server.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.server.service.AigcMessageService;
import cn.iocoder.yudao.module.langchat.server.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.server.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcMessageService;
import cn.iocoder.yudao.module.langchat.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.server.utils.R;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@ -36,7 +37,7 @@ import org.springframework.web.bind.annotation.*;
* @author tycoding
* @since 2024/1/19
*/
@RequestMapping("/aigc/message")
@RequestMapping("/chat/aigc/message")
@RestController
@AllArgsConstructor
public class AigcMessageController {
@ -44,26 +45,29 @@ public class AigcMessageController {
private final AigcMessageService aigcMessageService;
@GetMapping("/page")
public R list(AigcMessage data, QueryPage queryPage) {
@TenantIgnore
public CommonResult list(AigcMessage data, QueryPage queryPage) {
LambdaQueryWrapper<AigcMessage> queryWrapper = Wrappers.<AigcMessage>lambdaQuery()
.like(!StrUtil.isBlank(data.getMessage()), AigcMessage::getMessage, data.getMessage())
.like(!StrUtil.isBlank(data.getUsername()), AigcMessage::getUsername, data.getUsername())
.eq(!StrUtil.isBlank(data.getRole()), AigcMessage::getRole, data.getRole())
.orderByDesc(AigcMessage::getCreateTime);
IPage<AigcMessage> iPage = aigcMessageService.page(MybatisUtil.wrap(data, queryPage), queryWrapper);
return R.ok(MybatisUtil.getData(iPage));
return CommonResult.success(MybatisUtil.getData(iPage));
}
@GetMapping("/{id}")
public R getById(@PathVariable String id) {
return R.ok(aigcMessageService.getById(id));
@TenantIgnore
public CommonResult getById(@PathVariable String id) {
return CommonResult.success(aigcMessageService.getById(id));
}
@DeleteMapping("/{id}")
@ApiLog("删除会话消息")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:message:delete')")
public R del(@PathVariable String id) {
return R.ok(aigcMessageService.removeById(id));
public CommonResult del(@PathVariable String id) {
return CommonResult.success(aigcMessageService.removeById(id));
}
}

View File

@ -14,19 +14,20 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.controller;
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.server.component.ProviderRefreshEvent;
import cn.iocoder.yudao.module.langchat.server.component.SpringContextHolder;
import cn.iocoder.yudao.module.langchat.server.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.server.service.AigcModelService;
import cn.iocoder.yudao.module.langchat.server.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.server.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.component.ProviderRefreshEvent;
import cn.iocoder.yudao.module.langchat.component.SpringContextHolder;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import cn.iocoder.yudao.module.langchat.annotation.ApiLog;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.server.utils.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
@ -40,32 +41,36 @@ import java.util.List;
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/aigc/model")
@RequestMapping("/chat/aigc/model")
public class AigcModelController {
private final AigcModelService modelService;
private final SpringContextHolder contextHolder;
@GetMapping("/list")
public R<List<AigcModel>> list(AigcModel data) {
return R.ok(modelService.list(data));
@TenantIgnore
public CommonResult<List<AigcModel>> list(AigcModel data) {
return CommonResult.success(modelService.list(data));
}
@GetMapping("/page")
public R list(AigcModel data, QueryPage queryPage) {
@TenantIgnore
public CommonResult list(AigcModel data, QueryPage queryPage) {
Page<AigcModel> iPage = modelService.page(data, queryPage);
return R.ok(MybatisUtil.getData(iPage));
return CommonResult.success(MybatisUtil.getData(iPage));
}
@GetMapping("/{id}")
public R<AigcModel> findById(@PathVariable String id) {
return R.ok(modelService.selectById(id));
@TenantIgnore
public CommonResult<AigcModel> findById(@PathVariable String id) {
return CommonResult.success(modelService.selectById(id));
}
@PostMapping
@ApiLog("添加模型")
@PreAuthorize("@ss.hasPermission('aigc:model:add')")
public R add(@RequestBody AigcModel data) {
@TenantIgnore
public CommonResult add(@RequestBody AigcModel data) {
if (StrUtil.isNotBlank(data.getApiKey()) && data.getApiKey().contains("*")) {
data.setApiKey(null);
}
@ -74,13 +79,14 @@ public class AigcModelController {
}
modelService.save(data);
SpringContextHolder.publishEvent(new ProviderRefreshEvent(data));
return R.ok();
return CommonResult.success("success");
}
@PutMapping
@ApiLog("修改模型")
@PreAuthorize("@ss.hasPermission('aigc:model:update')")
public R update(@RequestBody AigcModel data) {
@TenantIgnore
public CommonResult update(@RequestBody AigcModel data) {
if (StrUtil.isNotBlank(data.getApiKey()) && data.getApiKey().contains("*")) {
data.setApiKey(null);
}
@ -89,18 +95,19 @@ public class AigcModelController {
}
modelService.updateById(data);
SpringContextHolder.publishEvent(new ProviderRefreshEvent(data));
return R.ok();
return CommonResult.success("success");
}
@DeleteMapping("/{id}")
@ApiLog("删除模型")
@PreAuthorize("@ss.hasPermission('aigc:model:delete')")
public R delete(@PathVariable String id) {
@TenantIgnore
public CommonResult delete(@PathVariable String id) {
modelService.removeById(id);
// Delete dynamically registered beans, according to ID
contextHolder.unregisterBean(id);
return R.ok();
return CommonResult.success("success");
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.controller.admin.endpoint;
import ch.qos.logback.core.net.LoginAuthenticator;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.EmbeddingR;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.entity.AigcOss;
import cn.iocoder.yudao.module.langchat.enums.EmbedConst;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcOssService;
import cn.iocoder.yudao.module.langchat.service.core.EmbeddingService;
import cn.iocoder.yudao.module.langchat.service.core.LangEmbeddingService;
import cn.iocoder.yudao.module.langchat.task.TaskManager;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.concurrent.Executors;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
* @author tycoding
* @since 2024/4/25
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/chat/aigc/embedding")
public class EmbeddingEndpoint {
private final LangEmbeddingService langEmbeddingService;
private final AigcKnowledgeService aigcKnowledgeService;
private final AigcDocsMapper aigcDocsMapper;
private final AigcOssService aigcOssService;
private final EmbeddingService embeddingService;
@PostMapping("/text")
@PreAuthorize("@ss.hasPermission('aigc:embedding:text')")
@TenantIgnore
public CommonResult text(@RequestBody AigcDocs data) {
if (StrUtil.isBlankIfStr(data.getContent())) {
throw new ServiceException(-1003,"文档内容不能为空");
}
if (StrUtil.isBlank(data.getId())) {
aigcKnowledgeService.addDocs(data);
}
data.setType(EmbedConst.ORIGIN_TYPE_INPUT).setSliceStatus(false);
try {
EmbeddingR embeddingR = langEmbeddingService.embeddingText(
new ChatReq().setMessage(data.getContent())
.setDocsName(data.getType())
.setDocsId(data.getId())
.setKnowledgeId(data.getKnowledgeId()));
aigcKnowledgeService.addDocsSlice(new AigcDocsSlice()
.setKnowledgeId(data.getKnowledgeId())
.setDocsId(data.getId())
.setVectorId(embeddingR.getVectorId())
.setName(data.getName())
.setContent(embeddingR.getText())
);
aigcKnowledgeService.updateDocs(new AigcDocs().setId(data.getId()).setSliceStatus(true).setSliceNum(1));
} catch (Exception e) {
e.printStackTrace();
// del data
aigcKnowledgeService.removeSlicesOfDoc(data.getId());
}
return CommonResult.success("success");
}
@PostMapping("/docs/{knowledgeId}")
@PreAuthorize("@ss.hasPermission('aigc:embedding:docs')")
@TenantIgnore
public CommonResult docs(MultipartFile file, @PathVariable String knowledgeId) {
// 1.1 获得用户信息
String userId = getLoginUserId().toString();
AigcOss oss = aigcOssService.upload(file, userId);
AigcDocs data = new AigcDocs()
.setName(oss.getOriginalFilename())
.setSliceStatus(false)
.setUrl(oss.getUrl())
.setSize(file.getSize())
.setType(EmbedConst.ORIGIN_TYPE_UPLOAD)
.setKnowledgeId(knowledgeId);
aigcKnowledgeService.addDocs(data);
TaskManager.submitTask(userId, Executors.callable(() -> {
embeddingService.embedDocsSlice(data, oss.getUrl());
}));
return CommonResult.success("success");
}
@GetMapping("/re-embed/{docsId}")
@TenantIgnore
public CommonResult reEmbed(@PathVariable String docsId) {
String userId = getLoginUserId().toString();
AigcDocs docs = aigcDocsMapper.selectById(docsId);
if (docs == null) {
throw new ServiceException(-1003,"没有查询到文档数据");
}
if (EmbedConst.ORIGIN_TYPE_INPUT.equals(docs.getType())) {
text(docs);
}
if (EmbedConst.ORIGIN_TYPE_UPLOAD.equals(docs.getType())) {
// clear before re-embed
embeddingService.clearDocSlices(docsId);
TaskManager.submitTask(userId, Executors.callable(() -> {
embeddingService.embedDocsSlice(docs, docs.getUrl());
}));
}
return CommonResult.success("success");
}
@PostMapping("/search")
@TenantIgnore
public CommonResult search(@RequestBody AigcDocs data) {
return CommonResult.success(embeddingService.search(data));
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.dal.dto;
import cn.iocoder.yudao.module.langchat.utils.StreamEmitter;
import dev.langchain4j.model.input.Prompt;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
/**
* @author tycoding
* @since 2024/1/30
*/
@Data
@Accessors(chain = true)
public class ChatReq {
private String appId;
private String modelId;
private String modelName;
private String modelProvider;
private String message;
private String conversationId;
private String userId;
private String username;
private String chatId;
private String promptText;
private String docsName;
private String knowledgeId;
private List<String> knowledgeIds = new ArrayList<>();
private String docsId;
private String url;
private String role;
private Prompt prompt;
private StreamEmitter emitter;
private Executor executor;
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.dal.dto;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author tycoding
* @since 2024/4/26
*/
@Data
@Accessors(chain = true)
public class EmbeddingR {
/**
* 写入到vector store的ID
*/
private String vectorId;
/**
* 文档ID
*/
private String docsId;
/**
* 知识库ID
*/
private String knowledgeId;
/**
* Embedding后切片的文本
*/
private String text;
}

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* @author tycoding
* @since 2024/1/6
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class AigcOss extends OssR {
private static final long serialVersionUID = -250127374910520163L;
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* 用户ID
*/
private String userId;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* OSS 文件对象
*
* @author tycoding
* @since 2024/1/30
*/
@Data
@Accessors(chain = true)
public class OssR implements Serializable {
private static final long serialVersionUID = 5117927170776709434L;
private String ossId;
private String url;
private Long size;
private String filename;
private String originalFilename;
private String basePath;
private String path;
private String ext;
private String contentType;
private String platform;
private Date createTime;
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.entity;
package cn.iocoder.yudao.module.langchat.entity;
import lombok.Data;
import lombok.experimental.Accessors;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.langchat.server.framework.security;
package cn.iocoder.yudao.module.langchat.framework.security;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.langchat.server.framework.security.config;
package cn.iocoder.yudao.module.langchat.framework.security.config;
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import cn.iocoder.yudao.module.langchat.server.framework.security.ApiConstants;
import cn.iocoder.yudao.module.langchat.framework.security.ApiConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;

View File

@ -0,0 +1,4 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.langchat.framework.security.core;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.server.entity.AigcConversation;
import cn.iocoder.yudao.module.langchat.entity.AigcConversation;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.entity.AigcDocsSlice;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.server.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.server.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -14,10 +14,10 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.hutool.core.lang.Dict;
import cn.iocoder.yudao.module.langchat.server.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.entity.AigcMessage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.server.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.mapper;
import cn.iocoder.yudao.module.langchat.entity.AigcOss;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* @author tycoding
* @since 2024/1/19
*/
@Mapper
public interface AigcOssMapper extends BaseMapper<AigcOss> {
}

View File

@ -14,11 +14,11 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.mapper;
package cn.iocoder.yudao.module.langchat.mapper;
import cn.hutool.core.lang.Dict;
import cn.iocoder.yudao.module.langchat.server.entity.SysUser;
import cn.iocoder.yudao.module.langchat.server.entity.UserInfo;
import cn.iocoder.yudao.module.langchat.entity.SysUser;
import cn.iocoder.yudao.module.langchat.entity.UserInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Mapper;

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author tycoding
* @since 2024/4/15
*/
@Data
@Component
@ConfigurationProperties(prefix = "langchat")
public class LangChatProps {
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* @author tycoding
* @since 2024/3/8
*/
@Slf4j
@Component
@AllArgsConstructor
public class EmbeddingProvider {
private final EmbeddingStoreFactory embeddingStoreFactory;
private final KnowledgeStoreFactory knowledgeStoreFactory;
private final ModelStoreFactory modelStoreFactory;
public static DocumentSplitter splitter() {
return DocumentSplitters.recursive(300, 20);
}
public EmbeddingModel getEmbeddingModel(List<String> knowledgeIds) {
List<String> storeIds = new ArrayList<>();
knowledgeIds.forEach(id -> {
if (knowledgeStoreFactory.containsKnowledge(id)) {
AigcKnowledge data = knowledgeStoreFactory.getKnowledge(id);
if (data.getEmbedModelId() != null) {
storeIds.add(data.getEmbedModelId());
}
}
});
if (storeIds.isEmpty()) {
throw new UnsupportedOperationException("知识库缺少Embedding Model配置请先检查配置");
}
HashSet<String> filterIds = new HashSet<>(storeIds);
if (filterIds.size() > 1) {
throw new UnsupportedOperationException("存在多个不同Embedding Model的知识库请先检查配置");
}
return modelStoreFactory.getEmbeddingModel(storeIds.get(0));
}
public EmbeddingModel getEmbeddingModel(String knowledgeId) {
if (knowledgeStoreFactory.containsKnowledge(knowledgeId)) {
AigcKnowledge data = knowledgeStoreFactory.getKnowledge(knowledgeId);
if (modelStoreFactory.containsEmbeddingModel(data.getEmbedModelId())) {
return modelStoreFactory.getEmbeddingModel(data.getEmbedModelId());
}
}
throw new UnsupportedOperationException("没有找到匹配的Embedding向量数据库");
}
public EmbeddingStore<TextSegment> getEmbeddingStore(String knowledgeId) {
if (knowledgeStoreFactory.containsKnowledge(knowledgeId)) {
AigcKnowledge data = knowledgeStoreFactory.getKnowledge(knowledgeId);
if (embeddingStoreFactory.containsEmbeddingStore(data.getEmbedStoreId())) {
return embeddingStoreFactory.getEmbeddingStore(data.getEmbedStoreId());
}
}
throw new UnsupportedOperationException("没有找到匹配的Embedding向量数据库");
}
public EmbeddingStore<TextSegment> getEmbeddingStore(List<String> knowledgeIds) {
List<String> storeIds = new ArrayList<>();
knowledgeIds.forEach(id -> {
if (knowledgeStoreFactory.containsKnowledge(id)) {
AigcKnowledge data = knowledgeStoreFactory.getKnowledge(id);
if (data.getEmbedStoreId() != null) {
storeIds.add(data.getEmbedStoreId());
}
}
});
if (storeIds.isEmpty()) {
throw new UnsupportedOperationException("知识库缺少Embedding Store配置请先检查配置");
}
HashSet<String> filterIds = new HashSet<>(storeIds);
if (filterIds.size() > 1) {
throw new UnsupportedOperationException("存在多个不同Embedding Store数据源的知识库请先检查配置");
}
return embeddingStoreFactory.getEmbeddingStore(storeIds.get(0));
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.enums.EmbedStoreEnum;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcEmbedStoreService;
import dev.langchain4j.community.store.embedding.redis.RedisEmbeddingStore;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author tycoding
* @since 2024/10/28
*/
@Slf4j
@Component
public class EmbeddingStoreFactory {
@Autowired
private AigcEmbedStoreService aigcEmbedStoreService;
private final List<AigcEmbedStore> modelStore = new ArrayList<>();
private final Map<String, EmbeddingStore<TextSegment>> embedStoreMap = new ConcurrentHashMap<>();
@Async
@PostConstruct
public void init() {
modelStore.clear();
List<AigcEmbedStore> list = aigcEmbedStoreService.list();
list.forEach(embed -> {
try {
if (EmbedStoreEnum.REDIS.name().equalsIgnoreCase(embed.getProvider())) {
RedisEmbeddingStore.Builder builder = RedisEmbeddingStore.builder()
.host(embed.getHost())
.port(embed.getPort())
.indexName(embed.getDatabaseName())
.dimension(embed.getDimension());
if (StrUtil.isNotBlank(embed.getUsername()) && StrUtil.isNotBlank(embed.getPassword())) {
builder.user(embed.getUsername()).password(embed.getPassword());
}
EmbeddingStore<TextSegment> store = builder.build();
embedStoreMap.put(embed.getId(), store);
}
if (EmbedStoreEnum.PGVECTOR.name().equalsIgnoreCase(embed.getProvider())) {
EmbeddingStore<TextSegment> store = PgVectorEmbeddingStore.builder()
.host(embed.getHost())
.port(embed.getPort())
.database(embed.getDatabaseName())
.dimension(embed.getDimension())
.user(embed.getUsername())
.password(embed.getPassword())
.table(embed.getTableName())
.indexListSize(1)
.useIndex(true)
.createTable(true)
.dropTableFirst(false)
.build();
embedStoreMap.put(embed.getId(), store);
}
if (EmbedStoreEnum.MILVUS.name().equalsIgnoreCase(embed.getProvider())) {
EmbeddingStore<TextSegment> store = MilvusEmbeddingStore.builder()
.host(embed.getHost())
.port(embed.getPort())
.databaseName(embed.getDatabaseName())
.dimension(embed.getDimension())
.username(embed.getUsername())
.password(embed.getPassword())
.collectionName(embed.getTableName())
.build();
embedStoreMap.put(embed.getId(), store);
}
modelStore.add(embed);
} catch (Exception e) {
e.printStackTrace();
log.error("向量数据库初始化失败:[{}] --- [{}],数据库配置信息:[{}]", embed.getName(), embed.getProvider(), embed);
}
});
modelStore.forEach(i -> log.info("已成功注册Embedding Store{} 配置信息:{}", i.getProvider(), i));
}
public EmbeddingStore<TextSegment> getEmbeddingStore(String embeddingId) {
return embedStoreMap.get(embeddingId);
}
public boolean containsEmbeddingStore(String embeddingId) {
return embedStoreMap.containsKey(embeddingId);
}
}

View File

@ -14,15 +14,15 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.provider;
package cn.iocoder.yudao.module.langchat.provider;
import cn.iocoder.yudao.module.langchat.server.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.server.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.server.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.server.service.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.server.service.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.server.service.AigcModelService;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.langchat.enums.ModelConst;
import cn.iocoder.yudao.module.langchat.component.ModelTypeEnum;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.provider.build.ModelBuildHandler;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author tycoding
* @since 2024/6/16
*/
@Configuration
@Slf4j
public class ModelStoreFactory {
@Autowired
private AigcModelService aigcModelService;
@Autowired
private List<ModelBuildHandler> modelBuildHandlers;
private final List<AigcModel> modelStore = new ArrayList<>();
private final Map<String, StreamingChatLanguageModel> streamingChatMap = new ConcurrentHashMap<>();
private final Map<String, ChatLanguageModel> chatLanguageMap = new ConcurrentHashMap<>();
private final Map<String, EmbeddingModel> embeddingModelMap = new ConcurrentHashMap<>();
private final Map<String, ImageModel> imageModelMap = new ConcurrentHashMap<>();
@Async
@PostConstruct
public void init() {
modelStore.clear();
streamingChatMap.clear();
chatLanguageMap.clear();
embeddingModelMap.clear();
imageModelMap.clear();
List<AigcModel> list = aigcModelService.list();
list.forEach(model -> {
if (Objects.equals(model.getBaseUrl(), "")) {
model.setBaseUrl(null);
}
chatHandler(model);
embeddingHandler(model);
imageHandler(model);
});
modelStore.forEach(i -> log.info("已成功注册模型:{} -- {} 模型配置:{}", i.getProvider(), i.getType(), i));
}
private void chatHandler(AigcModel model) {
try {
String type = model.getType();
if (!ModelTypeEnum.CHAT.name().equals(type)) {
return;
}
modelBuildHandlers.forEach(x -> {
StreamingChatLanguageModel streamingChatLanguageModel = x.buildStreamingChat(model);
if (ObjectUtil.isNotEmpty(streamingChatLanguageModel)) {
streamingChatMap.put(model.getId(), streamingChatLanguageModel);
modelStore.add(model);
}
ChatLanguageModel languageModel = x.buildChatLanguageModel(model);
if (ObjectUtil.isNotEmpty(languageModel)) {
chatLanguageMap.put(model.getId() + ModelConst.TEXT_SUFFIX, languageModel);
}
});
} catch (Exception e) {
log.error("model 【 id: {} name: {}】streaming chat 配置报错", model.getId(), model.getName());
}
}
private void embeddingHandler(AigcModel model) {
try {
String type = model.getType();
if (!ModelTypeEnum.EMBEDDING.name().equals(type)) {
return;
}
modelBuildHandlers.forEach(x -> {
EmbeddingModel embeddingModel = x.buildEmbedding(model);
if (ObjectUtil.isNotEmpty(embeddingModel)) {
embeddingModelMap.put(model.getId(), embeddingModel);
modelStore.add(model);
}
});
} catch (Exception e) {
log.error("model 【id{} name{}】 embedding 配置报错", model.getId(), model.getName());
}
}
private void imageHandler(AigcModel model) {
try {
String type = model.getType();
if (!ModelTypeEnum.TEXT_IMAGE.name().equals(type)) {
return;
}
modelBuildHandlers.forEach(x -> {
ImageModel imageModel = x.buildImage(model);
if (ObjectUtil.isNotEmpty(imageModel)) {
imageModelMap.put(model.getId(), imageModel);
modelStore.add(model);
}
});
} catch (Exception e) {
log.error("model 【id{} name{}】 image 配置报错", model.getId(), model.getName());
}
}
public StreamingChatLanguageModel getStreamingChatModel(String modelId) {
return streamingChatMap.get(modelId);
}
public boolean containsStreamingChatModel(String modelId) {
return streamingChatMap.containsKey(modelId);
}
public ChatLanguageModel getChatLanguageModel(String modelId) {
return chatLanguageMap.get(modelId + ModelConst.TEXT_SUFFIX);
}
public boolean containsChatLanguageModel(String modelId) {
return chatLanguageMap.containsKey(modelId + ModelConst.TEXT_SUFFIX);
}
public EmbeddingModel getEmbeddingModel(String modelId) {
return embeddingModelMap.get(modelId);
}
public boolean containsEmbeddingModel(String modelId) {
return embeddingModelMap.containsKey(modelId);
}
public ImageModel getImageModel(String modelId) {
return imageModelMap.get(modelId);
}
public boolean containsImageModel(String modelId) {
return imageModelMap.containsKey(modelId);
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider.build;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import org.springframework.stereotype.Component;
/**
* @author GB
* @since 2024-08-18 09:57
*/
public interface ModelBuildHandler {
/**
* 判断是不是当前模型
*/
boolean whetherCurrentModel(AigcModel model);
/**
* basic check
*/
boolean basicCheck(AigcModel model);
/**
* streaming chat build
*/
StreamingChatLanguageModel buildStreamingChat(AigcModel model);
/**
* chat build
*/
ChatLanguageModel buildChatLanguageModel(AigcModel model);
/**
* embedding config
*/
EmbeddingModel buildEmbedding(AigcModel model);
/**
* image config
*/
ImageModel buildImage(AigcModel model);
}

View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider.build;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.enums.ChatErrorEnum;
import cn.iocoder.yudao.module.langchat.enums.ProviderEnum;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import dev.langchain4j.model.ollama.OllamaChatModel;
import dev.langchain4j.model.ollama.OllamaEmbeddingModel;
import dev.langchain4j.model.ollama.OllamaStreamingChatModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* @author GB
* @since 2024-08-19 10:08
*/
@Slf4j
@Component
public class OllamaModelBuildHandler implements ModelBuildHandler {
@Override
public boolean whetherCurrentModel(AigcModel model) {
return ProviderEnum.OLLAMA.name().equals(model.getProvider());
}
@Override
public boolean basicCheck(AigcModel model) {
if (StringUtils.isBlank(model.getBaseUrl())) {
throw new ServiceException(ChatErrorEnum.BASE_URL_IS_NULL.getErrorCode(),
ChatErrorEnum.BASE_URL_IS_NULL.getErrorDesc(ProviderEnum.OLLAMA.name(), model.getType()));
}
return true;
}
@Override
public StreamingChatLanguageModel buildStreamingChat(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return OllamaStreamingChatModel
.builder()
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.temperature(model.getTemperature())
.topP(model.getTopP())
.logRequests(true)
.logResponses(true)
.timeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Ollama streaming chat 配置报错", e);
return null;
}
}
@Override
public ChatLanguageModel buildChatLanguageModel(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return OllamaChatModel
.builder()
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.temperature(model.getTemperature())
.topP(model.getTopP())
.logRequests(true)
.logResponses(true)
.timeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Ollama chat 配置报错", e);
return null;
}
}
@Override
public EmbeddingModel buildEmbedding(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return OllamaEmbeddingModel
.builder()
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.logRequests(true)
.logResponses(true)
.timeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Ollama embedding 配置报错", e);
return null;
}
}
@Override
public ImageModel buildImage(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return null;
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Ollama image 配置报错", e);
return null;
}
}
}

View File

@ -0,0 +1,199 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider.build;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.enums.ChatErrorEnum;
import cn.iocoder.yudao.module.langchat.enums.ProviderEnum;
import cn.iocoder.yudao.module.langchat.properties.LangChatProps;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.openai.OpenAiImageModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* @author tycoding
* @since 2024-08-19 10:08
*/
@Slf4j
@Component
@AllArgsConstructor
public class OpenAIModelBuildHandler implements ModelBuildHandler {
private final LangChatProps props;
/**
* 合并处理支持OpenAI接口的模型
*/
@Override
public boolean whetherCurrentModel(AigcModel model) {
String provider = model.getProvider();
return ProviderEnum.OPENAI.name().equals(provider) ||
ProviderEnum.GEMINI.name().equals(provider) ||
ProviderEnum.CLAUDE.name().equals(provider) ||
ProviderEnum.AZURE_OPENAI.name().equals(provider) ||
ProviderEnum.DOUYIN.name().equals(provider) ||
ProviderEnum.YI.name().equals(provider) ||
ProviderEnum.SILICON.name().equals(provider) ||
ProviderEnum.DEEPSEEK.name().equals(provider) ||
ProviderEnum.SPARK.name().equals(provider)
;
}
@Override
public boolean basicCheck(AigcModel model) {
String apiKey = model.getApiKey();
if (StrUtil.isBlank(apiKey)) {
throw new ServiceException(ChatErrorEnum.API_KEY_IS_NULL.getErrorCode(),
ChatErrorEnum.API_KEY_IS_NULL.getErrorDesc(model.getProvider().toUpperCase(), model.getType()));
}
return true;
}
@Override
public StreamingChatLanguageModel buildStreamingChat(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return OpenAiStreamingChatModel
.builder()
.apiKey(model.getApiKey())
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.maxTokens(model.getResponseLimit())
.temperature(model.getTemperature())
.logRequests(true)
.logResponses(true)
.topP(model.getTopP())
.timeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error(model.getProvider() + " Streaming Chat 模型配置报错", e);
return null;
}
}
@Override
public ChatLanguageModel buildChatLanguageModel(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return OpenAiChatModel
.builder()
.apiKey(model.getApiKey())
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.maxTokens(model.getResponseLimit())
.temperature(model.getTemperature())
.logRequests(true)
.logResponses(true)
.topP(model.getTopP())
.timeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error(model.getProvider() + " Chat 模型配置报错", e);
return null;
}
}
@Override
public EmbeddingModel buildEmbedding(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
OpenAiEmbeddingModel openAiEmbeddingModel = OpenAiEmbeddingModel
.builder()
.apiKey(model.getApiKey())
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.dimensions(model.getDimension())
.logRequests(true)
.logResponses(true)
.dimensions(1024)
.timeout(Duration.ofMinutes(10))
.build();
return openAiEmbeddingModel;
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error(model.getProvider() + " Embedding 模型配置报错", e);
return null;
}
}
@Override
public ImageModel buildImage(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return OpenAiImageModel
.builder()
.apiKey(model.getApiKey())
.baseUrl(model.getBaseUrl())
.modelName(model.getModel())
.size(model.getImageSize())
.quality(model.getImageQuality())
.style(model.getImageStyle())
.logRequests(true)
.logResponses(true)
.timeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error(model.getProvider() + " Image 模型配置报错", e);
return null;
}
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider.build;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.enums.ChatErrorEnum;
import cn.iocoder.yudao.module.langchat.enums.ProviderEnum;
import dev.langchain4j.community.model.qianfan.QianfanChatModel;
import dev.langchain4j.community.model.qianfan.QianfanEmbeddingModel;
import dev.langchain4j.community.model.qianfan.QianfanStreamingChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
/**
* @author GB
* @since 2024-08-19 10:08
*/
@Slf4j
@Component
public class QFanModelBuildHandler implements ModelBuildHandler {
@Override
public boolean whetherCurrentModel(AigcModel model) {
return ProviderEnum.Q_FAN.name().equals(model.getProvider());
}
@Override
public boolean basicCheck(AigcModel model) {
if (StringUtils.isBlank(model.getApiKey())) {
throw new ServiceException(ChatErrorEnum.API_KEY_IS_NULL.getErrorCode(),
ChatErrorEnum.API_KEY_IS_NULL.getErrorDesc(ProviderEnum.Q_FAN.name(), model.getType()));
}
if (StringUtils.isBlank(model.getSecretKey())) {
throw new ServiceException(ChatErrorEnum.SECRET_KEY_IS_NULL.getErrorCode(),
ChatErrorEnum.SECRET_KEY_IS_NULL.getErrorDesc(ProviderEnum.Q_FAN.name(), model.getType()));
}
return true;
}
@Override
public StreamingChatLanguageModel buildStreamingChat(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return QianfanStreamingChatModel
.builder()
.apiKey(model.getApiKey())
.secretKey(model.getSecretKey())
.modelName(model.getModel())
.baseUrl(model.getBaseUrl())
.temperature(model.getTemperature())
.topP(model.getTopP())
.logRequests(true)
.logResponses(true)
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Qianfan streaming chat 配置报错", e);
return null;
}
}
@Override
public ChatLanguageModel buildChatLanguageModel(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return QianfanChatModel
.builder()
.apiKey(model.getApiKey())
.secretKey(model.getSecretKey())
.modelName(model.getModel())
.baseUrl(model.getBaseUrl())
.temperature(model.getTemperature())
.topP(model.getTopP())
.logRequests(true)
.logResponses(true)
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Qianfan chat 配置报错", e);
return null;
}
}
@Override
public EmbeddingModel buildEmbedding(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return QianfanEmbeddingModel
.builder()
.apiKey(model.getApiKey())
.modelName(model.getModel())
.secretKey(model.getSecretKey())
.logRequests(true)
.logResponses(true)
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Qianfan embedding 配置报错", e);
return null;
}
}
@Override
public ImageModel buildImage(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return null;
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("Qianfan image 配置报错", e);
return null;
}
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider.build;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.enums.ChatErrorEnum;
import cn.iocoder.yudao.module.langchat.enums.ProviderEnum;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenEmbeddingModel;
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
/**
* @author GB
* @since 2024-08-19 10:08
*/
@Slf4j
@Component
public class QWenModelBuildHandler implements ModelBuildHandler {
@Override
public boolean whetherCurrentModel(AigcModel model) {
return ProviderEnum.Q_WEN.name().equals(model.getProvider());
}
@Override
public boolean basicCheck(AigcModel model) {
if (StringUtils.isBlank(model.getApiKey())) {
throw new ServiceException(ChatErrorEnum.API_KEY_IS_NULL.getErrorCode(),
ChatErrorEnum.API_KEY_IS_NULL.getErrorDesc(ProviderEnum.Q_WEN.name(), model.getType()));
}
return true;
}
@Override
public StreamingChatLanguageModel buildStreamingChat(AigcModel model) {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
try {
return QwenStreamingChatModel
.builder()
.apiKey(model.getApiKey())
.modelName(model.getModel())
.baseUrl(model.getBaseUrl())
.maxTokens(model.getResponseLimit())
.temperature(Float.parseFloat(model.getTemperature().toString()))
.topP(model.getTopP())
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("qian wen streaming chat 配置报错", e);
return null;
}
}
@Override
public ChatLanguageModel buildChatLanguageModel(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return QwenChatModel
.builder()
.apiKey(model.getApiKey())
.modelName(model.getModel())
.baseUrl(model.getBaseUrl())
.enableSearch(true)
.maxTokens(model.getResponseLimit())
.temperature(Float.parseFloat(model.getTemperature().toString()))
.topP(model.getTopP())
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("qian wen chat 配置报错", e);
return null;
}
}
@Override
public EmbeddingModel buildEmbedding(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return QwenEmbeddingModel
.builder()
.apiKey(model.getApiKey())
.modelName(model.getModel())
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("qian wen embedding 配置报错", e);
return null;
}
}
@Override
public ImageModel buildImage(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return null;
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("qian wen image 配置报错", e);
return null;
}
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider.build;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.enums.ChatErrorEnum;
import cn.iocoder.yudao.module.langchat.enums.ProviderEnum;
import cn.iocoder.yudao.module.langchat.properties.LangChatProps;
import dev.langchain4j.community.model.zhipu.ZhipuAiChatModel;
import dev.langchain4j.community.model.zhipu.ZhipuAiEmbeddingModel;
import dev.langchain4j.community.model.zhipu.ZhipuAiImageModel;
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.image.ImageModel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* @author GB
* @since 2024-08-19
*/
@Slf4j
@Component
@AllArgsConstructor
public class ZhipuModelBuildHandler implements ModelBuildHandler {
private final LangChatProps props;
@Override
public boolean whetherCurrentModel(AigcModel model) {
return ProviderEnum.ZHIPU.name().equals(model.getProvider());
}
@Override
public boolean basicCheck(AigcModel model) {
if (StringUtils.isBlank(model.getApiKey())) {
throw new ServiceException(ChatErrorEnum.API_KEY_IS_NULL.getErrorCode(),
ChatErrorEnum.API_KEY_IS_NULL.getErrorDesc(ProviderEnum.ZHIPU.name(), model.getType()));
}
return true;
}
@Override
public StreamingChatLanguageModel buildStreamingChat(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return ZhipuAiStreamingChatModel
.builder()
.apiKey(model.getApiKey())
.baseUrl(model.getBaseUrl())
.model(model.getModel())
.maxToken(model.getResponseLimit())
.temperature(model.getTemperature())
.topP(model.getTopP())
.logRequests(true)
.logResponses(true)
.callTimeout(Duration.ofMinutes(10))
.connectTimeout(Duration.ofMinutes(10))
.writeTimeout(Duration.ofMinutes(10))
.readTimeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("zhipu streaming chat 配置报错", e);
return null;
}
}
@Override
public ChatLanguageModel buildChatLanguageModel(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return ZhipuAiChatModel
.builder()
.apiKey(model.getApiKey())
.baseUrl(model.getBaseUrl())
.model(model.getModel())
.maxToken(model.getResponseLimit())
.temperature(model.getTemperature())
.topP(model.getTopP())
.logRequests(true)
.logResponses(true)
.callTimeout(Duration.ofMinutes(10))
.connectTimeout(Duration.ofMinutes(10))
.writeTimeout(Duration.ofMinutes(10))
.readTimeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("zhipu chat 配置报错", e);
return null;
}
}
@Override
public EmbeddingModel buildEmbedding(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return ZhipuAiEmbeddingModel
.builder()
.apiKey(model.getApiKey())
.model(model.getModel())
.baseUrl(model.getBaseUrl())
.logRequests(true)
.logResponses(true)
.callTimeout(Duration.ofMinutes(10))
.connectTimeout(Duration.ofMinutes(10))
.writeTimeout(Duration.ofMinutes(10))
.readTimeout(Duration.ofMinutes(10))
.dimensions(1024)
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("zhipu embedding 配置报错", e);
return null;
}
}
@Override
public ImageModel buildImage(AigcModel model) {
try {
if (!whetherCurrentModel(model)) {
return null;
}
if (!basicCheck(model)) {
return null;
}
return ZhipuAiImageModel
.builder()
.apiKey(model.getApiKey())
.model(model.getModel())
.baseUrl(model.getBaseUrl())
.logRequests(true)
.logResponses(true)
.callTimeout(Duration.ofMinutes(10))
.connectTimeout(Duration.ofMinutes(10))
.writeTimeout(Duration.ofMinutes(10))
.readTimeout(Duration.ofMinutes(10))
.build();
} catch (ServiceException e) {
log.error(e.getMessage());
throw e;
} catch (Exception e) {
log.error("zhipu image 配置报错", e);
return null;
}
}
}

View File

@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.langchat.server.framework.security.core;

View File

@ -1,104 +0,0 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.utils;
import lombok.Data;
import org.springframework.http.HttpStatus;
import java.io.Serializable;
/**
* @author tycoding
* @since 2024/1/2
*/
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
private int code = HttpStatus.OK.value();
private String message = HttpStatus.OK.getReasonPhrase();
private T result;
public R() {
super();
}
public R(int code, String message) {
this.code = code;
this.message = message;
}
protected R(T result) {
this.result = result;
}
protected R(HttpStatus httpStatus) {
this.code = httpStatus.value();
this.message = httpStatus.getReasonPhrase();
}
protected R(T result, HttpStatus httpStatus) {
this.result = result;
this.code = httpStatus.value();
this.message = httpStatus.getReasonPhrase();
}
protected R(Throwable e) {
super();
this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
this.message = e.getMessage();
}
public static <T> R<T> ok(T result) {
return new R(result);
}
public static <T> R<T> ok(T result, HttpStatus httpStatus) {
return new R(result, httpStatus);
}
public static <T> R<T> ok() {
return new R<>();
}
public static <T> R<T> ok(String message) {
return new R<>(HttpStatus.OK.value(), message);
}
public static <T> R<T> ok(HttpStatus httpStatus) {
return new R<>(httpStatus);
}
public static <T> R<T> fail(String message) {
return new R<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
}
public static <T> R<T> fail(int code, String message) {
return new R<>(code, message);
}
public static <T> R<T> fail(HttpStatus status) {
return new R<>(status.value(), status.getReasonPhrase());
}
public static <T> R<T> fail(Throwable e) {
return new R<>(e);
}
}

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service;
package cn.iocoder.yudao.module.langchat.service.aigc;
import cn.iocoder.yudao.module.langchat.server.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import com.baomidou.mybatisplus.extension.service.IService;
/**

View File

@ -14,11 +14,11 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service;
package cn.iocoder.yudao.module.langchat.service.aigc;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.server.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;

View File

@ -14,11 +14,11 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service;
package cn.iocoder.yudao.module.langchat.service.aigc;
import cn.iocoder.yudao.module.langchat.server.entity.AigcConversation;
import cn.iocoder.yudao.module.langchat.server.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.entity.AigcConversation;
import cn.iocoder.yudao.module.langchat.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;

View File

@ -14,10 +14,10 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service;
package cn.iocoder.yudao.module.langchat.service.aigc;
import cn.iocoder.yudao.module.langchat.server.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.aigc;
import cn.iocoder.yudao.module.langchat.entity.AigcOss;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
/**
* @author tycoding
* @since 2024/1/4
*/
public interface AigcOssService extends IService<AigcOss> {
AigcOss upload(MultipartFile file, String userId);
}

View File

@ -14,11 +14,11 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service.impl;
package cn.iocoder.yudao.module.langchat.service.aigc.impl;
import cn.iocoder.yudao.module.langchat.server.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcEmbedStoreMapper;
import cn.iocoder.yudao.module.langchat.server.service.AigcEmbedStoreService;
import cn.iocoder.yudao.module.langchat.entity.AigcEmbedStore;
import cn.iocoder.yudao.module.langchat.mapper.AigcEmbedStoreMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcEmbedStoreService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

View File

@ -14,15 +14,15 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service.impl;
package cn.iocoder.yudao.module.langchat.service.aigc.impl;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.server.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.server.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcDocsSliceMapper;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcKnowledgeMapper;
import cn.iocoder.yudao.module.langchat.server.service.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsSliceMapper;
import cn.iocoder.yudao.module.langchat.mapper.AigcKnowledgeMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

View File

@ -14,17 +14,17 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service.impl;
package cn.iocoder.yudao.module.langchat.service.aigc.impl;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.server.entity.AigcConversation;
import cn.iocoder.yudao.module.langchat.server.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.server.entity.SysUser;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcConversationMapper;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcMessageMapper;
import cn.iocoder.yudao.module.langchat.server.mapper.SysUserMapper;
import cn.iocoder.yudao.module.langchat.server.service.AigcMessageService;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.entity.AigcConversation;
import cn.iocoder.yudao.module.langchat.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.entity.SysUser;
import cn.iocoder.yudao.module.langchat.mapper.AigcConversationMapper;
import cn.iocoder.yudao.module.langchat.mapper.AigcMessageMapper;
import cn.iocoder.yudao.module.langchat.mapper.SysUserMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcMessageService;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -60,7 +60,7 @@ public class AigcMessageServiceImpl extends ServiceImpl<AigcMessageMapper, AigcM
@Override
public IPage<AigcConversation> conversationPages(AigcConversation data, QueryPage queryPage) {
Page<AigcConversation> page = new Page<>(queryPage.getPage(), queryPage.getLimit());
Page<AigcConversation> page = new Page<>(queryPage.getPageNo(), queryPage.getPageSize());
Page<AigcConversation> iPage = aigcConversationMapper.selectPage(page, Wrappers.<AigcConversation>lambdaQuery()
.like(!StrUtil.isBlank(data.getTitle()), AigcConversation::getTitle, data.getTitle())
.orderByDesc(AigcConversation::getCreateTime));

View File

@ -14,14 +14,14 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.service.impl;
package cn.iocoder.yudao.module.langchat.service.aigc.impl;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.server.component.ModelTypeEnum;
import cn.iocoder.yudao.module.langchat.server.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.server.mapper.AigcModelMapper;
import cn.iocoder.yudao.module.langchat.server.service.AigcModelService;
import cn.iocoder.yudao.module.langchat.server.utils.QueryPage;
import cn.iocoder.yudao.module.langchat.component.ModelTypeEnum;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.mapper.AigcModelMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -73,7 +73,7 @@ public class AigcModelServiceImpl extends ServiceImpl<AigcModelMapper, AigcModel
@Override
public Page<AigcModel> page(AigcModel data, QueryPage queryPage) {
Page<AigcModel> page = new Page<>(queryPage.getPage(), queryPage.getLimit());
Page<AigcModel> page = new Page<>(queryPage.getPageNo(), queryPage.getPageSize());
Page<AigcModel> iPage = this.page(page, Wrappers.<AigcModel>lambdaQuery().eq(AigcModel::getProvider, data.getProvider()));
iPage.getRecords().forEach(this::hide);
return iPage;

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.oss.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.module.langchat.entity.AigcOss;
import cn.iocoder.yudao.module.langchat.mapper.AigcOssMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcOssService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.Date;
/**
* @author tycoding
* @since 2024/1/4
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AigcOssServiceImpl extends ServiceImpl<AigcOssMapper, AigcOss> implements AigcOssService {
private final FileStorageService fileStorageService;
@Override
public AigcOss upload(MultipartFile file, String userId) {
log.info(">>>>>>>>>>>>>> OSS文件上传开始 {}", file.getOriginalFilename());
FileInfo info = fileStorageService.of(file)
.setPath(DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN))
.upload();
log.info(">>>>>>>>>>>>>> OSS文件上传结束 {} - {}", info.getFilename(), info.getUrl());
AigcOss oss = BeanUtil.copyProperties(info, AigcOss.class);
oss.setOssId(info.getId());
oss.setUserId(userId);
this.save(oss);
return oss;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import java.util.List;
import java.util.Map;
/**
* @author tycoding
* @since 2024/6/6
*/
public interface EmbeddingService {
void clearDocSlices(String docsId);
void embedDocsSlice(AigcDocs data, String url);
List<Map<String, Object>> search(AigcDocs data);
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.EmbeddingR;
import java.util.List;
/**
* @author tycoding
* @since 2024/4/4
*/
public interface LangEmbeddingService {
EmbeddingR embeddingText(ChatReq req);
List<EmbeddingR> embeddingDocs(ChatReq req);
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core.impl;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.EmbeddingR;
import cn.iocoder.yudao.module.langchat.enums.EmbedConst;
import cn.iocoder.yudao.module.langchat.entity.AigcDocs;
import cn.iocoder.yudao.module.langchat.entity.AigcDocsSlice;
import cn.iocoder.yudao.module.langchat.mapper.AigcDocsMapper;
import cn.iocoder.yudao.module.langchat.provider.EmbeddingProvider;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.service.core.EmbeddingService;
import cn.iocoder.yudao.module.langchat.service.core.LangEmbeddingService;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.filter.Filter;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
/**
* @author tycoding
* @since 2024/6/6
*/
@Slf4j
@Service
@AllArgsConstructor
public class EmbeddingServiceImpl implements EmbeddingService {
private final EmbeddingProvider embeddingProvider;
private final LangEmbeddingService langEmbeddingService;
private final AigcKnowledgeService aigcKnowledgeService;
private final AigcDocsMapper aigcDocsMapper;
@Override
@Transactional
public void clearDocSlices(String docsId) {
if (StrUtil.isBlank(docsId)) {
return;
}
// remove from embedding store
List<String> vectorIds = aigcKnowledgeService.listSliceVectorIdsOfDoc(docsId);
if (vectorIds.isEmpty()) {
return;
}
AigcDocs docs = aigcDocsMapper.selectById(docsId);
EmbeddingStore<TextSegment> embeddingStore = embeddingProvider.getEmbeddingStore(docs.getKnowledgeId());
embeddingStore.removeAll(vectorIds);
// remove from docSlice
aigcKnowledgeService.removeSlicesOfDoc(docsId);
}
@Override
public void embedDocsSlice(AigcDocs data, String url) {
List<EmbeddingR> list = langEmbeddingService.embeddingDocs(
new ChatReq()
.setDocsName(data.getName())
.setKnowledgeId(data.getKnowledgeId())
.setUrl(url));
list.forEach(i -> {
aigcKnowledgeService.addDocsSlice(new AigcDocsSlice()
.setKnowledgeId(data.getKnowledgeId())
.setDocsId(data.getId())
.setVectorId(i.getVectorId())
.setName(data.getName())
.setContent(i.getText())
);
});
aigcKnowledgeService.updateDocs(new AigcDocs().setId(data.getId()).setSliceStatus(true).setSliceNum(list.size()));
}
@Override
public List<Map<String, Object>> search(AigcDocs data) {
if (StrUtil.isBlank(data.getKnowledgeId()) || StrUtil.isBlank(data.getContent())) {
return List.of();
}
EmbeddingModel embeddingModel = embeddingProvider.getEmbeddingModel(data.getKnowledgeId());
EmbeddingStore<TextSegment> embeddingStore = embeddingProvider.getEmbeddingStore(data.getKnowledgeId());
Embedding queryEmbedding = embeddingModel.embed(data.getContent()).content();
Filter filter = metadataKey(EmbedConst.KNOWLEDGE).isEqualTo(data.getKnowledgeId());
EmbeddingSearchResult<TextSegment> list = embeddingStore.search(EmbeddingSearchRequest
.builder()
.queryEmbedding(queryEmbedding)
.filter(filter)
.build());
List<Map<String, Object>> result = new ArrayList<>();
list.matches().forEach(i -> {
TextSegment embedded = i.embedded();
Map<String, Object> map = embedded.metadata().toMap();
map.put("text", embedded.text());
result.add(map);
});
return result;
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core.impl;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.EmbeddingR;
import cn.iocoder.yudao.module.langchat.enums.EmbedConst;
import cn.iocoder.yudao.module.langchat.provider.EmbeddingProvider;
import cn.iocoder.yudao.module.langchat.service.core.LangEmbeddingService;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.data.document.loader.UrlDocumentLoader;
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author tycoding
* @since 2024/4/4
*/
@Slf4j
@Service
@AllArgsConstructor
public class LangEmbeddingServiceImpl implements LangEmbeddingService {
private final EmbeddingProvider embeddingProvider;
@Override
public EmbeddingR embeddingText(ChatReq req) {
log.info(">>>>>>>>>>>>>> Text文本向量解析开始KnowledgeId={}, DocsName={}", req.getKnowledgeId(), req.getDocsName());
TextSegment segment = TextSegment.from(req.getMessage(),
Metadata.metadata(EmbedConst.KNOWLEDGE, req.getKnowledgeId()).put(EmbedConst.FILENAME, req.getDocsName()));
EmbeddingModel embeddingModel = embeddingProvider.getEmbeddingModel(req.getKnowledgeId());
EmbeddingStore<TextSegment> embeddingStore = embeddingProvider.getEmbeddingStore(req.getKnowledgeId());
Embedding embedding = embeddingModel.embed(segment).content();
String id = embeddingStore.add(embedding, segment);
log.info(">>>>>>>>>>>>>> Text文本向量解析结束KnowledgeId={}, DocsName={}", req.getKnowledgeId(), req.getDocsName());
return new EmbeddingR().setVectorId(id).setText(segment.text());
}
@Override
public List<EmbeddingR> embeddingDocs(ChatReq req) {
log.info(">>>>>>>>>>>>>> Docs文档向量解析开始KnowledgeId={}, DocsName={}", req.getKnowledgeId(), req.getDocsName());
Document document = UrlDocumentLoader.load(req.getUrl(), new ApacheTikaDocumentParser());
document.metadata().put(EmbedConst.KNOWLEDGE, req.getKnowledgeId()).put(EmbedConst.FILENAME, req.getDocsName());
List<EmbeddingR> list = new ArrayList<>();
try {
DocumentSplitter splitter = EmbeddingProvider.splitter();
List<TextSegment> segments = splitter.split(document);
EmbeddingModel embeddingModel = embeddingProvider.getEmbeddingModel(req.getKnowledgeId());
EmbeddingStore<TextSegment> embeddingStore = embeddingProvider.getEmbeddingStore(req.getKnowledgeId());
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
List<String> ids = embeddingStore.addAll(embeddings, segments);
for (int i = 0; i < ids.size(); i++) {
list.add(new EmbeddingR().setVectorId(ids.get(i)).setText(segments.get(i).text()));
}
} catch (Exception e) {
e.printStackTrace();
}
log.info(">>>>>>>>>>>>>> Docs文档向量解析结束KnowledgeId={}, DocsName={}", req.getKnowledgeId(), req.getDocsName());
return list;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.task;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author GB
* @since 2024-08-22
*/
public class AnalysisThreadPool {
/**
* 根据cpu 数量动态配置核心线程数和最大线程数
*/
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
/**
* 核心线程数
*/
private static final int CORE_PO0L_SIZE = CPU_COUNT + 1;
/**
* 最大线程数
*/
private static final int MAX_POOL_SIZE = 2 * CPU_COUNT + 1;
/**
* 线程队列
*/
private static final int MAX_LIMIT_JOB_SIZE = 1000;
/**
* 非核心线程存活时间1s
*/
private static final int KEEP_ALIVE = 1;
volatile private static ThreadPoolExecutor EXECUTOR = null;
public static ThreadPoolExecutor getThreadPool() {
if (null == EXECUTOR) {
synchronized (AnalysisThreadPool.class) {
if (null == EXECUTOR) {
EXECUTOR = new ThreadPoolExecutor(
CORE_PO0L_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.MICROSECONDS,
new LinkedBlockingDeque<>(MAX_LIMIT_JOB_SIZE),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
super.rejectedExecution(r, e);
}
}
);
}
}
}
return EXECUTOR;
}
public static void execute(Runnable runable) {
getThreadPool().execute(runable);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.task;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
/**
* @author GB
* @since 2024-08-22
*/
public class TaskManager {
private static final ConcurrentHashMap<String, List<Future<?>>> TASK_MAP = new ConcurrentHashMap<>();
/**
* 提交任务
*/
public static void submitTask(String id, Callable<?> function) {
Future<?> future = AnalysisThreadPool.getThreadPool().submit(function);
List<Future<?>> orDefault = TASK_MAP.getOrDefault(id, new ArrayList<>());
orDefault.add(future);
TASK_MAP.put(id, orDefault);
}
/**
* 弹出任务
*/
public void popTaskResult(String id) {
TASK_MAP.remove(id);
}
public int getCount(String id) {
if (TASK_MAP.containsKey(id)) {
Collection<?> collection = TASK_MAP.get(id);
return collection != null ? collection.size() : 0;
}
return 0;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.utils;
package cn.iocoder.yudao.module.langchat.utils;
import cn.hutool.core.lang.Dict;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -33,13 +33,13 @@ public class MybatisUtil {
* @return 格式化后的Map对象
*/
public static Dict getData(IPage<?> page) {
return Dict.create().set("rows", page.getRecords()).set("total", (int) page.getTotal());
return Dict.create().set("list", page.getRecords()).set("total", (int) page.getTotal());
}
/**
* QueryPage对象转换为Page对象
*/
public static <T> IPage<T> wrap(T t, QueryPage query) {
return new Page<T>(query.getPage(), query.getLimit());
return new Page<T>(query.getPageNo(), query.getPageSize());
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.server.utils;
package cn.iocoder.yudao.module.langchat.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -33,10 +33,10 @@ public class QueryPage implements Serializable {
/**
* 当前页
*/
private int page = 1;
private int pageNo = 1;
/**
* 每页的记录数
*/
private int limit = 10;
private int pageSize = 10;
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.utils;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
/**
* @author tycoding
* @since 2024/1/30
*/
public class StreamEmitter {
private final SseEmitter emitter;
public StreamEmitter() {
emitter = new SseEmitter(5 * 60 * 1000L);
}
public SseEmitter get() {
return emitter;
}
public SseEmitter streaming(final ExecutorService executor, Runnable func) {
// ExecutorService executor = Executors.newSingleThreadExecutor();
emitter.onCompletion(() -> {
System.out.println("SseEmitter 完成");
executor.shutdownNow();
});
emitter.onError((e) -> {
System.out.println("SseEmitter 出现错误: " + e.getMessage());
executor.shutdownNow();
});
emitter.onTimeout(() -> {
System.out.println("SseEmitter 超时");
emitter.complete();
executor.shutdownNow();
});
executor.execute(() -> {
try {
func.run();
} catch (Exception e) {
System.out.println("捕获到异常: " + e.getMessage());
emitter.completeWithError(e);
Thread.currentThread().interrupt();
} finally {
if (!executor.isShutdown()) {
executor.shutdownNow();
}
}
});
return emitter;
}
public void send(Object obj) {
try {
emitter.send(obj);
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
public void complete() {
emitter.complete();
}
public void error(String message) {
try {
emitter.send("Error: " + message);
emitter.complete();
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.wrapper;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException;
import org.dromara.x.file.storage.core.file.FileWrapper;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* MultipartFile 文件包装类
*/
@Getter
@Setter
@NoArgsConstructor
public class MultipartFileWrapper implements FileWrapper {
private MultipartFile file;
private String name;
private String contentType;
private InputStream inputStream;
private Long size;
public MultipartFileWrapper(MultipartFile file, String name, String contentType, Long size) {
this.file = file;
this.name = name;
this.contentType = contentType;
this.size = size;
}
@Override
public InputStream getInputStream() throws IOException {
if (inputStream == null) {
inputStream = new BufferedInputStream(file.getInputStream());
}
return inputStream;
}
@Override
public void transferTo(File dest) {
// 在某些 SpringBoot 版本中例如 2.4.6此方法会调用失败
// 此时尝试手动将 InputStream 写入指定文件
// 根据文档来看 MultipartFile 最终都会由框架从临时目录中删除
try {
file.transferTo(dest);
IoUtil.close(inputStream);
} catch (Exception ignored) {
try {
FileUtil.writeFromStream(getInputStream(), dest);
} catch (Exception e) {
throw new FileStorageRuntimeException("文件移动失败", e);
}
}
}
@Override
public boolean supportTransfer() {
return true;
}
public void setSize(Long size) {
this.size = size;
}
public void setName(String name) {
this.name = name;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.wrapper;
import lombok.Getter;
import lombok.Setter;
import org.dromara.x.file.storage.core.file.FileWrapper;
import org.dromara.x.file.storage.core.file.FileWrapperAdapter;
import org.springframework.web.multipart.MultipartFile;
/**
* MultipartFile 文件包装适配器
*/
@Getter
@Setter
public class MultipartFileWrapperAdapter implements FileWrapperAdapter {
@Override
public boolean isSupport(Object source) {
return source instanceof MultipartFile || source instanceof MultipartFileWrapper;
}
@Override
public FileWrapper getFileWrapper(Object source, String name, String contentType, Long size) {
if (source instanceof MultipartFileWrapper) {
return updateFileWrapper((MultipartFileWrapper) source, name, contentType, size);
} else {
MultipartFile file = (MultipartFile) source;
if (name == null) name = file.getOriginalFilename();
if (contentType == null) contentType = file.getContentType();
if (size == null) size = file.getSize();
return new MultipartFileWrapper(file, name, contentType, size);
}
}
}

View File

@ -3,7 +3,7 @@
spring:
cloud:
nacos:
server-addr: 192.168.0.17:8848 # Nacos 服务器地址
server-addr: 192.168.0.15:8848 # Nacos 服务器地址
username: nacos # Nacos 账号
password: nacos # Nacos 密码
discovery: # 【配置中心】配置项

View File

@ -3,16 +3,16 @@
spring:
cloud:
nacos:
server-addr: 192.168.0.17:8848 # Nacos 服务器地址
server-addr: 192.168.0.15:8848 # Nacos 服务器地址
username: nacos # Nacos 账号
password: nacos # Nacos 密码
discovery: # 【配置中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
namespace: yudao # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
namespace: yudao # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
--- #################### 数据库相关配置 ####################
@ -77,7 +77,7 @@ spring:
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 192.168.0.21 # 地址
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
password: 123456 # 密码,建议生产环境开启
@ -211,3 +211,23 @@ justauth:
type: REDIS
prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
langchat:
oss:
default-platform: local
qiniu-kodo:
- platform: qiniu # 存储平台标识七牛qiniu、阿里OSSaliyun-oss、腾讯OSStencent-cos
enable-storage: true # 启用存储
access-key: <ak>
secret-key: <sk>
bucket-name:
domain: / # 访问域名,注意“/”结尾例如http://abc.hn-bkt.clouddn.com/
base-path: langchat/ # 基础路径
local-plus:
- platform: local # 存储平台标识
enable-storage: true #启用存储
enable-access: true #启用访问(线上请使用 Nginx 配置,效率更高)
domain: http://127.0.0.1:8100/ # 访问域名例如“http://127.0.0.1:8030/file/”,注意后面要和 path-patterns 保持一致,“/”结尾,本地存储建议使用相对路径,方便后期更换域名
base-path: langchat/ # 基础路径
path-patterns: /static/** # 访问路径默认本地target目录注意如果使用本地nginx容器此路径要修改为nginx存储路径
storage-path: ./static/ # 存储路径默认本地target目录注意如果使用本地nginx容器此路径要修改为nginx存储路径

View File

@ -3,7 +3,7 @@
spring:
cloud:
nacos:
server-addr: 192.168.0.17:8848 # Nacos 服务器地址
server-addr: 192.168.0.15:8848 # Nacos 服务器地址
username: nacos # Nacos 账号
password: nacos # Nacos 密码
discovery: # 【配置中心】配置项