增加RAG大模型知识库
This commit is contained in:
parent
a2c1ed5390
commit
029d6b9d50
20
README.md
20
README.md
|
@ -1,4 +1,4 @@
|
|||
# Getting Started
|
||||
## Getting Started
|
||||
|
||||
> 声明:此项目只发布于 Github,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号等行为,谨防受骗。
|
||||
|
||||
|
@ -20,19 +20,19 @@
|
|||
|
||||
#### 初始化
|
||||
|
||||
* 初始化数据库
|
||||
初始化数据库
|
||||
|
||||
* 创建数据库aideepin
|
||||
* 执行docs/create.sql
|
||||
* 填充openai的secret_key
|
||||
* 创建数据库aideepin
|
||||
* 执行docs/create.sql
|
||||
* 填充openai的secret\_key
|
||||
|
||||
```
|
||||
```plaintext
|
||||
update adi_sys_config set value = 'my_chatgpt_secret_key' where name = 'secret_key'
|
||||
```
|
||||
|
||||
* 修改配置文件
|
||||
|
||||
* mysql: application-[dev|prod].xml中的spring.datasource
|
||||
* postgresql: application-[dev|prod].xml中的spring.datasource
|
||||
* redis: application-[dev|prod].xml中的spring.data.redis
|
||||
* mail: application.xml中的spring.mail
|
||||
|
||||
|
@ -40,7 +40,7 @@ update adi_sys_config set value = 'my_chatgpt_secret_key' where name = 'secret_k
|
|||
|
||||
* 进入项目
|
||||
|
||||
```
|
||||
```plaintext
|
||||
cd aideepin
|
||||
```
|
||||
|
||||
|
@ -54,14 +54,14 @@ mvn clean package -Dmaven.test.skip=true
|
|||
|
||||
a. jar包启动:
|
||||
|
||||
```
|
||||
```plaintext
|
||||
cd adi-chat/target
|
||||
nohup java -jar -Xms768m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError adi-chat-0.0.1-SNAPSHOT.jar --spring.profiles.active=[dev|prod] dev/null 2>&1 &
|
||||
```
|
||||
|
||||
b. docker启动
|
||||
|
||||
```
|
||||
```plaintext
|
||||
cd adi-chat
|
||||
docker build . -t aideepin:0.0.1
|
||||
docker run -d \
|
||||
|
|
|
@ -11,18 +11,7 @@
|
|||
|
||||
<artifactId>adi-admin</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.moyz</groupId>
|
||||
<artifactId>adi-bootstrap</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.moyz</groupId>
|
||||
<artifactId>adi-common</artifactId>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.moyz.adi.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.entity.SysConfig;
|
||||
import com.moyz.adi.common.service.KnowledgeBaseService;
|
||||
import com.moyz.adi.common.service.SysConfigService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/admin/sys-config")
|
||||
@Validated
|
||||
public class SystemConfigController {
|
||||
|
||||
@Resource
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
public Page<SysConfig> list(String keyword, @NotNull @Min(1) Integer currentPage, @NotNull @Min(10) Integer pageSize) {
|
||||
return sysConfigService.search(keyword, currentPage, pageSize);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,18 @@
|
|||
|
||||
<artifactId>adi-bootstrap</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.moyz</groupId>
|
||||
<artifactId>adi-chat</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.moyz</groupId>
|
||||
<artifactId>adi-admin</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
||||
username: root
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://172.17.18.164:5432/aideepin2?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
||||
username: postgres
|
||||
password: 123456
|
||||
data:
|
||||
redis:
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
||||
username: your-mysql-account
|
||||
password: your-mysql-password
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
||||
username: your-db-account
|
||||
password: your-db-password
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
|
|
|
@ -12,11 +12,6 @@
|
|||
<artifactId>adi-chat</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.moyz</groupId>
|
||||
<artifactId>adi-bootstrap</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.moyz</groupId>
|
||||
<artifactId>adi-common</artifactId>
|
||||
|
|
|
@ -38,7 +38,7 @@ public class FileController {
|
|||
@PostMapping(path = "/file/upload", headers = "content-type=multipart/form-data", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public Map<String, String> upload(@RequestPart(value = "file") MultipartFile file) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("uuid", fileService.writeToLocal(file));
|
||||
result.put("uuid", fileService.writeToLocal(file).getUuid());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package com.moyz.adi.chat.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.dto.KbEditReq;
|
||||
import com.moyz.adi.common.entity.AdiFile;
|
||||
import com.moyz.adi.common.entity.KnowledgeBase;
|
||||
import com.moyz.adi.common.service.KnowledgeBaseService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/knowledge-base")
|
||||
@Validated
|
||||
public class KnowledgeBaseController {
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseService knowledgeBaseService;
|
||||
|
||||
@PostMapping("/saveOrUpdate")
|
||||
public KnowledgeBase saveOrUpdate(@RequestBody KbEditReq kbEditReq) {
|
||||
return knowledgeBaseService.saveOrUpdate(kbEditReq);
|
||||
}
|
||||
|
||||
@PostMapping(path = "/uploadDocs/{uuid}", headers = "content-type=multipart/form-data", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public boolean uploadDocs(@PathVariable String uuid,@RequestParam(value = "embedding", defaultValue = "true") Boolean embedding, @RequestParam("files") MultipartFile[] docs) {
|
||||
knowledgeBaseService.uploadDocs(uuid, embedding, docs);
|
||||
return true;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/upload/{uuid}", headers = "content-type=multipart/form-data", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public AdiFile upload(@PathVariable String uuid, @RequestParam(value = "embedding", defaultValue = "true") Boolean embedding, @RequestParam("file") MultipartFile doc) {
|
||||
return knowledgeBaseService.uploadDoc(uuid, embedding, doc);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public Page<KnowledgeBase> list(String keyword, Boolean includeOthersPublic, @NotNull @Min(1) Integer currentPage, @NotNull @Min(10) Integer pageSize) {
|
||||
return knowledgeBaseService.search(keyword, includeOthersPublic, currentPage, pageSize);
|
||||
}
|
||||
|
||||
@GetMapping("/info/{uuid}")
|
||||
public KnowledgeBase info(@PathVariable String uuid) {
|
||||
return knowledgeBaseService.lambdaQuery()
|
||||
.eq(KnowledgeBase::getUuid, uuid)
|
||||
.eq(KnowledgeBase::getIsDeleted, false)
|
||||
.one();
|
||||
}
|
||||
|
||||
@PostMapping("/del/{uuid}")
|
||||
public boolean softDelete(@PathVariable String uuid) {
|
||||
return knowledgeBaseService.softDelete(uuid);
|
||||
}
|
||||
|
||||
@PostMapping("/embedding/{uuid}")
|
||||
public boolean embedding(@PathVariable String uuid, @RequestParam(defaultValue = "false") Boolean forceAll) {
|
||||
return knowledgeBaseService.embedding(uuid, forceAll);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.moyz.adi.chat.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.dto.KbItemEmbeddingDto;
|
||||
import com.moyz.adi.common.service.KnowledgeBaseEmbeddingService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/knowledge-base-embedding")
|
||||
@Validated
|
||||
public class KnowledgeBaseEmbeddingController {
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseEmbeddingService knowledgeBaseEmbeddingService;
|
||||
|
||||
@GetMapping("/list/{kbItemUuid}")
|
||||
public Page<KbItemEmbeddingDto> list(@PathVariable String kbItemUuid, int currentPage, int pageSize) {
|
||||
return knowledgeBaseEmbeddingService.listByItemUuid(kbItemUuid, currentPage, pageSize);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.moyz.adi.chat.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.dto.KbItemEditReq;
|
||||
import com.moyz.adi.common.dto.KbItemEmbeddingBatchReq;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseItem;
|
||||
import com.moyz.adi.common.service.KnowledgeBaseItemService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/knowledge-base-item")
|
||||
@Validated
|
||||
public class KnowledgeBaseItemController {
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseItemService knowledgeBaseItemService;
|
||||
|
||||
@PostMapping("/saveOrUpdate")
|
||||
public KnowledgeBaseItem saveOrUpdate(@RequestBody KbItemEditReq itemEditReq) {
|
||||
return knowledgeBaseItemService.saveOrUpdate(itemEditReq);
|
||||
}
|
||||
|
||||
@GetMapping("/search")
|
||||
public Page<KnowledgeBaseItem> search(String kbUuid, String keyword, @NotNull @Min(1) Integer currentPage, @NotNull @Min(10) Integer pageSize) {
|
||||
return knowledgeBaseItemService.search(kbUuid, keyword, currentPage, pageSize);
|
||||
}
|
||||
|
||||
@GetMapping("/info/{uuid}")
|
||||
public KnowledgeBaseItem info(@PathVariable String uuid) {
|
||||
return knowledgeBaseItemService.lambdaQuery()
|
||||
.eq(KnowledgeBaseItem::getUuid, uuid)
|
||||
.eq(KnowledgeBaseItem::getIsDeleted, false)
|
||||
.one();
|
||||
}
|
||||
|
||||
@PostMapping("/embedding/{uuid}")
|
||||
public boolean embedding(@PathVariable String uuid) {
|
||||
return knowledgeBaseItemService.checkAndEmbedding(uuid);
|
||||
}
|
||||
|
||||
@PostMapping("/embedding-list")
|
||||
public boolean embeddingBatch(@RequestBody KbItemEmbeddingBatchReq req) {
|
||||
return knowledgeBaseItemService.checkAndEmbedding(req.getUuids());
|
||||
}
|
||||
|
||||
@PostMapping("/del/{uuid}")
|
||||
public boolean softDelete(@PathVariable String uuid) {
|
||||
return knowledgeBaseItemService.softDelete(uuid);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package com.moyz.adi.chat.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.dto.QAReq;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseQaRecord;
|
||||
import com.moyz.adi.common.service.KnowledgeBaseQaRecordService;
|
||||
import com.moyz.adi.common.service.KnowledgeBaseService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "知识库问答controller")
|
||||
@RequestMapping("/knowledge-base/qa/")
|
||||
@RestController
|
||||
public class KnowledgeBaseQAController {
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseService knowledgeBaseService;
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseQaRecordService knowledgeBaseQaRecordService;
|
||||
|
||||
@PostMapping("/ask/{kbUuid}")
|
||||
public KnowledgeBaseQaRecord ask(@PathVariable String kbUuid, @RequestBody @Validated QAReq req) {
|
||||
return knowledgeBaseService.answerAndRecord(kbUuid, req.getQuestion());
|
||||
}
|
||||
|
||||
@GetMapping("/record/search")
|
||||
public Page<KnowledgeBaseQaRecord> list(String kbUuid, String keyword, @NotNull @Min(1) Integer currentPage, @NotNull @Min(10) Integer pageSize) {
|
||||
return knowledgeBaseQaRecordService.search(kbUuid, keyword, currentPage, pageSize);
|
||||
}
|
||||
|
||||
@PostMapping("/record/del/{uuid}")
|
||||
public boolean recordDel(@PathVariable String uuid) {
|
||||
return knowledgeBaseQaRecordService.softDelele(uuid);
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ public class UserController {
|
|||
|
||||
@Operation(summary = "用户信息")
|
||||
@GetMapping("/{uuid}")
|
||||
public void login(@Validated @PathVariable String uuid) {
|
||||
public void info(@Validated @PathVariable String uuid) {
|
||||
log.info(uuid);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import java.util.Collections;
|
|||
|
||||
public class CodeGenerator {
|
||||
public static void main(String[] args) {
|
||||
FastAutoGenerator.create("jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true", "root", "123456")
|
||||
FastAutoGenerator.create("jdbc:postgres://172.17.30.40:5432/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true", "postgres", "postgres")
|
||||
.globalConfig(builder -> {
|
||||
builder.author("moyz") // 设置作者
|
||||
.enableSwagger() // 开启 swagger 模式
|
||||
|
@ -35,7 +35,7 @@ public class CodeGenerator {
|
|||
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatisplus-generatorcode")); // 设置mapperXml生成路径
|
||||
})
|
||||
.strategyConfig(builder -> {
|
||||
builder.addInclude("adi_user,adi_conversation,adi_conversation_message") // 设置需要生成的表名
|
||||
builder.addInclude("adi_knowledge_base_qa_record") // 设置需要生成的表名
|
||||
.addTablePrefix("adi_");
|
||||
builder.mapperBuilder().enableBaseResultMap().enableMapperAnnotation().build();
|
||||
})
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package com.moyz.adi.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||
import com.pgvector.PGvector;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -34,7 +34,7 @@ public class BeanConfig {
|
|||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
log.info("Configuration==create restTemplate");
|
||||
log.info("Configuration:create restTemplate");
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
// 设置建立连接超时时间 毫秒
|
||||
requestFactory.setConnectTimeout(60000);
|
||||
|
@ -50,9 +50,9 @@ public class BeanConfig {
|
|||
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
log.info("Configuration==create objectMapper");
|
||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||||
public ObjectMapper objectMapper() {
|
||||
log.info("Configuration:create objectMapper");
|
||||
ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder().createXmlMapper(false).build();
|
||||
objectMapper.registerModules(LocalDateTimeUtil.getSimpleModule(), new JavaTimeModule(), new Jdk8Module());
|
||||
//设置null值不参与序列化(字段不被显示)
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
|
@ -88,12 +88,18 @@ public class BeanConfig {
|
|||
bean.setDataSource(dataSource);
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
|
||||
// 防止全表更新
|
||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||
bean.setPlugins(interceptor);
|
||||
bean.setMapperLocations(
|
||||
new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
|
||||
MybatisConfiguration configuration = bean.getConfiguration();
|
||||
if(null == configuration){
|
||||
configuration = new MybatisConfiguration();
|
||||
bean.setConfiguration(configuration);
|
||||
}
|
||||
bean.getConfiguration().getTypeHandlerRegistry().register(PGvector.class, PostgresVectorTypeHandler.class);
|
||||
return bean.getObject();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.moyz.adi.common.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties("adi.dev-mock")
|
||||
@Data
|
||||
public class DevMockProperty {
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.moyz.adi.common.config;
|
||||
|
||||
import com.pgvector.PGvector;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class PostgresVectorTypeHandler extends BaseTypeHandler<PGvector> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, PGvector parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
ps.setObject(i, parameter);
|
||||
// ps.setArray(i, ps.getConnection().createArrayOf("float", parameter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGvector getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return toFloatArray(rs.getArray(columnName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGvector getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return toFloatArray(rs.getArray(columnIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGvector getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return toFloatArray(cs.getArray(columnIndex));
|
||||
}
|
||||
|
||||
private PGvector toFloatArray(java.sql.Array sqlArray) throws SQLException {
|
||||
PGvector pGvector = new PGvector(new float[0]);
|
||||
if (sqlArray == null) {
|
||||
return pGvector;
|
||||
}
|
||||
pGvector.setValue(sqlArray.toString());
|
||||
return pGvector;
|
||||
}
|
||||
|
||||
}
|
|
@ -54,8 +54,7 @@ public class AdiConstant {
|
|||
public static final List<String> OPENAI_CREATE_IMAGE_SIZES = List.of("256x256", "512x512", "1024x1024");
|
||||
|
||||
|
||||
|
||||
public static class GenerateImage{
|
||||
public static class GenerateImage {
|
||||
public static final int INTERACTING_METHOD_GENERATE_IMAGE = 1;
|
||||
public static final int INTERACTING_METHOD_EDIT_IMAGE = 2;
|
||||
public static final int INTERACTING_METHOD_VARIATION = 3;
|
||||
|
@ -78,4 +77,6 @@ public class AdiConstant {
|
|||
public static final String QUOTA_BY_IMAGE_MONTHLY = "quota_by_image_monthly";
|
||||
|
||||
}
|
||||
|
||||
public static final String[] POI_DOC_TYPES = {"doc", "docx", "ppt", "pptx", "xls", "xlsx"};
|
||||
}
|
||||
|
|
|
@ -78,4 +78,17 @@ public class RedisKeyConstant {
|
|||
* 值: 用户id,用于校验后续流程中的重置密码使用
|
||||
*/
|
||||
public static final String FIND_MY_PASSWORD = "user:find:password:{0}";
|
||||
|
||||
/**
|
||||
* qa提问次数(每天)
|
||||
* 参数:用户id:日期yyyyMMdd
|
||||
* 值:提问数量
|
||||
*/
|
||||
public static final String AQ_ASK_TIMES = "qa:ask:limit:{0}:{1}";
|
||||
|
||||
/**
|
||||
* 知识库知识点生成数量
|
||||
* 值: 用户id
|
||||
*/
|
||||
public static final String qa_item_create_limit = "aq:item:create:{0}";
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ public class ConvMsgResp {
|
|||
private Long parentMessageId;
|
||||
|
||||
@Schema(title = "对话的消息")
|
||||
@TableField("content")
|
||||
private String content;
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
@Schema(title = "产生该消息的角色:1: 用户,2:系统,3:助手")
|
||||
private String messageRole;
|
||||
private Integer messageRole;
|
||||
|
||||
@Schema(title = "消耗的token数量")
|
||||
private Integer tokens;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package com.moyz.adi.common.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Data
|
||||
@Validated
|
||||
public class KbEditReq {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String uuid;
|
||||
|
||||
@NotBlank
|
||||
private String title;
|
||||
|
||||
private String remark;
|
||||
|
||||
private Boolean isPublic;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.moyz.adi.common.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Data
|
||||
@Validated
|
||||
public class KbItemEditReq {
|
||||
|
||||
private Long id;
|
||||
|
||||
@Min(1)
|
||||
private Long kbId;
|
||||
|
||||
private String kbUuid;
|
||||
|
||||
private String uuid;
|
||||
|
||||
@NotBlank
|
||||
private String title;
|
||||
|
||||
private String brief;
|
||||
|
||||
@NotBlank
|
||||
private String remark;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.moyz.adi.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class KbItemEmbeddingBatchReq {
|
||||
private String[] uuids;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.moyz.adi.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class KbItemEmbeddingDto {
|
||||
private String embeddingId;
|
||||
|
||||
private float[] embedding;
|
||||
|
||||
private String text;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.moyz.adi.common.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Validated
|
||||
@Data
|
||||
public class QAReq {
|
||||
|
||||
@NotBlank
|
||||
private String question;
|
||||
}
|
|
@ -37,7 +37,4 @@ public class AdiFile extends BaseEntity {
|
|||
@TableField(value = "ref_count")
|
||||
private Integer refCount;
|
||||
|
||||
@Schema(title = "是否删除(0:未删除,1:已删除)")
|
||||
@TableField(value = "is_delete")
|
||||
private Boolean isDelete;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,4 @@ public class AiImage extends BaseEntity {
|
|||
|
||||
@TableField("process_status")
|
||||
private Integer processStatus;
|
||||
|
||||
@TableField("is_delete")
|
||||
private Boolean isDelete;
|
||||
}
|
||||
|
|
|
@ -3,16 +3,13 @@ package com.moyz.adi.common.entity;
|
|||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -26,4 +23,7 @@ public class BaseEntity implements Serializable {
|
|||
@TableField(value = "update_time")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(title = "是否删除(0:未删除,1:已删除)")
|
||||
@TableField(value = "is_deleted")
|
||||
private Boolean isDeleted;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,4 @@ public class Conversation extends BaseEntity {
|
|||
@Schema(title = "set the system message to ai, ig: you are a lawyer")
|
||||
@TableField("ai_system_message")
|
||||
private String aiSystemMessage;
|
||||
|
||||
@TableField(value = "is_delete")
|
||||
private Boolean isDelete;
|
||||
}
|
||||
|
|
|
@ -39,12 +39,12 @@ public class ConversationMessage extends BaseEntity {
|
|||
private Long userId;
|
||||
|
||||
@Schema(title = "对话的消息")
|
||||
@TableField("content")
|
||||
private String content;
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
@Schema(title = "产生该消息的角色:1: 用户,2:系统,3:助手")
|
||||
@TableField("message_role")
|
||||
private String messageRole;
|
||||
private Integer messageRole;
|
||||
|
||||
@Schema(title = "消耗的token数量")
|
||||
@TableField("tokens")
|
||||
|
@ -57,7 +57,4 @@ public class ConversationMessage extends BaseEntity {
|
|||
@Schema(name = "上下文理解中携带的消息对数量(提示词及回复)")
|
||||
@TableField("understand_context_msg_pair_num")
|
||||
private Integer understandContextMsgPairNum;
|
||||
|
||||
@TableField(value = "is_delete")
|
||||
private Boolean isDelete;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.moyz.adi.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("adi_knowledge_base")
|
||||
@Schema(title = "知识库实体", description = "知识库表")
|
||||
public class KnowledgeBase extends BaseEntity {
|
||||
|
||||
@Schema(title = "uuid")
|
||||
@TableField("uuid")
|
||||
private String uuid;
|
||||
|
||||
@Schema(title = "名称")
|
||||
@TableField("title")
|
||||
private String title;
|
||||
|
||||
@Schema(title = "描述")
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
@Schema(title = "是否公开")
|
||||
@TableField("is_public")
|
||||
private Boolean isPublic;
|
||||
|
||||
@Schema(title = "所属人id")
|
||||
@TableField("owner_id")
|
||||
private Long ownerId;
|
||||
|
||||
@Schema(title = "所属人名称")
|
||||
@TableField("owner_name")
|
||||
private String ownerName;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.moyz.adi.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pgvector.PGvector;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("adi_knowledge_base_embedding")
|
||||
@Schema(title = "知识库-嵌入实体", description = "知识库嵌入表")
|
||||
public class KnowledgeBaseEmbedding extends BaseEntity {
|
||||
|
||||
@Schema(title = "embedding uuid")
|
||||
@TableField("embedding")
|
||||
private String embeddingId;
|
||||
|
||||
@Schema(title = "embedding")
|
||||
@TableField("embedding")
|
||||
private PGvector embedding;
|
||||
|
||||
@Schema(title = "对应的文档")
|
||||
@TableField("text")
|
||||
private String text;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.moyz.adi.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("adi_knowledge_base_item")
|
||||
@Schema(title = "知识库条目实体", description = "知识库条目表")
|
||||
public class KnowledgeBaseItem extends BaseEntity {
|
||||
|
||||
@Schema(title = "知识库id")
|
||||
@TableField("kb_id")
|
||||
private Long kbId;
|
||||
|
||||
@Schema(title = "知识库uuid")
|
||||
@TableField("kb_uuid")
|
||||
private String kbUuid;
|
||||
|
||||
@Schema(title = "名称")
|
||||
@TableField("source_file_id")
|
||||
private Long sourceFileId;
|
||||
|
||||
@Schema(title = "uuid")
|
||||
@TableField("uuid")
|
||||
private String uuid;
|
||||
|
||||
@Schema(title = "标题")
|
||||
@TableField("title")
|
||||
private String title;
|
||||
|
||||
@Schema(title = "内容摘要")
|
||||
@TableField("brief")
|
||||
private String brief;
|
||||
|
||||
@Schema(title = "内容")
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
@Schema(title = "是否已向量化")
|
||||
@TableField("is_embedded")
|
||||
private Boolean isEmbedded;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.moyz.adi.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
@Data
|
||||
@TableName("adi_knowledge_base_qa_record")
|
||||
@Schema(title = "知识库问答记录实体", description = "知识库问答记录表")
|
||||
public class KnowledgeBaseQaRecord extends BaseEntity {
|
||||
|
||||
@Schema(title = "uuid")
|
||||
@TableField(value = "uuid", jdbcType = JdbcType.VARCHAR)
|
||||
private String uuid;
|
||||
|
||||
@Schema(title = "知识库id")
|
||||
@TableField("kb_id")
|
||||
private Long kbId;
|
||||
|
||||
@Schema(title = "知识库uuid")
|
||||
@TableField("kb_uuid")
|
||||
private String kbUuid;
|
||||
|
||||
@Schema(title = "来源文档id,以逗号隔开")
|
||||
@TableField("source_file_ids")
|
||||
private String sourceFileIds;
|
||||
|
||||
@Schema(title = "问题")
|
||||
@TableField("question")
|
||||
private String question;
|
||||
|
||||
@Schema(title = "答案")
|
||||
@TableField("answer")
|
||||
private String answer;
|
||||
|
||||
@Schema(title = "提问用户id")
|
||||
@TableField("user_id")
|
||||
private Long userId;
|
||||
}
|
|
@ -22,7 +22,4 @@ public class Prompt extends BaseEntity {
|
|||
@TableField(value = "prompt")
|
||||
private String prompt;
|
||||
|
||||
@Schema(title = "是否删除(0:未删除,1:已删除)")
|
||||
@TableField(value = "is_delete")
|
||||
private Boolean isDelete;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,4 @@ public class SysConfig extends BaseEntity {
|
|||
@Schema(title = "配置项的值")
|
||||
private String value;
|
||||
|
||||
@Schema(title = "是否删除(0:未删除,1:已删除)")
|
||||
@TableField(value = "is_delete")
|
||||
private Boolean isDelete;
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ public class User extends BaseEntity {
|
|||
@TableField("active_time")
|
||||
private LocalDateTime activeTime;
|
||||
|
||||
@TableField("is_delete")
|
||||
private Boolean isDelete;
|
||||
@Schema(title = "是否管理员(0:否,1:是)")
|
||||
@TableField(value = "is_admin")
|
||||
private Boolean isAdmin;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.moyz.adi.common.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ChatMessageRoleEnum implements BaseEnum {
|
||||
|
||||
USER(1, "user"),
|
||||
SYSTEM(2, "system"),
|
||||
|
||||
ASSISTANT(3, "assistant");
|
||||
|
||||
private final Integer value;
|
||||
private final String desc;
|
||||
}
|
|
@ -17,6 +17,11 @@ public enum ErrorEnum {
|
|||
A_REGISTER_USER_EXIST("A0013", "账号已经存在,请使用账号密码登录"),
|
||||
A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
|
||||
A_USER_WAIT_CONFIRM("A0015", "用户未激活"),
|
||||
A_USER_NOT_AUTH("A0016", "用户无权限"),
|
||||
A_DATA_NOT_FOUND("A0017", "数据不存在"),
|
||||
A_UPLOAD_FAIL("A0018", "上传失败"),
|
||||
A_QA_ASK_LIMIT("A0019", "请求次数太多"),
|
||||
A_QA_ITEM_LIMIT("A0020", "知识点生成已超额度"),
|
||||
B_UNCAUGHT_ERROR("B0001", "未捕捉异常"),
|
||||
B_COMMON_ERROR("B0002", "业务出错"),
|
||||
B_GLOBAL_ERROR("B0003", "全局异常"),
|
||||
|
|
|
@ -29,7 +29,7 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
|||
public class TokenFilter extends OncePerRequestFilter {
|
||||
|
||||
public static final String[] EXCLUDE_API = {
|
||||
"/auth/",
|
||||
"/auth/"
|
||||
};
|
||||
|
||||
@Resource
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
package com.moyz.adi.common.helper;
|
||||
|
||||
import com.moyz.adi.common.util.AdiPgVectorEmbeddingStore;
|
||||
import dev.langchain4j.data.document.DocumentSplitter;
|
||||
import dev.langchain4j.data.document.splitter.DocumentSplitters;
|
||||
import dev.langchain4j.data.embedding.Embedding;
|
||||
import dev.langchain4j.data.message.AiMessage;
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.model.chat.ChatLanguageModel;
|
||||
import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.model.input.Prompt;
|
||||
import dev.langchain4j.model.input.PromptTemplate;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import dev.langchain4j.model.openai.OpenAiTokenizer;
|
||||
import dev.langchain4j.store.embedding.EmbeddingMatch;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class EmbeddingHelper {
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String dataBaseUrl;
|
||||
|
||||
@Value("${spring.datasource.username}")
|
||||
private String dataBaseUserName;
|
||||
|
||||
@Value("${spring.datasource.password}")
|
||||
private String dataBasePassword;
|
||||
|
||||
@Value("${openai.proxy.enable:false}")
|
||||
private boolean proxyEnable;
|
||||
|
||||
@Value("${openai.proxy.host:0}")
|
||||
private String proxyHost;
|
||||
|
||||
@Value("${openai.proxy.http-port:0}")
|
||||
private int proxyHttpPort;
|
||||
|
||||
private static final PromptTemplate promptTemplate = PromptTemplate.from("尽可能准确地回答下面的问题: {{question}}\n\n根据以下知识库的内容:\n{{information}}");
|
||||
|
||||
@Resource
|
||||
private OpenAiHelper openAiHelper;
|
||||
|
||||
private EmbeddingModel embeddingModel;
|
||||
|
||||
private EmbeddingStore<TextSegment> embeddingStore;
|
||||
|
||||
private ChatLanguageModel chatLanguageModel;
|
||||
|
||||
public void init() {
|
||||
log.info("initEmbeddingModel");
|
||||
embeddingModel = new AllMiniLmL6V2EmbeddingModel();
|
||||
embeddingStore = initEmbeddingStore();
|
||||
chatLanguageModel = initChatLanguageModel();
|
||||
}
|
||||
|
||||
private EmbeddingStore<TextSegment> initEmbeddingStore() {
|
||||
// 正则表达式匹配
|
||||
String regex = "jdbc:postgresql://([^:/]+):(\\d+)/(\\w+).+";
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(dataBaseUrl);
|
||||
|
||||
String host = "";
|
||||
String port = "";
|
||||
String databaseName = "";
|
||||
if (matcher.matches()) {
|
||||
host = matcher.group(1);
|
||||
port = matcher.group(2);
|
||||
databaseName = matcher.group(3);
|
||||
|
||||
System.out.println("Host: " + host);
|
||||
System.out.println("Port: " + port);
|
||||
System.out.println("Database: " + databaseName);
|
||||
} else {
|
||||
throw new RuntimeException("parse url error");
|
||||
}
|
||||
AdiPgVectorEmbeddingStore embeddingStore = AdiPgVectorEmbeddingStore.builder()
|
||||
.host(host)
|
||||
.port(Integer.parseInt(port))
|
||||
.database(databaseName)
|
||||
.user(dataBaseUserName)
|
||||
.password(dataBasePassword)
|
||||
.dimension(384)
|
||||
.createTable(true)
|
||||
.dropTableFirst(false)
|
||||
.table("adi_knowledge_base_embedding")
|
||||
.build();
|
||||
return embeddingStore;
|
||||
}
|
||||
|
||||
private ChatLanguageModel initChatLanguageModel() {
|
||||
OpenAiChatModel.OpenAiChatModelBuilder builder = OpenAiChatModel.builder().apiKey(openAiHelper.getSecretKey());
|
||||
if (proxyEnable) {
|
||||
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
||||
builder.proxy(proxy);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public EmbeddingStoreIngestor getEmbeddingStoreIngestor() {
|
||||
DocumentSplitter documentSplitter = DocumentSplitters.recursive(1000, 0, new OpenAiTokenizer(GPT_3_5_TURBO));
|
||||
EmbeddingStoreIngestor embeddingStoreIngestor = EmbeddingStoreIngestor.builder()
|
||||
.documentSplitter(documentSplitter)
|
||||
.embeddingModel(embeddingModel)
|
||||
.embeddingStore(embeddingStore)
|
||||
.build();
|
||||
return embeddingStoreIngestor;
|
||||
}
|
||||
|
||||
public String findAnswer(String kbUuid, String question) {
|
||||
|
||||
// Embed the question
|
||||
Embedding questionEmbedding = embeddingModel.embed(question).content();
|
||||
|
||||
// Find relevant embeddings in embedding store by semantic similarity
|
||||
// You can play with parameters below to find a sweet spot for your specific use case
|
||||
int maxResults = 3;
|
||||
double minScore = 0.6;
|
||||
List<EmbeddingMatch<TextSegment>> relevantEmbeddings = ((AdiPgVectorEmbeddingStore) embeddingStore).findRelevantByKbUuid(kbUuid, questionEmbedding, maxResults, minScore);
|
||||
|
||||
// Create a prompt for the model that includes question and relevant embeddings
|
||||
String information = relevantEmbeddings.stream()
|
||||
.map(match -> match.embedded().text())
|
||||
.collect(joining("\n\n"));
|
||||
|
||||
if (StringUtils.isBlank(information)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
Prompt prompt = promptTemplate.apply(Map.of("question", question, "information", Matcher.quoteReplacement(information)));
|
||||
|
||||
AiMessage aiMessage = chatLanguageModel.generate(prompt.toUserMessage()).content();
|
||||
|
||||
// See an answer from the model
|
||||
return aiMessage.text();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +1,34 @@
|
|||
package com.moyz.adi.common.helper;
|
||||
|
||||
import com.didalgo.gpt3.ChatFormatDescriptor;
|
||||
import com.didalgo.gpt3.Encoding;
|
||||
import com.didalgo.gpt3.GPT3Tokenizer;
|
||||
import com.didalgo.gpt3.TokenCount;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.moyz.adi.common.cosntant.AdiConstant;
|
||||
import com.moyz.adi.common.base.ThreadContext;
|
||||
import com.moyz.adi.common.entity.AiImage;
|
||||
import com.moyz.adi.common.entity.User;
|
||||
import com.moyz.adi.common.enums.ErrorEnum;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.model.AnswerMeta;
|
||||
import com.moyz.adi.common.model.ChatMeta;
|
||||
import com.moyz.adi.common.model.QuestionMeta;
|
||||
import com.moyz.adi.common.interfaces.IChatAssistant;
|
||||
import com.moyz.adi.common.service.FileService;
|
||||
import com.moyz.adi.common.service.SysConfigService;
|
||||
import com.moyz.adi.common.util.FileUtil;
|
||||
import com.moyz.adi.common.util.ImageUtil;
|
||||
import com.moyz.adi.common.util.JsonUtil;
|
||||
import com.moyz.adi.common.util.TriConsumer;
|
||||
import com.moyz.adi.common.vo.AnswerMeta;
|
||||
import com.moyz.adi.common.vo.ChatMeta;
|
||||
import com.moyz.adi.common.vo.QuestionMeta;
|
||||
import com.moyz.adi.common.vo.SseAskParams;
|
||||
import com.theokanning.openai.OpenAiApi;
|
||||
import com.theokanning.openai.completion.chat.ChatCompletionChoice;
|
||||
import com.theokanning.openai.completion.chat.ChatCompletionRequest;
|
||||
import com.theokanning.openai.completion.chat.ChatMessage;
|
||||
import com.theokanning.openai.image.*;
|
||||
import com.theokanning.openai.image.CreateImageEditRequest;
|
||||
import com.theokanning.openai.image.CreateImageVariationRequest;
|
||||
import com.theokanning.openai.image.ImageResult;
|
||||
import com.theokanning.openai.service.OpenAiService;
|
||||
import dev.langchain4j.data.image.Image;
|
||||
import dev.langchain4j.memory.ChatMemory;
|
||||
import dev.langchain4j.model.image.ImageModel;
|
||||
import dev.langchain4j.model.openai.OpenAiImageModel;
|
||||
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||
import dev.langchain4j.model.output.Response;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
@ -35,19 +39,23 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|||
import retrofit2.Retrofit;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.moyz.adi.common.cosntant.AdiConstant.OPENAI_CREATE_IMAGE_RESP_FORMATS_URL;
|
||||
import static com.moyz.adi.common.cosntant.AdiConstant.OPENAI_CREATE_IMAGE_SIZES;
|
||||
import static com.theokanning.openai.service.OpenAiService.defaultClient;
|
||||
import static com.theokanning.openai.service.OpenAiService.defaultRetrofit;
|
||||
import static dev.ai4j.openai4j.image.ImageModel.DALL_E_SIZE_1024_x_1024;
|
||||
import static dev.ai4j.openai4j.image.ImageModel.DALL_E_SIZE_512_x_512;
|
||||
import static dev.langchain4j.model.openai.OpenAiModelName.DALL_E_2;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
|
@ -62,21 +70,23 @@ public class OpenAiHelper {
|
|||
@Value("${openai.proxy.http-port:0}")
|
||||
private int proxyHttpPort;
|
||||
|
||||
@Value("${local.images}")
|
||||
private String localImagesPath;
|
||||
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public OpenAiService getOpenAiService(User user) {
|
||||
public String getSecretKey() {
|
||||
String secretKey = SysConfigService.getSecretKey();
|
||||
String userSecretKey = user.getSecretKey();
|
||||
if (StringUtils.isNotBlank(userSecretKey)) {
|
||||
secretKey = userSecretKey;
|
||||
User user = ThreadContext.getCurrentUser();
|
||||
if (null != user && StringUtils.isNotBlank(user.getSecretKey())) {
|
||||
secretKey = user.getSecretKey();
|
||||
}
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public OpenAiService getOpenAiService() {
|
||||
String secretKey = getSecretKey();
|
||||
if (proxyEnable) {
|
||||
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
||||
OkHttpClient client = defaultClient(secretKey, Duration.of(60, ChronoUnit.SECONDS))
|
||||
|
@ -90,102 +100,121 @@ public class OpenAiHelper {
|
|||
return new OpenAiService(secretKey, Duration.of(60, ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send http request to openai server <br/>
|
||||
* Calculate token
|
||||
*
|
||||
* @param user
|
||||
* @param regenerateQuestionUuid
|
||||
* @param chatMessageList
|
||||
* @param sseEmitter
|
||||
* @param consumer
|
||||
*/
|
||||
public void sseAsk(User user, String regenerateQuestionUuid, List<ChatMessage> chatMessageList, SseEmitter sseEmitter, TriConsumer<String, QuestionMeta, AnswerMeta> consumer) {
|
||||
final int[] answerTokens = {0};
|
||||
StringBuilder response = new StringBuilder();
|
||||
OpenAiService service = getOpenAiService(user);
|
||||
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
|
||||
.builder()
|
||||
.model(AdiConstant.DEFAULT_MODEL)
|
||||
.messages(chatMessageList)
|
||||
.n(1)
|
||||
.logitBias(new HashMap<>())
|
||||
public IChatAssistant getChatAssistant(ChatMemory chatMemory) {
|
||||
String secretKey = getSecretKey();
|
||||
OpenAiStreamingChatModel.OpenAiStreamingChatModelBuilder builder = OpenAiStreamingChatModel.builder();
|
||||
if (proxyEnable) {
|
||||
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
||||
builder.proxy(proxy);
|
||||
}
|
||||
builder.apiKey(secretKey).timeout(Duration.of(60, ChronoUnit.SECONDS));
|
||||
AiServices<IChatAssistant> serviceBuilder = AiServices.builder(IChatAssistant.class)
|
||||
.streamingChatLanguageModel(builder.build());
|
||||
if (null != chatMemory) {
|
||||
serviceBuilder.chatMemory(chatMemory);
|
||||
}
|
||||
return serviceBuilder.build();
|
||||
}
|
||||
|
||||
public ImageModel getImageModel(User user, String size) {
|
||||
String secretKey = getSecretKey();
|
||||
if (proxyEnable) {
|
||||
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
||||
return OpenAiImageModel.builder()
|
||||
.modelName(DALL_E_2)
|
||||
.apiKey(secretKey)
|
||||
.user(user.getUuid())
|
||||
.responseFormat(OPENAI_CREATE_IMAGE_RESP_FORMATS_URL)
|
||||
.size(StringUtils.defaultString(size, DALL_E_SIZE_512_x_512))
|
||||
.logRequests(true)
|
||||
.logResponses(true)
|
||||
.withPersisting(false)
|
||||
.maxRetries(2)
|
||||
.proxy(proxy)
|
||||
.build();
|
||||
service.streamChatCompletion(chatCompletionRequest)
|
||||
.doOnError(onError -> {
|
||||
log.error("openai error", onError);
|
||||
sseEmitter.send(SseEmitter.event().name("error").data(onError.getMessage()));
|
||||
sseEmitter.complete();
|
||||
}).subscribe(completionChunk -> {
|
||||
answerTokens[0]++;
|
||||
List<ChatCompletionChoice> choices = completionChunk.getChoices();
|
||||
String content = choices.get(0).getMessage().getContent();
|
||||
}
|
||||
return OpenAiImageModel.builder()
|
||||
.modelName(DALL_E_2)
|
||||
.apiKey(secretKey)
|
||||
.user(user.getUuid())
|
||||
.responseFormat(OPENAI_CREATE_IMAGE_RESP_FORMATS_URL)
|
||||
.size(StringUtils.defaultString(size, DALL_E_SIZE_512_x_512))
|
||||
.logRequests(true)
|
||||
.logResponses(true)
|
||||
.withPersisting(false)
|
||||
.maxRetries(2)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send http request to llm server
|
||||
*/
|
||||
public void sseAsk(SseAskParams params, TriConsumer<String, QuestionMeta, AnswerMeta> consumer) {
|
||||
IChatAssistant chatAssistant = getChatAssistant(params.getChatMemory());
|
||||
TokenStream tokenStream;
|
||||
if (StringUtils.isNotBlank(params.getSystemMessage())) {
|
||||
tokenStream = chatAssistant.chat(params.getSystemMessage(), params.getUserMessage());
|
||||
} else {
|
||||
tokenStream = chatAssistant.chat(params.getUserMessage());
|
||||
}
|
||||
tokenStream.onNext((content) -> {
|
||||
log.info("get content:{}", content);
|
||||
if (null == content && response.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (null == content || AdiConstant.OPENAI_MESSAGE_DONE_FLAG.equals(content)) {
|
||||
log.info("OpenAI返回数据结束了");
|
||||
sseEmitter.send(AdiConstant.OPENAI_MESSAGE_DONE_FLAG);
|
||||
|
||||
|
||||
GPT3Tokenizer tokenizer = new GPT3Tokenizer(Encoding.CL100K_BASE);
|
||||
int questionTokens = 0;
|
||||
//加空格配合前端的fetchEventSource进行解析,见https://github.com/Azure/fetch-event-source/blob/45ac3cfffd30b05b79fbf95c21e67d4ef59aa56a/src/parse.ts#L129-L133
|
||||
try {
|
||||
questionTokens = TokenCount.fromMessages(chatMessageList, tokenizer, ChatFormatDescriptor.forModel(AdiConstant.DEFAULT_MODEL));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("该模型的token无法统计,model:{}", AdiConstant.DEFAULT_MODEL);
|
||||
params.getSseEmitter().send(" " + content);
|
||||
} catch (IOException e) {
|
||||
log.error("stream onNext error", e);
|
||||
}
|
||||
System.out.println("requestTokens:" + questionTokens);
|
||||
System.out.println("返回内容:" + response);
|
||||
|
||||
String questionUuid = StringUtils.isNotBlank(regenerateQuestionUuid) ? regenerateQuestionUuid : UUID.randomUUID().toString().replace("-", "");
|
||||
QuestionMeta questionMeta = new QuestionMeta(questionTokens, questionUuid);
|
||||
AnswerMeta answerMeta = new AnswerMeta(answerTokens[0], UUID.randomUUID().toString().replace("-", ""));
|
||||
})
|
||||
.onComplete((response) -> {
|
||||
log.info("返回数据结束了:{}", response);
|
||||
String questionUuid = StringUtils.isNotBlank(params.getRegenerateQuestionUuid()) ? params.getRegenerateQuestionUuid() : UUID.randomUUID().toString().replace("-", "");
|
||||
QuestionMeta questionMeta = new QuestionMeta(response.tokenUsage().inputTokenCount(), questionUuid);
|
||||
AnswerMeta answerMeta = new AnswerMeta(response.tokenUsage().outputTokenCount(), UUID.randomUUID().toString().replace("-", ""));
|
||||
ChatMeta chatMeta = new ChatMeta(questionMeta, answerMeta);
|
||||
// String meta = JsonUtil.toJson(chatMeta).replaceAll("\r\n", "");
|
||||
String meta = JsonUtil.toJson(chatMeta).replaceAll("\r\n", "");
|
||||
log.info("meta:" + meta);
|
||||
sseEmitter.send(" [META]" + meta);
|
||||
try {
|
||||
params.getSseEmitter().send(" [META]" + meta);
|
||||
} catch (IOException e) {
|
||||
log.error("stream onComplete error", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// close eventSourceEmitter after tokens was calculated
|
||||
sseEmitter.complete();
|
||||
consumer.accept(response.toString(), questionMeta, answerMeta);
|
||||
return;
|
||||
params.getSseEmitter().complete();
|
||||
consumer.accept(response.content().text(), questionMeta, answerMeta);
|
||||
})
|
||||
.onError((error) -> {
|
||||
log.error("stream error", error);
|
||||
try {
|
||||
params.getSseEmitter().send(SseEmitter.event().name("error").data(error.getMessage()));
|
||||
} catch (IOException e) {
|
||||
log.error("sse error", e);
|
||||
}
|
||||
//加空格配合前端的fetchEventSource进行解析,见https://github.com/Azure/fetch-event-source/blob/45ac3cfffd30b05b79fbf95c21e67d4ef59aa56a/src/parse.ts#L129-L133
|
||||
sseEmitter.send(" " + content);
|
||||
response.append(content);
|
||||
});
|
||||
|
||||
|
||||
System.out.println("返回内容1111:" + response);
|
||||
params.getSseEmitter().complete();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
public List<Image> createImage(User user, AiImage aiImage) {
|
||||
public List<String> createImage(User user, AiImage aiImage) {
|
||||
if (aiImage.getGenerateNumber() < 1 || aiImage.getGenerateNumber() > 10) {
|
||||
throw new BaseException(ErrorEnum.A_IMAGE_NUMBER_ERROR);
|
||||
}
|
||||
if (!OPENAI_CREATE_IMAGE_SIZES.contains(aiImage.getGenerateSize())) {
|
||||
throw new BaseException(ErrorEnum.A_IMAGE_SIZE_ERROR);
|
||||
}
|
||||
OpenAiService service = getOpenAiService(user);
|
||||
CreateImageRequest createImageRequest = new CreateImageRequest();
|
||||
createImageRequest.setPrompt(aiImage.getPrompt());
|
||||
createImageRequest.setN(aiImage.getGenerateNumber());
|
||||
createImageRequest.setSize(aiImage.getGenerateSize());
|
||||
createImageRequest.setResponseFormat(OPENAI_CREATE_IMAGE_RESP_FORMATS_URL);
|
||||
createImageRequest.setUser(user.getUuid());
|
||||
ImageModel imageModel = getImageModel(user, aiImage.getGenerateSize());
|
||||
try {
|
||||
ImageResult imageResult = service.createImage(createImageRequest);
|
||||
log.info("createImage response:{}", imageResult);
|
||||
return imageResult.getData();
|
||||
Response<List<Image>> response = imageModel.generate(aiImage.getPrompt(), aiImage.getGenerateNumber());
|
||||
log.info("createImage response:{}", response);
|
||||
return response.content().stream().map(item -> item.url().toString()).collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
log.error("create image error", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<Image> editImage(User user, AiImage aiImage) {
|
||||
public List<String> editImage(User user, AiImage aiImage) {
|
||||
File originalFile = new File(fileService.getImagePath(aiImage.getOriginalImage()));
|
||||
File maskFile = null;
|
||||
if (StringUtils.isNotBlank(aiImage.getMaskImage())) {
|
||||
|
@ -193,7 +222,7 @@ public class OpenAiHelper {
|
|||
}
|
||||
//如果不是RGBA类型的图片,先转成RGBA
|
||||
File rgbaOriginalImage = ImageUtil.rgbConvertToRgba(originalFile, fileService.getTmpImagesPath(aiImage.getOriginalImage()));
|
||||
OpenAiService service = getOpenAiService(user);
|
||||
OpenAiService service = getOpenAiService();
|
||||
CreateImageEditRequest request = new CreateImageEditRequest();
|
||||
request.setPrompt(aiImage.getPrompt());
|
||||
request.setN(aiImage.getGenerateNumber());
|
||||
|
@ -203,16 +232,16 @@ public class OpenAiHelper {
|
|||
try {
|
||||
ImageResult imageResult = service.createImageEdit(request, rgbaOriginalImage, maskFile);
|
||||
log.info("editImage response:{}", imageResult);
|
||||
return imageResult.getData();
|
||||
return imageResult.getData().stream().map(item -> item.getUrl()).collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
log.error("edit image error", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<Image> createImageVariation(User user, AiImage aiImage) {
|
||||
public List<String> createImageVariation(User user, AiImage aiImage) {
|
||||
File imagePath = new File(fileService.getImagePath(aiImage.getOriginalImage()));
|
||||
OpenAiService service = getOpenAiService(user);
|
||||
OpenAiService service = getOpenAiService();
|
||||
CreateImageVariationRequest request = new CreateImageVariationRequest();
|
||||
request.setN(aiImage.getGenerateNumber());
|
||||
request.setSize(aiImage.getGenerateSize());
|
||||
|
@ -221,7 +250,7 @@ public class OpenAiHelper {
|
|||
try {
|
||||
ImageResult imageResult = service.createImageVariation(request, imagePath);
|
||||
log.info("createImageVariation response:{}", imageResult);
|
||||
return imageResult.getData();
|
||||
return imageResult.getData().stream().map(item -> item.getUrl()).collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
log.error("image variation error", e);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.moyz.adi.common.helper;
|
|||
|
||||
import com.moyz.adi.common.entity.User;
|
||||
import com.moyz.adi.common.enums.ErrorEnum;
|
||||
import com.moyz.adi.common.model.CostStat;
|
||||
import com.moyz.adi.common.vo.CostStat;
|
||||
import com.moyz.adi.common.service.UserDayCostService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.moyz.adi.common.helper;
|
||||
|
||||
import com.moyz.adi.common.model.RequestRateLimit;
|
||||
import com.moyz.adi.common.vo.RequestRateLimit;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.moyz.adi.common.interfaces;
|
||||
|
||||
import dev.langchain4j.service.SystemMessage;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import dev.langchain4j.service.UserMessage;
|
||||
import dev.langchain4j.service.V;
|
||||
|
||||
public interface IChatAssistant {
|
||||
|
||||
@SystemMessage("{{sm}}")
|
||||
TokenStream chat(@V("sm") String systemMessage, @UserMessage String prompt);
|
||||
|
||||
TokenStream chat(@UserMessage String prompt);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.moyz.adi.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseEmbedding;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface KnowledgeBaseEmbeddingMapper extends BaseMapper<KnowledgeBaseEmbedding> {
|
||||
|
||||
Page<KnowledgeBaseEmbedding> selectByItemUuid(Page<KnowledgeBaseEmbedding> page, @Param("kbItemUuid") String uuid);
|
||||
|
||||
boolean deleteByItemUuid(@Param("kbItemUuid") String uuid);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.moyz.adi.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface KnowledgeBaseItemMapper extends BaseMapper<KnowledgeBaseItem> {
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.moyz.adi.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.moyz.adi.common.entity.KnowledgeBase;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface KnowledgeBaseMapper extends BaseMapper<KnowledgeBase> {
|
||||
|
||||
/**
|
||||
* 搜索知识库(管理员)
|
||||
*
|
||||
* @param keyword 关键词
|
||||
* @return
|
||||
*/
|
||||
Page<KnowledgeBase> searchByAdmin(Page<KnowledgeBase> page, @Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 搜索知识库(用户)
|
||||
*
|
||||
* @param ownerId 用户id
|
||||
* @param keyword 关键词
|
||||
* @return
|
||||
*/
|
||||
Page<KnowledgeBase> searchByUser(Page<KnowledgeBase> page, @Param("ownerId") long ownerId, @Param("keyword") String keyword, @Param("includeOthersPublic") Boolean includeOthersPublic);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.moyz.adi.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseQaRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface KnowledgeBaseQaRecordMapper extends BaseMapper<KnowledgeBaseQaRecord> {
|
||||
|
||||
}
|
|
@ -17,7 +17,6 @@ import com.moyz.adi.common.mapper.AiImageMapper;
|
|||
import com.moyz.adi.common.util.LocalCache;
|
||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||
import com.moyz.adi.common.util.UserUtil;
|
||||
import com.theokanning.openai.image.Image;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -174,7 +173,7 @@ public class AiImageService extends ServiceImpl<AiImageMapper, AiImage> {
|
|||
String requestTimesKey = MessageFormat.format(RedisKeyConstant.USER_REQUEST_TEXT_TIMES, user.getId());
|
||||
rateLimitHelper.increaseRequestTimes(requestTimesKey, LocalCache.IMAGE_RATE_LIMIT_CONFIG);
|
||||
|
||||
List<Image> images = new ArrayList<>();
|
||||
List<String> images = new ArrayList<>();
|
||||
if (aiImage.getInteractingMethod() == INTERACTING_METHOD_GENERATE_IMAGE) {
|
||||
images = openAiHelper.createImage(user, aiImage);
|
||||
} else if (aiImage.getInteractingMethod() == INTERACTING_METHOD_EDIT_IMAGE) {
|
||||
|
@ -183,8 +182,8 @@ public class AiImageService extends ServiceImpl<AiImageMapper, AiImage> {
|
|||
images = openAiHelper.createImageVariation(user, aiImage);
|
||||
}
|
||||
List<String> imageUuids = new ArrayList();
|
||||
images.forEach(image -> {
|
||||
String imageUuid = fileService.saveToLocal(user, image.getUrl());
|
||||
images.forEach(imageUrl -> {
|
||||
String imageUuid = fileService.saveToLocal(user, imageUrl);
|
||||
imageUuids.add(imageUuid);
|
||||
});
|
||||
String imageUuidsJoin = imageUuids.stream().collect(Collectors.joining(","));
|
||||
|
@ -192,7 +191,7 @@ public class AiImageService extends ServiceImpl<AiImageMapper, AiImage> {
|
|||
_this.lambdaUpdate().eq(AiImage::getId, aiImage.getId()).set(AiImage::getProcessStatus, STATUS_FAIL).update();
|
||||
return;
|
||||
}
|
||||
String respImagesPath = images.stream().map(Image::getUrl).collect(Collectors.joining(","));
|
||||
String respImagesPath = images.stream().collect(Collectors.joining(","));
|
||||
updateAiImageStatus(aiImage.getId(), respImagesPath, imageUuidsJoin, STATUS_SUCCESS);
|
||||
|
||||
//Update the cost of current user
|
||||
|
@ -231,7 +230,7 @@ public class AiImageService extends ServiceImpl<AiImageMapper, AiImage> {
|
|||
public AiImagesListResp listAll(@RequestParam Long maxId, @RequestParam int pageSize) {
|
||||
List<AiImage> list = this.lambdaQuery()
|
||||
.eq(AiImage::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(AiImage::getIsDelete, false)
|
||||
.eq(AiImage::getIsDeleted, false)
|
||||
.lt(AiImage::getId, maxId)
|
||||
.orderByDesc(AiImage::getId)
|
||||
.last("limit " + pageSize)
|
||||
|
|
|
@ -10,19 +10,25 @@ import com.moyz.adi.common.entity.Conversation;
|
|||
import com.moyz.adi.common.entity.ConversationMessage;
|
||||
import com.moyz.adi.common.entity.User;
|
||||
import com.moyz.adi.common.entity.UserDayCost;
|
||||
import com.moyz.adi.common.enums.ChatMessageRoleEnum;
|
||||
import com.moyz.adi.common.enums.ErrorEnum;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.helper.OpenAiHelper;
|
||||
import com.moyz.adi.common.helper.QuotaHelper;
|
||||
import com.moyz.adi.common.helper.RateLimitHelper;
|
||||
import com.moyz.adi.common.mapper.ConversationMessageMapper;
|
||||
import com.moyz.adi.common.model.AnswerMeta;
|
||||
import com.moyz.adi.common.model.QuestionMeta;
|
||||
import com.moyz.adi.common.util.LocalCache;
|
||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||
import com.moyz.adi.common.util.UserUtil;
|
||||
import com.theokanning.openai.completion.chat.ChatMessage;
|
||||
import com.moyz.adi.common.vo.AnswerMeta;
|
||||
import com.moyz.adi.common.vo.QuestionMeta;
|
||||
import com.moyz.adi.common.vo.SseAskParams;
|
||||
import com.theokanning.openai.completion.chat.ChatMessageRole;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.memory.ChatMemory;
|
||||
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
|
||||
import dev.langchain4j.model.openai.OpenAiTokenizer;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -35,12 +41,12 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.moyz.adi.common.enums.ErrorEnum.B_MESSAGE_NOT_FOUND;
|
||||
import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
|
@ -90,7 +96,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
//check 2: the conversation has been deleted
|
||||
Conversation delConv = conversationService.lambdaQuery()
|
||||
.eq(Conversation::getUuid, askReq.getConversationUuid())
|
||||
.eq(Conversation::getIsDelete, true)
|
||||
.eq(Conversation::getIsDeleted, true)
|
||||
.one();
|
||||
if (null != delConv) {
|
||||
sendErrorMsg(sseEmitter, "该对话已经删除");
|
||||
|
@ -100,7 +106,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
//check 3: conversation quota
|
||||
Long convsCount = conversationService.lambdaQuery()
|
||||
.eq(Conversation::getUserId, user.getId())
|
||||
.eq(Conversation::getIsDelete, false)
|
||||
.eq(Conversation::getIsDeleted, false)
|
||||
.count();
|
||||
long convsMax = Integer.parseInt(LocalCache.CONFIGS.get(AdiConstant.SysConfigKey.CONVERSATION_MAX_NUM));
|
||||
if (convsCount >= convsMax) {
|
||||
|
@ -175,12 +181,16 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
}
|
||||
}
|
||||
);
|
||||
SseAskParams sseAskParams = new SseAskParams();
|
||||
String prompt = askReq.getPrompt();
|
||||
if (StringUtils.isNotBlank(askReq.getRegenerateQuestionUuid())) {
|
||||
prompt = getPromptMsgByQuestionUuid(askReq.getRegenerateQuestionUuid()).getContent();
|
||||
prompt = getPromptMsgByQuestionUuid(askReq.getRegenerateQuestionUuid()).getRemark();
|
||||
}
|
||||
sseAskParams.setSystemMessage(StringUtils.EMPTY);
|
||||
sseAskParams.setSseEmitter(sseEmitter);
|
||||
sseAskParams.setUserMessage(prompt);
|
||||
sseAskParams.setRegenerateQuestionUuid(askReq.getRegenerateQuestionUuid());
|
||||
//questions
|
||||
final List<ChatMessage> chatMessageList = new ArrayList<>();
|
||||
//system message
|
||||
Conversation conversation = conversationService.lambdaQuery()
|
||||
.eq(Conversation::getUuid, askReq.getConversationUuid())
|
||||
|
@ -188,8 +198,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
.orElse(null);
|
||||
if (null != conversation) {
|
||||
if (StringUtils.isNotBlank(conversation.getAiSystemMessage())) {
|
||||
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), conversation.getAiSystemMessage());
|
||||
chatMessageList.add(chatMessage);
|
||||
sseAskParams.setSystemMessage(conversation.getAiSystemMessage());
|
||||
}
|
||||
//history message
|
||||
if (Boolean.TRUE.equals(conversation.getUnderstandContextEnable()) && user.getUnderstandContextMsgPairNum() > 0) {
|
||||
|
@ -200,19 +209,23 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
.last("limit " + user.getUnderstandContextMsgPairNum() * 2)
|
||||
.list();
|
||||
if (!historyMsgList.isEmpty()) {
|
||||
ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer(GPT_3_5_TURBO));
|
||||
historyMsgList.sort(Comparator.comparing(ConversationMessage::getId));
|
||||
for (ConversationMessage historyMsg : historyMsgList) {
|
||||
ChatMessage chatMessage = new ChatMessage(historyMsg.getMessageRole(), historyMsg.getContent());
|
||||
chatMessageList.add(chatMessage);
|
||||
if (ChatMessageRole.USER.value().equals(historyMsg.getMessageRole())) {
|
||||
UserMessage userMessage = UserMessage.from(historyMsg.getRemark());
|
||||
chatMemory.add(userMessage);
|
||||
} else if (ChatMessageRole.SYSTEM.value().equals(historyMsg.getMessageRole())) {
|
||||
SystemMessage userMessage = SystemMessage.from(historyMsg.getRemark());
|
||||
chatMemory.add(userMessage);
|
||||
}
|
||||
}
|
||||
sseAskParams.setChatMemory(chatMemory);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
//new user message
|
||||
ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt);
|
||||
chatMessageList.add(userMessage);
|
||||
openAiHelper.sseAsk(user, askReq.getRegenerateQuestionUuid(), chatMessageList, sseEmitter, (response, questionMeta, answerMeta) -> {
|
||||
openAiHelper.sseAsk(sseAskParams, (response, questionMeta, answerMeta) -> {
|
||||
try {
|
||||
_this.saveAfterAiResponse(user, askReq, response, questionMeta, answerMeta);
|
||||
} catch (Exception e) {
|
||||
|
@ -228,7 +241,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
queryWrapper.eq(ConversationMessage::getConversationId, convId);
|
||||
queryWrapper.eq(ConversationMessage::getParentMessageId, 0);
|
||||
queryWrapper.lt(ConversationMessage::getId, maxId);
|
||||
queryWrapper.eq(ConversationMessage::getIsDelete, false);
|
||||
queryWrapper.eq(ConversationMessage::getIsDeleted, false);
|
||||
queryWrapper.last("limit " + pageSize);
|
||||
queryWrapper.orderByDesc(ConversationMessage::getId);
|
||||
return getBaseMapper().selectList(queryWrapper);
|
||||
|
@ -257,8 +270,8 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
question.setUuid(questionMeta.getUuid());
|
||||
question.setConversationId(conversation.getId());
|
||||
question.setConversationUuid(convUuid);
|
||||
question.setMessageRole(ChatMessageRole.USER.value());
|
||||
question.setContent(prompt);
|
||||
question.setMessageRole(ChatMessageRoleEnum.USER.getValue());
|
||||
question.setRemark(prompt);
|
||||
question.setTokens(questionMeta.getTokens());
|
||||
question.setSecretKeyType(secretKeyType);
|
||||
question.setUnderstandContextMsgPairNum(user.getUnderstandContextMsgPairNum());
|
||||
|
@ -273,8 +286,8 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
aiAnswer.setUuid(answerMeta.getUuid());
|
||||
aiAnswer.setConversationId(conversation.getId());
|
||||
aiAnswer.setConversationUuid(convUuid);
|
||||
aiAnswer.setMessageRole(ChatMessageRole.ASSISTANT.value());
|
||||
aiAnswer.setContent(response);
|
||||
aiAnswer.setMessageRole(ChatMessageRoleEnum.ASSISTANT.getValue());
|
||||
aiAnswer.setRemark(response);
|
||||
aiAnswer.setTokens(answerMeta.getTokens());
|
||||
aiAnswer.setParentMessageId(promptMsg.getId());
|
||||
aiAnswer.setSecretKeyType(secretKeyType);
|
||||
|
@ -321,8 +334,8 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
|||
return this.lambdaUpdate()
|
||||
.eq(ConversationMessage::getUuid, uuid)
|
||||
.eq(ConversationMessage::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(ConversationMessage::getIsDelete, false)
|
||||
.set(ConversationMessage::getIsDelete, true)
|
||||
.eq(ConversationMessage::getIsDeleted, false)
|
||||
.set(ConversationMessage::getIsDeleted, true)
|
||||
.update();
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
|||
public List<ConvDto> listByUser() {
|
||||
List<Conversation> list = this.lambdaQuery()
|
||||
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Conversation::getIsDelete, false)
|
||||
.eq(Conversation::getIsDeleted, false)
|
||||
.orderByDesc(Conversation::getId)
|
||||
.last("limit " + sysConfigService.getConversationMaxNum())
|
||||
.list();
|
||||
|
@ -61,7 +61,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
|||
ConversationMessage maxMsg = conversationMessageService.lambdaQuery()
|
||||
.select(ConversationMessage::getId)
|
||||
.eq(ConversationMessage::getUuid, maxMsgUuid)
|
||||
.eq(ConversationMessage::getIsDelete, false)
|
||||
.eq(ConversationMessage::getIsDeleted, false)
|
||||
.one();
|
||||
if (null == maxMsg) {
|
||||
throw new RuntimeException("找不到对应的消息");
|
||||
|
@ -88,7 +88,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
|||
List<ConversationMessage> childMessages = conversationMessageService
|
||||
.lambdaQuery()
|
||||
.in(ConversationMessage::getParentMessageId, parentIds)
|
||||
.eq(ConversationMessage::getIsDelete, false)
|
||||
.eq(ConversationMessage::getIsDeleted, false)
|
||||
.list();
|
||||
Map<Long, List<ConversationMessage>> idToMessages = childMessages.stream().collect(Collectors.groupingBy(ConversationMessage::getParentMessageId));
|
||||
|
||||
|
@ -126,7 +126,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
|||
Conversation conversation = this.lambdaQuery()
|
||||
.eq(Conversation::getUuid, uuid)
|
||||
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Conversation::getIsDelete, false)
|
||||
.eq(Conversation::getIsDeleted, false)
|
||||
.one();
|
||||
if (null == conversation) {
|
||||
throw new BaseException(A_CONVERSATION_NOT_EXIST);
|
||||
|
@ -144,7 +144,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
|||
return this.lambdaUpdate()
|
||||
.eq(Conversation::getUuid, uuid)
|
||||
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
||||
.set(Conversation::getIsDelete, true)
|
||||
.set(Conversation::getIsDeleted, true)
|
||||
.update();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,14 +38,14 @@ public class FileService extends ServiceImpl<FileMapper, AdiFile> {
|
|||
@Value("${local.tmp_images}")
|
||||
private String tmpImagesPath;
|
||||
|
||||
public String writeToLocal(MultipartFile file) {
|
||||
public AdiFile writeToLocal(MultipartFile file) {
|
||||
String md5 = MD5Utils.md5ByMultipartFile(file);
|
||||
Optional<AdiFile> existFile = this.lambdaQuery()
|
||||
.eq(AdiFile::getMd5, md5)
|
||||
.eq(AdiFile::getIsDelete, false)
|
||||
.eq(AdiFile::getIsDeleted, false)
|
||||
.oneOpt();
|
||||
if (existFile.isPresent()) {
|
||||
return existFile.get().getUuid();
|
||||
return existFile.get();
|
||||
}
|
||||
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
Pair<String, String> originalFile = FileUtil.saveToLocal(file, imagePath, uuid);
|
||||
|
@ -56,7 +56,7 @@ public class FileService extends ServiceImpl<FileMapper, AdiFile> {
|
|||
adiFile.setExt(originalFile.getRight());
|
||||
adiFile.setUserId(ThreadContext.getCurrentUserId());
|
||||
this.getBaseMapper().insert(adiFile);
|
||||
return uuid;
|
||||
return adiFile;
|
||||
}
|
||||
|
||||
public String saveToLocal(User user, String sourceImageUrl) {
|
||||
|
@ -83,7 +83,7 @@ public class FileService extends ServiceImpl<FileMapper, AdiFile> {
|
|||
return this.lambdaUpdate()
|
||||
.eq(AdiFile::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(AdiFile::getUuid, uuid)
|
||||
.set(AdiFile::getIsDelete, true)
|
||||
.set(AdiFile::getIsDeleted, true)
|
||||
.update();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.moyz.adi.common.service;
|
||||
|
||||
import com.moyz.adi.common.helper.EmbeddingHelper;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class Initializer {
|
||||
|
||||
@Resource
|
||||
private SysConfigService sysConfigService;
|
||||
|
||||
@Resource
|
||||
private EmbeddingHelper embeddingHelper;
|
||||
|
||||
@PostConstruct
|
||||
public void init(){
|
||||
sysConfigService.reload();
|
||||
embeddingHelper.init();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.moyz.adi.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.moyz.adi.common.dto.KbItemEmbeddingDto;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseEmbedding;
|
||||
import com.moyz.adi.common.mapper.KnowledgeBaseEmbeddingMapper;
|
||||
import com.moyz.adi.common.util.MPPageUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KnowledgeBaseEmbeddingService extends ServiceImpl<KnowledgeBaseEmbeddingMapper, KnowledgeBaseEmbedding> {
|
||||
|
||||
public Page<KbItemEmbeddingDto> listByItemUuid(String kbItemUuid, int currentPage, int pageSize) {
|
||||
Page<KnowledgeBaseEmbedding> sourcePage = baseMapper.selectByItemUuid(new Page<>(currentPage, pageSize), kbItemUuid);
|
||||
Page<KbItemEmbeddingDto> result = new Page<>();
|
||||
MPPageUtil.convertTo(sourcePage, result, KbItemEmbeddingDto.class, (source, target) -> {
|
||||
target.setEmbedding(source.getEmbedding().toArray());
|
||||
return target;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean deleteByItemUuid(String kbItemUuid){
|
||||
return baseMapper.deleteByItemUuid(kbItemUuid);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package com.moyz.adi.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.moyz.adi.common.base.ThreadContext;
|
||||
import com.moyz.adi.common.dto.KbItemEditReq;
|
||||
import com.moyz.adi.common.entity.KnowledgeBase;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseItem;
|
||||
import com.moyz.adi.common.entity.User;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.helper.EmbeddingHelper;
|
||||
import com.moyz.adi.common.mapper.KnowledgeBaseItemMapper;
|
||||
import dev.langchain4j.data.document.Document;
|
||||
import dev.langchain4j.data.document.Metadata;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.moyz.adi.common.enums.ErrorEnum.*;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KnowledgeBaseItemService extends ServiceImpl<KnowledgeBaseItemMapper, KnowledgeBaseItem> {
|
||||
|
||||
@Resource
|
||||
private EmbeddingHelper embeddingHelper;
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseEmbeddingService knowledgeBaseEmbeddingService;
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
private KnowledgeBaseService knowledgeBaseService;
|
||||
|
||||
public KnowledgeBaseItem saveOrUpdate(KbItemEditReq itemEditReq) {
|
||||
String uuid = itemEditReq.getUuid();
|
||||
KnowledgeBaseItem item = new KnowledgeBaseItem();
|
||||
item.setTitle(itemEditReq.getTitle());
|
||||
if (StringUtils.isNotBlank(itemEditReq.getBrief())) {
|
||||
item.setBrief(itemEditReq.getBrief());
|
||||
} else {
|
||||
item.setBrief(StringUtils.substring(itemEditReq.getRemark(), 0, 200));
|
||||
}
|
||||
item.setRemark(itemEditReq.getRemark());
|
||||
if (null == itemEditReq.getId() || itemEditReq.getId() < 1) {
|
||||
uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
item.setUuid(uuid);
|
||||
item.setKbId(itemEditReq.getKbId());
|
||||
item.setKbUuid(itemEditReq.getKbUuid());
|
||||
baseMapper.insert(item);
|
||||
} else {
|
||||
item.setId(itemEditReq.getId());
|
||||
baseMapper.updateById(item);
|
||||
}
|
||||
return ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBaseItem::getUuid, uuid)
|
||||
.one();
|
||||
}
|
||||
|
||||
public KnowledgeBaseItem getEnable(String uuid) {
|
||||
return ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBaseItem::getUuid, uuid)
|
||||
.eq(KnowledgeBaseItem::getIsDeleted, false)
|
||||
.one();
|
||||
}
|
||||
|
||||
public Page<KnowledgeBaseItem> search(String kbUuid, String keyword, Integer currentPage, Integer pageSize) {
|
||||
LambdaQueryChainWrapper<KnowledgeBaseItem> wrapper = ChainWrappers.lambdaQueryChain(baseMapper);
|
||||
wrapper.select(KnowledgeBaseItem::getId, KnowledgeBaseItem::getUuid, KnowledgeBaseItem::getTitle, KnowledgeBaseItem::getBrief, KnowledgeBaseItem::getKbUuid, KnowledgeBaseItem::getIsEmbedded, KnowledgeBaseItem::getCreateTime, KnowledgeBaseItem::getUpdateTime);
|
||||
wrapper.eq(KnowledgeBaseItem::getIsDeleted, false);
|
||||
wrapper.eq(KnowledgeBaseItem::getKbUuid, kbUuid);
|
||||
if (StringUtils.isNotBlank(keyword)) {
|
||||
wrapper.eq(KnowledgeBaseItem::getTitle, keyword);
|
||||
}
|
||||
return wrapper.page(new Page<>(currentPage, pageSize));
|
||||
}
|
||||
|
||||
public boolean checkAndEmbedding(String[] uuids) {
|
||||
if (ArrayUtils.isEmpty(uuids)) {
|
||||
return false;
|
||||
}
|
||||
for (String uuid : uuids) {
|
||||
checkAndEmbedding(uuid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkAndEmbedding(String uuid) {
|
||||
if (checkPrivilege(uuid)) {
|
||||
KnowledgeBaseItem one = getEnable(uuid);
|
||||
return embedding(one);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean embedding(KnowledgeBaseItem one) {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.add("kb_uuid", one.getKbUuid());
|
||||
metadata.add("kb_item_uuid", one.getUuid());
|
||||
Document document = new Document(one.getRemark(), metadata);
|
||||
embeddingHelper.getEmbeddingStoreIngestor().ingest(document);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public boolean softDelete(String uuid) {
|
||||
boolean privilege = checkPrivilege(uuid);
|
||||
if (!privilege) throw new BaseException(A_USER_NOT_AUTH);
|
||||
boolean success = ChainWrappers.lambdaUpdateChain(baseMapper)
|
||||
.eq(KnowledgeBaseItem::getUuid, uuid)
|
||||
.set(KnowledgeBaseItem::getIsDeleted, true)
|
||||
.update();
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
knowledgeBaseEmbeddingService.deleteByItemUuid(uuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkPrivilege(String uuid) {
|
||||
if (StringUtils.isBlank(uuid)) {
|
||||
throw new BaseException(A_PARAMS_ERROR);
|
||||
}
|
||||
User user = ThreadContext.getCurrentUser();
|
||||
if (null == user) {
|
||||
throw new BaseException(A_USER_NOT_EXIST);
|
||||
}
|
||||
if (user.getIsAdmin()) {
|
||||
return true;
|
||||
}
|
||||
Optional<KnowledgeBaseItem> kbItem = ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBaseItem::getUuid, uuid)
|
||||
.oneOpt();
|
||||
if (kbItem.isPresent()) {
|
||||
KnowledgeBase kb = knowledgeBaseService.getById(kbItem.get().getKbId());
|
||||
if (null != kb) {
|
||||
return kb.getOwnerId().equals(user.getId());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.moyz.adi.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.moyz.adi.common.base.ThreadContext;
|
||||
import com.moyz.adi.common.entity.KnowledgeBaseQaRecord;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.mapper.KnowledgeBaseQaRecordMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static com.moyz.adi.common.enums.ErrorEnum.A_DATA_NOT_FOUND;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KnowledgeBaseQaRecordService extends ServiceImpl<KnowledgeBaseQaRecordMapper, KnowledgeBaseQaRecord> {
|
||||
|
||||
public Page<KnowledgeBaseQaRecord> search(String kbUuid, String keyword, Integer currentPage, Integer pageSize) {
|
||||
LambdaQueryWrapper<KnowledgeBaseQaRecord> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(KnowledgeBaseQaRecord::getKbUuid, kbUuid);
|
||||
if (!ThreadContext.getCurrentUser().getIsAdmin()) {
|
||||
wrapper.eq(KnowledgeBaseQaRecord::getUserId, ThreadContext.getCurrentUserId());
|
||||
}
|
||||
if (StringUtils.isNotBlank(keyword)) {
|
||||
wrapper.like(KnowledgeBaseQaRecord::getQuestion, keyword);
|
||||
}
|
||||
wrapper.orderByDesc(KnowledgeBaseQaRecord::getUpdateTime);
|
||||
return baseMapper.selectPage(new Page<>(currentPage, pageSize), wrapper);
|
||||
}
|
||||
|
||||
public boolean softDelele(String uuid) {
|
||||
if (ThreadContext.getCurrentUser().getIsAdmin()) {
|
||||
return ChainWrappers.lambdaUpdateChain(baseMapper)
|
||||
.eq(KnowledgeBaseQaRecord::getUuid, uuid)
|
||||
.set(KnowledgeBaseQaRecord::getIsDeleted, true)
|
||||
.update();
|
||||
}
|
||||
KnowledgeBaseQaRecord exist = ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBaseQaRecord::getUuid, uuid)
|
||||
.one();
|
||||
if (null == exist) {
|
||||
throw new BaseException(A_DATA_NOT_FOUND);
|
||||
}
|
||||
return ChainWrappers.lambdaUpdateChain(baseMapper)
|
||||
.eq(KnowledgeBaseQaRecord::getId, exist.getId())
|
||||
.set(KnowledgeBaseQaRecord::getIsDeleted, true)
|
||||
.update();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package com.moyz.adi.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.moyz.adi.common.base.ThreadContext;
|
||||
import com.moyz.adi.common.cosntant.RedisKeyConstant;
|
||||
import com.moyz.adi.common.dto.KbEditReq;
|
||||
import com.moyz.adi.common.entity.*;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.helper.EmbeddingHelper;
|
||||
import com.moyz.adi.common.mapper.KnowledgeBaseMapper;
|
||||
import com.moyz.adi.common.util.BizPager;
|
||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||
import dev.langchain4j.data.document.Document;
|
||||
import dev.langchain4j.data.document.parser.TextDocumentParser;
|
||||
import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
|
||||
import dev.langchain4j.data.document.parser.apache.poi.ApachePoiDocumentParser;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.moyz.adi.common.cosntant.AdiConstant.POI_DOC_TYPES;
|
||||
import static com.moyz.adi.common.enums.ErrorEnum.*;
|
||||
import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KnowledgeBaseService extends ServiceImpl<KnowledgeBaseMapper, KnowledgeBase> {
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Resource
|
||||
private EmbeddingHelper embeddingHelper;
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseItemService knowledgeBaseItemService;
|
||||
|
||||
@Resource
|
||||
private KnowledgeBaseQaRecordService knowledgeBaseQaRecordService;
|
||||
|
||||
@Resource
|
||||
private FileService fileService;
|
||||
|
||||
public KnowledgeBase saveOrUpdate(KbEditReq kbEditReq) {
|
||||
String uuid = kbEditReq.getUuid();
|
||||
KnowledgeBase knowledgeBase = new KnowledgeBase();
|
||||
knowledgeBase.setTitle(kbEditReq.getTitle());
|
||||
knowledgeBase.setRemark(kbEditReq.getRemark());
|
||||
if (null != kbEditReq.getIsPublic()) {
|
||||
knowledgeBase.setIsPublic(kbEditReq.getIsPublic());
|
||||
}
|
||||
if (null == kbEditReq.getId() || kbEditReq.getId() < 1) {
|
||||
User user = ThreadContext.getCurrentUser();
|
||||
uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
knowledgeBase.setUuid(uuid);
|
||||
knowledgeBase.setOwnerId(user.getId());
|
||||
knowledgeBase.setOwnerName(user.getName());
|
||||
baseMapper.insert(knowledgeBase);
|
||||
} else {
|
||||
knowledgeBase.setId(kbEditReq.getId());
|
||||
baseMapper.updateById(knowledgeBase);
|
||||
}
|
||||
return ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBase::getUuid, uuid)
|
||||
.one();
|
||||
}
|
||||
|
||||
public List<AdiFile> uploadDocs(String kbUuid, Boolean embedding, MultipartFile[] docs) {
|
||||
if (ArrayUtils.isEmpty(docs)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<AdiFile> result = new ArrayList<>();
|
||||
KnowledgeBase knowledgeBase = ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBase::getUuid, kbUuid)
|
||||
.eq(KnowledgeBase::getIsDeleted, false)
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new BaseException(A_DATA_NOT_FOUND));
|
||||
for (MultipartFile doc : docs) {
|
||||
try {
|
||||
result.add(uploadDoc(knowledgeBase, doc, embedding));
|
||||
} catch (Exception e) {
|
||||
log.warn("uploadDocs fail,fileName:{}", doc.getOriginalFilename(), e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public AdiFile uploadDoc(String kbUuid, Boolean embedding, MultipartFile doc) {
|
||||
KnowledgeBase knowledgeBase = ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBase::getUuid, kbUuid)
|
||||
.eq(KnowledgeBase::getIsDeleted, false)
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new BaseException(A_DATA_NOT_FOUND));
|
||||
return uploadDoc(knowledgeBase, doc, embedding);
|
||||
}
|
||||
|
||||
private AdiFile uploadDoc(KnowledgeBase knowledgeBase, MultipartFile doc, Boolean embedding) {
|
||||
try {
|
||||
String fileName = doc.getOriginalFilename();
|
||||
AdiFile adiFile = fileService.writeToLocal(doc);
|
||||
|
||||
//解析文档
|
||||
Document document;
|
||||
if (adiFile.getExt().equalsIgnoreCase("txt")) {
|
||||
document = loadDocument(adiFile.getPath(), new TextDocumentParser());
|
||||
} else if (adiFile.getExt().equalsIgnoreCase("pdf")) {
|
||||
document = loadDocument(adiFile.getPath(), new ApachePdfBoxDocumentParser());
|
||||
} else if (ArrayUtils.contains(POI_DOC_TYPES, adiFile.getExt())) {
|
||||
document = loadDocument(adiFile.getPath(), new ApachePoiDocumentParser());
|
||||
} else {
|
||||
log.warn("该文件类型:{}无法解析,忽略", adiFile.getExt());
|
||||
return adiFile;
|
||||
}
|
||||
//创建知识库条目
|
||||
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
KnowledgeBaseItem knowledgeBaseItem = new KnowledgeBaseItem();
|
||||
knowledgeBaseItem.setUuid(uuid);
|
||||
knowledgeBaseItem.setKbId(knowledgeBase.getId());
|
||||
knowledgeBaseItem.setKbUuid(knowledgeBase.getUuid());
|
||||
knowledgeBaseItem.setSourceFileId(adiFile.getId());
|
||||
knowledgeBaseItem.setTitle(fileName);
|
||||
knowledgeBaseItem.setBrief(StringUtils.substring(document.text(), 0, 200));
|
||||
knowledgeBaseItem.setRemark(document.text());
|
||||
knowledgeBaseItem.setIsEmbedded(true);
|
||||
boolean success = knowledgeBaseItemService.save(knowledgeBaseItem);
|
||||
if (success && Boolean.TRUE.equals(embedding)) {
|
||||
knowledgeBaseItem = knowledgeBaseItemService.getEnable(uuid);
|
||||
|
||||
//向量化
|
||||
Document docWithoutPath = new Document(document.text());
|
||||
docWithoutPath.metadata()
|
||||
.add("kb_uuid", knowledgeBase.getUuid())
|
||||
.add("kb_item_uuid", knowledgeBaseItem.getUuid());
|
||||
|
||||
embeddingHelper.getEmbeddingStoreIngestor().ingest(docWithoutPath);
|
||||
|
||||
knowledgeBaseItemService
|
||||
.lambdaUpdate()
|
||||
.eq(KnowledgeBaseItem::getId, knowledgeBaseItem.getId())
|
||||
.set(KnowledgeBaseItem::getIsEmbedded, true)
|
||||
.update();
|
||||
}
|
||||
return adiFile;
|
||||
} catch (Exception e) {
|
||||
log.error("upload error", e);
|
||||
throw new BaseException(A_UPLOAD_FAIL);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean embedding(String kbUuid, boolean forceAll) {
|
||||
boolean privilege = checkPrivilege(null, kbUuid);
|
||||
if (!privilege) throw new BaseException(A_USER_NOT_AUTH);
|
||||
LambdaQueryWrapper<KnowledgeBaseItem> wrapper = new LambdaQueryWrapper();
|
||||
wrapper.eq(KnowledgeBaseItem::getIsDeleted, false);
|
||||
wrapper.eq(KnowledgeBaseItem::getUuid, kbUuid);
|
||||
BizPager.oneByOneWithAnchor(wrapper, knowledgeBaseItemService, KnowledgeBaseItem::getId, one -> {
|
||||
if (forceAll || !one.getIsEmbedded()) {
|
||||
knowledgeBaseItemService.embedding(one);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public Page<KnowledgeBase> search(String keyword, Boolean includeOthersPublic, Integer currentPage, Integer pageSize) {
|
||||
User user = ThreadContext.getCurrentUser();
|
||||
if (user.getIsAdmin()) {
|
||||
return baseMapper.searchByAdmin(new Page<>(currentPage, pageSize), keyword);
|
||||
} else {
|
||||
return baseMapper.searchByUser(new Page<>(currentPage, pageSize), user.getId(), keyword, includeOthersPublic);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean softDelete(String uuid) {
|
||||
boolean privs = checkPrivilege(null, uuid);
|
||||
if (!privs) throw new BaseException(A_USER_NOT_AUTH);
|
||||
return ChainWrappers.lambdaUpdateChain(baseMapper)
|
||||
.eq(KnowledgeBase::getUuid, uuid)
|
||||
.set(KnowledgeBase::getIsDeleted, true)
|
||||
.update();
|
||||
}
|
||||
|
||||
public KnowledgeBaseQaRecord answerAndRecord(String kbUuid, String question) {
|
||||
|
||||
String key = MessageFormat.format(RedisKeyConstant.AQ_ASK_TIMES, ThreadContext.getCurrentUserId(), LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMdd"));
|
||||
String askTimes = stringRedisTemplate.opsForValue().get(key);
|
||||
String askQuota = SysConfigService.getByKey("quota_by_qa_ask_daily");
|
||||
if (null != askTimes && null != askQuota && Integer.parseInt(askTimes) >= Integer.parseInt(askQuota)) {
|
||||
throw new BaseException(A_QA_ASK_LIMIT);
|
||||
}
|
||||
stringRedisTemplate.opsForValue().increment(key);
|
||||
|
||||
KnowledgeBase knowledgeBase = getOrThrow(kbUuid);
|
||||
String answer = embeddingHelper.findAnswer(kbUuid, question);
|
||||
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
KnowledgeBaseQaRecord newObj = new KnowledgeBaseQaRecord();
|
||||
newObj.setKbId(knowledgeBase.getId());
|
||||
newObj.setKbUuid((knowledgeBase.getUuid()));
|
||||
newObj.setUuid(uuid);
|
||||
newObj.setUserId(ThreadContext.getCurrentUserId());
|
||||
newObj.setQuestion(question);
|
||||
newObj.setAnswer(answer);
|
||||
knowledgeBaseQaRecordService.save(newObj);
|
||||
return knowledgeBaseQaRecordService.lambdaQuery().eq(KnowledgeBaseQaRecord::getUuid, uuid).one();
|
||||
}
|
||||
|
||||
public KnowledgeBase getOrThrow(String kbUuid) {
|
||||
return ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(KnowledgeBase::getUuid, kbUuid)
|
||||
.eq(KnowledgeBase::getIsDeleted, false)
|
||||
.oneOpt().orElseThrow(() -> new BaseException(A_DATA_NOT_FOUND));
|
||||
}
|
||||
|
||||
private boolean checkPrivilege(Long kbId, String kbUuid) {
|
||||
if (null == kbId && StringUtils.isBlank(kbUuid)) {
|
||||
throw new BaseException(A_PARAMS_ERROR);
|
||||
}
|
||||
User user = ThreadContext.getCurrentUser();
|
||||
if (null == user) {
|
||||
throw new BaseException(A_USER_NOT_EXIST);
|
||||
}
|
||||
boolean privilege = user.getIsAdmin();
|
||||
if (privilege) {
|
||||
return true;
|
||||
}
|
||||
LambdaQueryWrapper<KnowledgeBase> wrapper = new LambdaQueryWrapper();
|
||||
wrapper.eq(KnowledgeBase::getOwnerId, user.getId());
|
||||
if (null != kbId) {
|
||||
wrapper = wrapper.eq(KnowledgeBase::getId, kbId);
|
||||
} else if (StringUtils.isNotBlank(kbUuid)) {
|
||||
wrapper = wrapper.eq(KnowledgeBase::getUuid, kbUuid);
|
||||
}
|
||||
return baseMapper.exists(wrapper);
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ import java.util.Map;
|
|||
public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||
|
||||
public List<PromptDto> getAll(long userId) {
|
||||
List<Prompt> prompts = this.lambdaQuery().eq(Prompt::getUserId, userId).eq(Prompt::getIsDelete, false).list();
|
||||
List<Prompt> prompts = this.lambdaQuery().eq(Prompt::getUserId, userId).eq(Prompt::getIsDeleted, false).list();
|
||||
return MPPageUtil.convertTo(prompts, PromptDto.class);
|
||||
}
|
||||
|
||||
|
@ -34,13 +34,13 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
|||
if (StringUtils.isNotBlank(keyword)) {
|
||||
promptPage = this.lambdaQuery()
|
||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.like(Prompt::getAct, keyword)
|
||||
.page(new Page<>(currentPage, pageSize));
|
||||
} else {
|
||||
promptPage = this.lambdaQuery()
|
||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.page(new Page<>(currentPage, pageSize));
|
||||
}
|
||||
return MPPageUtil.convertTo(promptPage, new Page<>(), PromptDto.class);
|
||||
|
@ -51,14 +51,14 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
|||
if (StringUtils.isNotBlank(keyword)) {
|
||||
promptPage = this.lambdaQuery()
|
||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.like(Prompt::getAct, keyword)
|
||||
.last("limit 10")
|
||||
.list();
|
||||
} else {
|
||||
promptPage = this.lambdaQuery()
|
||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.last("limit 10")
|
||||
.list();
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
|||
Prompt existOne = this.lambdaQuery()
|
||||
.eq(Prompt::getUserId, userId)
|
||||
.eq(Prompt::getAct, title)
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.one();
|
||||
if (null != existOne) {
|
||||
//modify
|
||||
|
@ -137,14 +137,14 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
|||
Prompt prompt = this.lambdaQuery()
|
||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Prompt::getId, id)
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.one();
|
||||
if (null == prompt) {
|
||||
return false;
|
||||
}
|
||||
Prompt updateOne = new Prompt();
|
||||
updateOne.setId(id);
|
||||
updateOne.setIsDelete(true);
|
||||
updateOne.setIsDeleted(true);
|
||||
return this.updateById(updateOne);
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
|||
Prompt prompt = this.lambdaQuery()
|
||||
.eq(Prompt::getId, id)
|
||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||
.eq(Prompt::getIsDelete, false)
|
||||
.eq(Prompt::getIsDeleted, false)
|
||||
.one();
|
||||
if (null == prompt) {
|
||||
return false;
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package com.moyz.adi.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.moyz.adi.common.cosntant.AdiConstant;
|
||||
import com.moyz.adi.common.model.RequestRateLimit;
|
||||
import com.moyz.adi.common.util.JsonUtil;
|
||||
import com.moyz.adi.common.util.LocalCache;
|
||||
import com.moyz.adi.common.entity.SysConfig;
|
||||
import com.moyz.adi.common.mapper.SysConfigMapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.moyz.adi.common.util.JsonUtil;
|
||||
import com.moyz.adi.common.util.LocalCache;
|
||||
import com.moyz.adi.common.vo.RequestRateLimit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -21,7 +24,7 @@ public class SysConfigService extends ServiceImpl<SysConfigMapper, SysConfig> {
|
|||
@Scheduled(fixedDelay = 20 * 60 * 1000)
|
||||
public void reload() {
|
||||
log.info("reload system config");
|
||||
List<SysConfig> configsFromDB = this.lambdaQuery().eq(SysConfig::getIsDelete, false).list();
|
||||
List<SysConfig> configsFromDB = this.lambdaQuery().eq(SysConfig::getIsDeleted, false).list();
|
||||
if (LocalCache.CONFIGS.isEmpty()) {
|
||||
configsFromDB.stream().forEach(item -> LocalCache.CONFIGS.put(item.getName(), item.getValue()));
|
||||
} else {
|
||||
|
@ -58,4 +61,24 @@ public class SysConfigService extends ServiceImpl<SysConfigMapper, SysConfig> {
|
|||
return LocalCache.CONFIGS.get(AdiConstant.SysConfigKey.SECRET_KEY);
|
||||
}
|
||||
|
||||
public static String getByKey(String key) {
|
||||
return LocalCache.CONFIGS.get(key);
|
||||
}
|
||||
|
||||
public static Integer getIntByKey(String key) {
|
||||
String val = LocalCache.CONFIGS.get(key);
|
||||
if (null != val) {
|
||||
return Integer.parseInt(val);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Page<SysConfig> search(String keyword, Integer currentPage, Integer pageSize) {
|
||||
LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<>();
|
||||
if (StringUtils.isNotBlank(keyword)) {
|
||||
wrapper.eq(SysConfig::getName, keyword);
|
||||
}
|
||||
return baseMapper.selectPage(new Page<>(currentPage, pageSize), wrapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import com.moyz.adi.common.entity.User;
|
|||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||
import com.moyz.adi.common.entity.UserDayCost;
|
||||
import com.moyz.adi.common.mapper.UserDayCostMapper;
|
||||
import com.moyz.adi.common.model.CostStat;
|
||||
import com.moyz.adi.common.vo.CostStat;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.moyz.adi.common.util.UserUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
|
@ -3,22 +3,22 @@ package com.moyz.adi.common.service;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
||||
import com.moyz.adi.common.cosntant.AdiConstant;
|
||||
import com.moyz.adi.common.enums.ErrorEnum;
|
||||
import com.moyz.adi.common.enums.UserStatusEnum;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.util.JsonUtil;
|
||||
import com.moyz.adi.common.util.LocalCache;
|
||||
import com.moyz.adi.common.base.ThreadContext;
|
||||
import com.moyz.adi.common.cosntant.AdiConstant;
|
||||
import com.moyz.adi.common.cosntant.RedisKeyConstant;
|
||||
import com.moyz.adi.common.dto.ConfigResp;
|
||||
import com.moyz.adi.common.dto.LoginReq;
|
||||
import com.moyz.adi.common.dto.LoginResp;
|
||||
import com.moyz.adi.common.dto.UserUpdateReq;
|
||||
import com.moyz.adi.common.entity.User;
|
||||
import com.moyz.adi.common.enums.ErrorEnum;
|
||||
import com.moyz.adi.common.enums.UserStatusEnum;
|
||||
import com.moyz.adi.common.exception.BaseException;
|
||||
import com.moyz.adi.common.helper.AdiMailSender;
|
||||
import com.moyz.adi.common.mapper.UserMapper;
|
||||
import com.moyz.adi.common.model.CostStat;
|
||||
import com.moyz.adi.common.util.JsonUtil;
|
||||
import com.moyz.adi.common.util.LocalCache;
|
||||
import com.moyz.adi.common.vo.CostStat;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -71,7 +71,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
|||
}
|
||||
return this.lambdaQuery()
|
||||
.eq(User::getEmail, email)
|
||||
.eq(User::getIsDelete, false)
|
||||
.eq(User::getIsDeleted, false)
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new BaseException(A_USER_NOT_EXIST));
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
|||
stringRedisTemplate.delete(captchaInCache);
|
||||
|
||||
User user = ChainWrappers.lambdaQueryChain(baseMapper)
|
||||
.eq(User::getIsDelete, false)
|
||||
.eq(User::getIsDeleted, false)
|
||||
.eq(User::getEmail, email)
|
||||
.one();
|
||||
if (null != user && user.getUserStatus() == UserStatusEnum.NORMAL) {
|
||||
|
@ -112,7 +112,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
|||
|
||||
//创建用户
|
||||
User newOne = new User();
|
||||
newOne.setName(email.substring(0, email.indexOf("@")));
|
||||
newOne.setName(StringUtils.substringBetween(email, "@"));
|
||||
newOne.setUuid(UUID.randomUUID().toString().replace("-", ""));
|
||||
newOne.setEmail(email);
|
||||
newOne.setPassword(hashed);
|
||||
|
@ -157,7 +157,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
|||
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
||||
User user = this.lambdaQuery()
|
||||
.eq(User::getEmail, email)
|
||||
.eq(User::getIsDelete, false)
|
||||
.eq(User::getIsDeleted, false)
|
||||
.oneOpt()
|
||||
.orElse(null);
|
||||
if (null == user) {
|
||||
|
@ -201,7 +201,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
|||
//captcha check end
|
||||
|
||||
User user = this.lambdaQuery()
|
||||
.eq(User::getIsDelete, false)
|
||||
.eq(User::getIsDeleted, false)
|
||||
.eq(User::getEmail, loginReq.getEmail())
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new BaseException(ErrorEnum.A_USER_NOT_EXIST));
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
package com.moyz.adi.common.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.pgvector.PGvector;
|
||||
import dev.langchain4j.data.document.Metadata;
|
||||
import dev.langchain4j.data.embedding.Embedding;
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.store.embedding.EmbeddingMatch;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||
import lombok.Builder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
import static dev.langchain4j.internal.Utils.*;
|
||||
import static dev.langchain4j.internal.ValidationUtils.*;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* 复制并做了少许改动()
|
||||
* PGVector EmbeddingStore Implementation
|
||||
* <p>
|
||||
* Only cosine similarity is used.
|
||||
* Only ivfflat index is used.
|
||||
*/
|
||||
public class AdiPgVectorEmbeddingStore implements EmbeddingStore<TextSegment> {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AdiPgVectorEmbeddingStore.class);
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private final String host;
|
||||
private final Integer port;
|
||||
private final String user;
|
||||
private final String password;
|
||||
private final String database;
|
||||
private final String table;
|
||||
|
||||
/**
|
||||
* All args constructor for PgVectorEmbeddingStore Class
|
||||
*
|
||||
* @param host The database host
|
||||
* @param port The database port
|
||||
* @param user The database user
|
||||
* @param password The database password
|
||||
* @param database The database name
|
||||
* @param table The database table
|
||||
* @param dimension The vector dimension
|
||||
* @param useIndex Should use <a href="https://github.com/pgvector/pgvector#ivfflat">IVFFlat</a> index
|
||||
* @param indexListSize The IVFFlat number of lists
|
||||
* @param createTable Should create table automatically
|
||||
* @param dropTableFirst Should drop table first, usually for testing
|
||||
*/
|
||||
@Builder
|
||||
public AdiPgVectorEmbeddingStore(
|
||||
String host,
|
||||
Integer port,
|
||||
String user,
|
||||
String password,
|
||||
String database,
|
||||
String table,
|
||||
Integer dimension,
|
||||
Boolean useIndex,
|
||||
Integer indexListSize,
|
||||
Boolean createTable,
|
||||
Boolean dropTableFirst) {
|
||||
this.host = ensureNotBlank(host, "host");
|
||||
this.port = ensureGreaterThanZero(port, "port");
|
||||
this.user = ensureNotBlank(user, "user");
|
||||
this.password = ensureNotBlank(password, "password");
|
||||
this.database = ensureNotBlank(database, "database");
|
||||
this.table = ensureNotBlank(table, "table");
|
||||
|
||||
useIndex = getOrDefault(useIndex, false);
|
||||
createTable = getOrDefault(createTable, true);
|
||||
dropTableFirst = getOrDefault(dropTableFirst, false);
|
||||
|
||||
try (Connection connection = setupConnection()) {
|
||||
|
||||
if (dropTableFirst) {
|
||||
connection.createStatement().executeUpdate(String.format("DROP TABLE IF EXISTS %s", table));
|
||||
}
|
||||
|
||||
if (createTable) {
|
||||
connection.createStatement().executeUpdate(String.format(
|
||||
"CREATE TABLE IF NOT EXISTS %s (" +
|
||||
"embedding_id UUID PRIMARY KEY, " +
|
||||
"embedding vector(%s), " +
|
||||
"text TEXT NULL, " +
|
||||
"metadata JSON NULL" +
|
||||
")",
|
||||
table, ensureGreaterThanZero(dimension, "dimension")));
|
||||
}
|
||||
|
||||
if (useIndex) {
|
||||
final String indexName = table + "_ivfflat_index";
|
||||
connection.createStatement().executeUpdate(String.format(
|
||||
"CREATE INDEX IF NOT EXISTS %s ON %s " +
|
||||
"USING ivfflat (embedding vector_cosine_ops) " +
|
||||
"WITH (lists = %s)",
|
||||
indexName, table, ensureGreaterThanZero(indexListSize, "indexListSize")));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Connection setupConnection() throws SQLException {
|
||||
Connection connection = DriverManager.getConnection(
|
||||
String.format("jdbc:postgresql://%s:%s/%s", host, port, database),
|
||||
user,
|
||||
password
|
||||
);
|
||||
connection.createStatement().executeUpdate("CREATE EXTENSION IF NOT EXISTS vector");
|
||||
PGvector.addVectorType(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given embedding to the store.
|
||||
*
|
||||
* @param embedding The embedding to be added to the store.
|
||||
* @return The auto-generated ID associated with the added embedding.
|
||||
*/
|
||||
@Override
|
||||
public String add(Embedding embedding) {
|
||||
String id = randomUUID();
|
||||
addInternal(id, embedding, null);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given embedding to the store.
|
||||
*
|
||||
* @param id The unique identifier for the embedding to be added.
|
||||
* @param embedding The embedding to be added to the store.
|
||||
*/
|
||||
@Override
|
||||
public void add(String id, Embedding embedding) {
|
||||
addInternal(id, embedding, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a given embedding and the corresponding content that has been embedded to the store.
|
||||
*
|
||||
* @param embedding The embedding to be added to the store.
|
||||
* @param textSegment Original content that was embedded.
|
||||
* @return The auto-generated ID associated with the added embedding.
|
||||
*/
|
||||
@Override
|
||||
public String add(Embedding embedding, TextSegment textSegment) {
|
||||
String id = randomUUID();
|
||||
addInternal(id, embedding, textSegment);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple embeddings to the store.
|
||||
*
|
||||
* @param embeddings A list of embeddings to be added to the store.
|
||||
* @return A list of auto-generated IDs associated with the added embeddings.
|
||||
*/
|
||||
@Override
|
||||
public List<String> addAll(List<Embedding> embeddings) {
|
||||
List<String> ids = embeddings.stream().map(ignored -> randomUUID()).collect(toList());
|
||||
addAllInternal(ids, embeddings, null);
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple embeddings and their corresponding contents that have been embedded to the store.
|
||||
*
|
||||
* @param embeddings A list of embeddings to be added to the store.
|
||||
* @param embedded A list of original contents that were embedded.
|
||||
* @return A list of auto-generated IDs associated with the added embeddings.
|
||||
*/
|
||||
@Override
|
||||
public List<String> addAll(List<Embedding> embeddings, List<TextSegment> embedded) {
|
||||
List<String> ids = embeddings.stream().map(ignored -> randomUUID()).collect(toList());
|
||||
addAllInternal(ids, embeddings, embedded);
|
||||
return ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the most relevant (closest in space) embeddings to the provided reference embedding.
|
||||
*
|
||||
* @param referenceEmbedding The embedding used as a reference. Returned embeddings should be relevant (closest) to this one.
|
||||
* @param maxResults The maximum number of embeddings to be returned.
|
||||
* @param minScore The minimum relevance score, ranging from 0 to 1 (inclusive).
|
||||
* Only embeddings with a score of this value or higher will be returned.
|
||||
* @return A list of embedding matches.
|
||||
* Each embedding match includes a relevance score (derivative of cosine distance),
|
||||
* ranging from 0 (not relevant) to 1 (highly relevant).
|
||||
*/
|
||||
@Override
|
||||
public List<EmbeddingMatch<TextSegment>> findRelevant(Embedding referenceEmbedding, int maxResults, double minScore) {
|
||||
List<EmbeddingMatch<TextSegment>> result = new ArrayList<>();
|
||||
try (Connection connection = setupConnection()) {
|
||||
String referenceVector = Arrays.toString(referenceEmbedding.vector());
|
||||
String query = String.format(
|
||||
"WITH temp AS (SELECT (2 - (embedding <=> '%s')) / 2 AS score, embedding_id, embedding, text, metadata FROM %s) SELECT * FROM temp WHERE score >= %s ORDER BY score desc LIMIT %s;",
|
||||
referenceVector, table, minScore, maxResults);
|
||||
PreparedStatement selectStmt = connection.prepareStatement(query);
|
||||
|
||||
ResultSet resultSet = selectStmt.executeQuery();
|
||||
while (resultSet.next()) {
|
||||
double score = resultSet.getDouble("score");
|
||||
String embeddingId = resultSet.getString("embedding_id");
|
||||
|
||||
PGvector vector = (PGvector) resultSet.getObject("embedding");
|
||||
Embedding embedding = new Embedding(vector.toArray());
|
||||
|
||||
String text = resultSet.getString("text");
|
||||
TextSegment textSegment = null;
|
||||
if (isNotNullOrBlank(text)) {
|
||||
String metadataJson = Optional.ofNullable(resultSet.getString("metadata")).orElse("{}");
|
||||
Type type = new TypeToken<Map<String, String>>() {
|
||||
}.getType();
|
||||
Metadata metadata = new Metadata(new HashMap<>(GSON.fromJson(metadataJson, type)));
|
||||
textSegment = TextSegment.from(text, metadata);
|
||||
}
|
||||
|
||||
result.add(new EmbeddingMatch<>(score, embeddingId, embedding, textSegment));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addInternal(String id, Embedding embedding, TextSegment embedded) {
|
||||
addAllInternal(
|
||||
singletonList(id),
|
||||
singletonList(embedding),
|
||||
embedded == null ? null : singletonList(embedded));
|
||||
}
|
||||
|
||||
private void addAllInternal(
|
||||
List<String> ids, List<Embedding> embeddings, List<TextSegment> embedded) {
|
||||
if (isNullOrEmpty(ids) || isNullOrEmpty(embeddings)) {
|
||||
log.info("Empty embeddings - no ops");
|
||||
return;
|
||||
}
|
||||
ensureTrue(ids.size() == embeddings.size(), "ids size is not equal to embeddings size");
|
||||
ensureTrue(embedded == null || embeddings.size() == embedded.size(),
|
||||
"embeddings size is not equal to embedded size");
|
||||
|
||||
try (Connection connection = setupConnection()) {
|
||||
String query = String.format(
|
||||
"INSERT INTO %s (embedding_id, embedding, text, metadata) VALUES (?, ?, ?, ?)" +
|
||||
"ON CONFLICT (embedding_id) DO UPDATE SET " +
|
||||
"embedding = EXCLUDED.embedding," +
|
||||
"text = EXCLUDED.text," +
|
||||
"metadata = EXCLUDED.metadata;",
|
||||
table);
|
||||
|
||||
PreparedStatement upsertStmt = connection.prepareStatement(query);
|
||||
|
||||
for (int i = 0; i < ids.size(); ++i) {
|
||||
upsertStmt.setObject(1, UUID.fromString(ids.get(i)));
|
||||
upsertStmt.setObject(2, new PGvector(embeddings.get(i).vector()));
|
||||
|
||||
if (embedded != null && embedded.get(i) != null) {
|
||||
upsertStmt.setObject(3, embedded.get(i).text());
|
||||
Map<String, String> metadata = embedded.get(i).metadata().asMap();
|
||||
upsertStmt.setObject(4, GSON.toJson(metadata), Types.OTHER);
|
||||
} else {
|
||||
upsertStmt.setNull(3, Types.VARCHAR);
|
||||
upsertStmt.setNull(4, Types.OTHER);
|
||||
}
|
||||
upsertStmt.addBatch();
|
||||
}
|
||||
|
||||
upsertStmt.executeBatch();
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//adi
|
||||
public List<EmbeddingMatch<TextSegment>> findRelevantByKbUuid(String kbUuid, Embedding referenceEmbedding, int maxResults, double minScore) {
|
||||
List<EmbeddingMatch<TextSegment>> result = new ArrayList<>();
|
||||
try (Connection connection = setupConnection()) {
|
||||
String referenceVector = Arrays.toString(referenceEmbedding.vector());
|
||||
//新增查询条件kb_id
|
||||
String query = String.format(
|
||||
"WITH temp AS (SELECT (2 - (embedding <=> '%s')) / 2 AS score, embedding_id, embedding, text, metadata FROM %s where metadata->>'kb_uuid' = '%s') SELECT * FROM temp WHERE score >= %s ORDER BY score desc LIMIT %s;",
|
||||
referenceVector, table, kbUuid, minScore, maxResults);
|
||||
PreparedStatement selectStmt = connection.prepareStatement(query);
|
||||
|
||||
ResultSet resultSet = selectStmt.executeQuery();
|
||||
while (resultSet.next()) {
|
||||
double score = resultSet.getDouble("score");
|
||||
String embeddingId = resultSet.getString("embedding_id");
|
||||
|
||||
PGvector vector = (PGvector) resultSet.getObject("embedding");
|
||||
Embedding embedding = new Embedding(vector.toArray());
|
||||
|
||||
String text = resultSet.getString("text");
|
||||
TextSegment textSegment = null;
|
||||
if (isNotNullOrBlank(text)) {
|
||||
String metadataJson = Optional.ofNullable(resultSet.getString("metadata")).orElse("{}");
|
||||
Type type = new TypeToken<Map<String, String>>() {
|
||||
}.getType();
|
||||
Metadata metadata = new Metadata(new HashMap<>(GSON.fromJson(metadataJson, type)));
|
||||
textSegment = TextSegment.from(text, metadata);
|
||||
}
|
||||
|
||||
result.add(new EmbeddingMatch<>(score, embeddingId, embedding, textSegment));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int deleteByMetadata(String metadataKey, String metadataValue) {
|
||||
if (StringUtils.isAnyBlank(metadataKey, metadataValue)) {
|
||||
return NumberUtils.INTEGER_ZERO;
|
||||
}
|
||||
try (Connection connection = setupConnection()) {
|
||||
String query = String.format("delete from %s where metadata->'%s'=?", table, metadataKey);
|
||||
PreparedStatement prepareStatement = connection.prepareStatement(query);
|
||||
prepareStatement.setString(1, metadataValue);
|
||||
return prepareStatement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package com.moyz.adi.common.util;
|
||||
|
||||
import com.moyz.adi.common.model.RequestRateLimit;
|
||||
import com.moyz.adi.common.vo.RequestRateLimit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
|
@ -9,11 +9,16 @@ import java.lang.reflect.InvocationTargetException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Slf4j
|
||||
public class MPPageUtil {
|
||||
|
||||
public static <T, U> Page<U> convertTo(Page<T> source, Page<U> target, Class<U> targetRecordClass) {
|
||||
return MPPageUtil.convertTo(source, target, targetRecordClass, null);
|
||||
}
|
||||
|
||||
public static <T, U> Page<U> convertTo(Page<T> source, Page<U> target, Class<U> targetRecordClass, BiFunction<T, U, U> biFunction) {
|
||||
BeanUtils.copyProperties(source, target);
|
||||
List<U> records = new ArrayList<>();
|
||||
target.setRecords(records);
|
||||
|
@ -21,6 +26,9 @@ public class MPPageUtil {
|
|||
for (T t : source.getRecords()) {
|
||||
U u = targetRecordClass.getDeclaredConstructor().newInstance();
|
||||
BeanUtils.copyProperties(t, u);
|
||||
if (null != biFunction) {
|
||||
biFunction.apply(t, u);
|
||||
}
|
||||
records.add(u);
|
||||
}
|
||||
} catch (NoSuchMethodException e1) {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.moyz.adi.common.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class AnswerMeta {
|
||||
private Integer tokens;
|
||||
private String uuid;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.moyz.adi.common.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ChatMeta {
|
||||
private QuestionMeta question;
|
||||
private AnswerMeta answer;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package com.moyz.adi.common.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CostStat {
|
||||
private int day;
|
||||
private int textRequestTimesByDay;
|
||||
private int textTokenCostByDay;
|
||||
private int imageGeneratedNumberByDay;
|
||||
private int textTokenCostByMonth;
|
||||
private int textRequestTimesByMonth;
|
||||
private int imageGeneratedNumberByMonth;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.moyz.adi.common.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class QuestionMeta {
|
||||
private Integer tokens;
|
||||
private String uuid;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.moyz.adi.common.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RequestRateLimit {
|
||||
private int times;
|
||||
private int minutes;
|
||||
private int type;
|
||||
|
||||
public static final int TYPE_TEXT = 1;
|
||||
public static final int TYPE_IMAGE = 2;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.moyz.adi.common.vo;
|
||||
|
||||
import com.moyz.adi.common.entity.User;
|
||||
import com.moyz.adi.common.util.TriConsumer;
|
||||
import dev.langchain4j.memory.ChatMemory;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
|
||||
@Data
|
||||
public class SseAskParams {
|
||||
|
||||
private User user;
|
||||
|
||||
private String regenerateQuestionUuid;
|
||||
|
||||
private String systemMessage;
|
||||
|
||||
private ChatMemory chatMemory;
|
||||
|
||||
private String userMessage;
|
||||
|
||||
private SseEmitter sseEmitter;
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.moyz.adi.common.mapper.KnowledgeBaseEmbeddingMapper">
|
||||
|
||||
<select id="selectByItemUuid" resultType="com.moyz.adi.common.entity.KnowledgeBaseEmbedding">
|
||||
select * from adi_knowledge_base_embedding where metadata->>'kb_item_uuid' = #{kbItemUuid}
|
||||
</select>
|
||||
|
||||
<delete id="deleteByItemUuid">
|
||||
delete from adi_knowledge_base_embedding where metadata->>'kb_item_uuid' = #{kbItemUuid}
|
||||
</delete>
|
||||
</mapper>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.moyz.adi.common.mapper.KnowledgeBaseMapper">
|
||||
<select id="searchByAdmin" resultType="com.moyz.adi.common.entity.KnowledgeBase">
|
||||
select *
|
||||
from adi_knowledge_base
|
||||
where is_deleted = false
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and title like "%"#{keyword}"%"
|
||||
</if>
|
||||
order by update_time desc
|
||||
</select>
|
||||
|
||||
<select id="searchByUser" resultType="com.moyz.adi.common.entity.KnowledgeBase">
|
||||
select *
|
||||
from adi_knowledge_base
|
||||
where is_deleted = false
|
||||
<choose>
|
||||
<when test="includeOthersPublic">
|
||||
nd (is_public = true or owner_id = #{ownerId})
|
||||
</when>
|
||||
<otherwise>
|
||||
and owner_id = #{ownerId}
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
and title like "%"#{keyword}"%"
|
||||
</if>
|
||||
order by update_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.moyz.adi.common.mapper.KnowledgeBaseQaRecordMapper">
|
||||
|
||||
|
||||
</mapper>
|
668
docs/create.sql
668
docs/create.sql
|
@ -1,186 +1,528 @@
|
|||
CREATE TABLE `adi_ai_model`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(45) NOT NULL DEFAULT '',
|
||||
`remark` varchar(1000) DEFAULT NULL,
|
||||
`model_status` tinyint NOT NULL DEFAULT '1' COMMENT '1:正常使用,2:不可用',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='ai模型';
|
||||
-- 需要先安装pgvector这个扩展(https://github.com/pgvector/pgvector)
|
||||
-- CREATE EXTENSION vector;
|
||||
|
||||
CREATE TABLE `adi_sys_config`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(100) NOT NULL DEFAULT '',
|
||||
`value` varchar(100) NOT NULL DEFAULT '',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='系统配置表';
|
||||
SET client_encoding = 'UTF8';
|
||||
CREATE SCHEMA public;
|
||||
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
CREATE TABLE public.adi_ai_image
|
||||
(
|
||||
id bigserial primary key,
|
||||
user_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
uuid character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
prompt character varying(1024) DEFAULT ''::character varying NOT NULL,
|
||||
generate_size character varying(20) DEFAULT ''::character varying NOT NULL,
|
||||
generate_number integer DEFAULT 1 NOT NULL,
|
||||
original_image character varying(1000) DEFAULT ''::character varying NOT NULL,
|
||||
mask_image character varying(1000) DEFAULT ''::character varying NOT NULL,
|
||||
resp_images_path character varying(2048) DEFAULT ''::character varying NOT NULL,
|
||||
generated_images character varying(2048) DEFAULT ''::character varying NOT NULL,
|
||||
interacting_method smallint DEFAULT '1'::smallint NOT NULL,
|
||||
process_status smallint DEFAULT '1'::smallint NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT adi_ai_image_generate_number_check CHECK (((generate_number >= 1) AND (generate_number <= 10))),
|
||||
CONSTRAINT adi_ai_image_interacting_method_check CHECK ((interacting_method = ANY (ARRAY [1, 2, 3]))),
|
||||
CONSTRAINT adi_ai_image_process_status_check CHECK ((process_status = ANY (ARRAY [1, 2, 3]))),
|
||||
CONSTRAINT adi_ai_image_user_id_check CHECK ((user_id >= 0))
|
||||
);
|
||||
ALTER TABLE ONLY public.adi_ai_image
|
||||
ADD CONSTRAINT udx_uuid UNIQUE (uuid);
|
||||
COMMENT ON TABLE public.adi_ai_image IS 'Images generated by ai';
|
||||
COMMENT ON COLUMN public.adi_ai_image.user_id IS 'The user who generated the image';
|
||||
COMMENT ON COLUMN public.adi_ai_image.uuid IS 'The uuid of the request of generated images';
|
||||
COMMENT ON COLUMN public.adi_ai_image.prompt IS 'The prompt for generating images';
|
||||
COMMENT ON COLUMN public.adi_ai_image.generate_size IS 'The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024"';
|
||||
COMMENT ON COLUMN public.adi_ai_image.generate_number IS 'The number of images to generate. Must be between 1 and 10. Defaults to 1.';
|
||||
COMMENT ON COLUMN public.adi_ai_image.original_image IS 'The path of the original image (local path or http path), interacting_method must be 2/3';
|
||||
COMMENT ON COLUMN public.adi_ai_image.mask_image IS 'The path of the mask image (local path or http path), interacting_method must be 2';
|
||||
COMMENT ON COLUMN public.adi_ai_image.resp_images_path IS 'The url of the generated images which from openai response, separated by commas';
|
||||
|
||||
COMMENT ON COLUMN public.adi_ai_image.generated_images IS 'The path of the generated images, separated by commas';
|
||||
|
||||
COMMENT ON COLUMN public.adi_ai_image.interacting_method IS '1: Creating images from scratch based on a text prompt; 2: Creating edits of an existing image based on a new text prompt; 3: Creating variations of an existing image';
|
||||
COMMENT ON COLUMN public.adi_ai_image.process_status IS 'Generate image status, 1: doing, 2: fail, 3: success';
|
||||
COMMENT ON COLUMN public.adi_ai_image.create_time IS 'Timestamp of record creation';
|
||||
COMMENT ON COLUMN public.adi_ai_image.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
COMMENT ON COLUMN public.adi_ai_image.is_deleted IS 'Flag indicating whether the record is deleted (0: not deleted, 1: deleted)';
|
||||
|
||||
|
||||
CREATE TABLE public.adi_ai_model
|
||||
(
|
||||
id bigserial primary key,
|
||||
name character varying(45) DEFAULT ''::character varying NOT NULL,
|
||||
remark character varying(1000),
|
||||
model_status smallint DEFAULT '1'::smallint NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT adi_ai_model_model_status_check CHECK ((model_status = ANY (ARRAY [1, 2])))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_ai_model IS 'ai模型';
|
||||
|
||||
COMMENT ON COLUMN public.adi_ai_model.name IS 'The name of the AI model';
|
||||
COMMENT ON COLUMN public.adi_ai_model.remark IS 'Additional remarks about the AI model';
|
||||
COMMENT ON COLUMN public.adi_ai_model.model_status IS '1: Normal usage, 2: Not available';
|
||||
|
||||
COMMENT ON COLUMN public.adi_ai_model.create_time IS 'Timestamp of record creation';
|
||||
|
||||
COMMENT ON COLUMN public.adi_ai_model.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
|
||||
CREATE TABLE public.adi_conversation
|
||||
(
|
||||
id bigserial primary key,
|
||||
user_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
uuid character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
title character varying(45) DEFAULT ''::character varying NOT NULL,
|
||||
openai_conversation_id character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
tokens integer DEFAULT 0 NOT NULL,
|
||||
ai_system_message character varying(1000) DEFAULT ''::character varying NOT NULL,
|
||||
ai_model character varying(45) DEFAULT ''::character varying NOT NULL,
|
||||
understand_context_enable boolean DEFAULT false NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_conversation IS '对话表';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation.user_id IS '用户id';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation.ai_model IS '模型名称';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation.title IS '对话标题';
|
||||
|
||||
CREATE TABLE public.adi_conversation_message
|
||||
(
|
||||
id bigserial primary key,
|
||||
parent_message_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
conversation_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
conversation_uuid character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
remark text NOT NULL,
|
||||
uuid character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
message_role integer DEFAULT 1 NOT NULL,
|
||||
tokens integer DEFAULT 0 NOT NULL,
|
||||
openai_message_id character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
user_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
secret_key_type integer DEFAULT 1 NOT NULL,
|
||||
understand_context_msg_pair_num integer DEFAULT 0 NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE public.adi_conversation_message IS '对话消息表';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.parent_message_id IS '父级消息id';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.conversation_id IS '对话id';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.conversation_uuid IS 'conversation''s uuid';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.remark IS 'ai回复的消息';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.uuid IS '唯一标识消息的UUID';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.message_role IS '产生该消息的角色:1: 用户, 2: 系统, 3: 助手';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.tokens IS '消耗的token数量';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.openai_message_id IS 'OpenAI生成的消息ID';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.user_id IS '用户ID';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.secret_key_type IS '加密密钥类型';
|
||||
|
||||
COMMENT ON COLUMN public.adi_conversation_message.understand_context_msg_pair_num IS '上下文消息对数量';
|
||||
|
||||
CREATE TABLE public.adi_file
|
||||
(
|
||||
id bigserial primary key,
|
||||
name character varying(36) DEFAULT ''::character varying NOT NULL,
|
||||
uuid character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
ext character varying(36) DEFAULT ''::character varying NOT NULL,
|
||||
user_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
path character varying(250) DEFAULT ''::character varying NOT NULL,
|
||||
ref_count integer DEFAULT 0 NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL,
|
||||
md5 character varying(128) DEFAULT ''::character varying NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_file IS '文件';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.name IS 'File name';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.uuid IS 'UUID of the file';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.ext IS 'File extension';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.user_id IS '0: System; Other: User';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.path IS 'File path';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.ref_count IS 'The number of references to this file';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.create_time IS 'Timestamp of record creation';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.is_deleted IS '0: Normal; 1: Deleted';
|
||||
|
||||
COMMENT ON COLUMN public.adi_file.md5 IS 'MD5 hash of the file';
|
||||
|
||||
CREATE TABLE public.adi_prompt
|
||||
(
|
||||
id bigserial primary key,
|
||||
user_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
act character varying(120) DEFAULT ''::character varying NOT NULL,
|
||||
prompt text NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_prompt IS '提示词';
|
||||
|
||||
COMMENT ON COLUMN public.adi_prompt.user_id IS '所属用户(0: system)';
|
||||
|
||||
COMMENT ON COLUMN public.adi_prompt.act IS '提示词标题';
|
||||
|
||||
COMMENT ON COLUMN public.adi_prompt.prompt IS '提示词内容';
|
||||
|
||||
COMMENT ON COLUMN public.adi_prompt.create_time IS 'Timestamp of record creation';
|
||||
|
||||
COMMENT ON COLUMN public.adi_prompt.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
|
||||
COMMENT ON COLUMN public.adi_prompt.is_deleted IS '0:未删除;1:已删除';
|
||||
|
||||
CREATE TABLE public.adi_sys_config
|
||||
(
|
||||
id bigserial primary key,
|
||||
name character varying(100) DEFAULT ''::character varying NOT NULL,
|
||||
value character varying(100) DEFAULT ''::character varying NOT NULL,
|
||||
create_time timestamp DEFAULT localtimestamp NOT NULL,
|
||||
update_time timestamp DEFAULT localtimestamp NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_sys_config IS '系统配置表';
|
||||
|
||||
COMMENT ON COLUMN public.adi_sys_config.name IS '配置项名称';
|
||||
|
||||
COMMENT ON COLUMN public.adi_sys_config.value IS '配置项值';
|
||||
|
||||
COMMENT ON COLUMN public.adi_sys_config.create_time IS 'Timestamp of record creation';
|
||||
|
||||
COMMENT ON COLUMN public.adi_sys_config.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
|
||||
COMMENT ON COLUMN public.adi_sys_config.is_deleted IS '0:未删除;1:已删除';
|
||||
|
||||
CREATE TABLE public.adi_user
|
||||
(
|
||||
id bigserial primary key,
|
||||
name character varying(45) DEFAULT ''::character varying NOT NULL,
|
||||
password character varying(120) DEFAULT ''::character varying NOT NULL,
|
||||
uuid character varying(32) DEFAULT ''::character varying NOT NULL,
|
||||
email character varying(120) DEFAULT ''::character varying NOT NULL,
|
||||
active_time timestamp,
|
||||
user_status smallint DEFAULT '1'::smallint NOT NULL,
|
||||
is_admin boolean DEFAULT false NOT NULL,
|
||||
quota_by_token_daily integer DEFAULT 0 NOT NULL,
|
||||
quota_by_token_monthly integer DEFAULT 0 NOT NULL,
|
||||
quota_by_request_daily integer DEFAULT 0 NOT NULL,
|
||||
quota_by_request_monthly integer DEFAULT 0 NOT NULL,
|
||||
secret_key character varying(120) DEFAULT ''::character varying NOT NULL,
|
||||
understand_context_enable smallint DEFAULT '0'::smallint NOT NULL,
|
||||
understand_context_msg_pair_num integer DEFAULT 0 NOT NULL,
|
||||
quota_by_image_daily integer DEFAULT 0 NOT NULL,
|
||||
quota_by_image_monthly integer DEFAULT 0 NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_user IS '用户表';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.name IS '用户名';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.password IS '密码';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.uuid IS 'UUID of the user';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.email IS '用户邮箱';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.active_time IS '激活时间';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.create_time IS 'Timestamp of record creation';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.user_status IS '用户状态,1:待验证;2:正常;3:冻结';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.is_admin IS '是否管理员,0:否;1:是';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.is_deleted IS '0:未删除;1:已删除';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.quota_by_token_daily IS '每日token配额';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.quota_by_token_monthly IS '每月token配额';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.quota_by_request_daily IS '每日请求配额';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.quota_by_request_monthly IS '每月请求配额';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.secret_key IS '用户密钥';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.understand_context_enable IS '上下文理解开关';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.understand_context_msg_pair_num IS '上下文消息对数量';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.quota_by_image_daily IS '每日图片配额';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user.quota_by_image_monthly IS '每月图片配额';
|
||||
|
||||
CREATE TABLE public.adi_user_day_cost
|
||||
(
|
||||
id bigserial primary key,
|
||||
user_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
day integer DEFAULT 0 NOT NULL,
|
||||
requests integer DEFAULT 0 NOT NULL,
|
||||
tokens integer DEFAULT 0 NOT NULL,
|
||||
create_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
update_time timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
secret_key_type integer DEFAULT 0 NOT NULL,
|
||||
images_number integer DEFAULT 0 NOT NULL,
|
||||
is_deleted boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.adi_user_day_cost IS '用户每天消耗总量表';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.user_id IS '用户ID';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.day IS '日期,用7位整数表示,如20230901';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.requests IS '请求数量';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.tokens IS '消耗的token数量';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.create_time IS 'Timestamp of record creation';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.update_time IS 'Timestamp of record last update, automatically updated on each update';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.secret_key_type IS '加密密钥类型';
|
||||
|
||||
COMMENT ON COLUMN public.adi_user_day_cost.images_number IS '图片数量';
|
||||
|
||||
|
||||
-- update_time trigger
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_modified_column()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
NEW.update_time = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER trigger_ai_image_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_ai_image
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_ai_model_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_ai_model
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_conv_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_conversation
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_conv_message_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_conversation_message
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_file_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_file
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_prompt_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_prompt
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_sys_config_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_sys_config
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_user_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_user
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
CREATE TRIGGER trigger_user_day_cost_update_time
|
||||
BEFORE UPDATE
|
||||
ON adi_user_day_cost
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_modified_column();
|
||||
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('secret_key', '');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('request_text_rate_limit', '{"times":24,"minutes":3}');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('request_image_rate_limit', '{"times":6,"minutes":3}');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('conversation_max_num', '50');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_token_daily', '10000');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_token_monthly', '200000');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_request_daily', '150');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_request_monthly', '3000');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_image_daily', '30');
|
||||
INSERT INTO `adi_sys_config` (`name`, `value`)
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_image_monthly', '300');
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_qa_ask_daily', '50');
|
||||
INSERT INTO adi_sys_config (name, value)
|
||||
VALUES ('quota_by_qa_item_monthly', '100');
|
||||
|
||||
CREATE TABLE `adi_conversation`
|
||||
create table adi_knowledge_base
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户id',
|
||||
`title` varchar(45) NOT NULL DEFAULT '' COMMENT '对话标题',
|
||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
||||
`understand_context_enable` tinyint NOT NULL default '0' COMMENT '是否开启上下文理解',
|
||||
`ai_model` varchar(45) NOT NULL DEFAULT '' COMMENT 'ai model',
|
||||
`ai_system_message` varchar(1024) NOT NULL DEFAULT '' COMMENT 'set the system message to ai, ig: you are a lawyer',
|
||||
`tokens` int NOT NULL DEFAULT '0' COMMENT '消耗token数量',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='对话表';
|
||||
id bigserial primary key,
|
||||
uuid varchar(32) default ''::character varying not null,
|
||||
title varchar(250) default ''::character varying not null,
|
||||
remark text default ''::character varying not null,
|
||||
is_public boolean default false not null,
|
||||
owner_id bigint default 0 not null,
|
||||
owner_name varchar(45) default ''::character varying not null,
|
||||
create_time timestamp default CURRENT_TIMESTAMP not null,
|
||||
update_time timestamp default CURRENT_TIMESTAMP not null,
|
||||
is_deleted boolean default false not null
|
||||
);
|
||||
|
||||
CREATE TABLE `adi_conversation_message`
|
||||
comment on table adi_knowledge_base is '知识库';
|
||||
|
||||
comment on column adi_knowledge_base.title is '知识库名称';
|
||||
|
||||
comment on column adi_knowledge_base.remark is '知识库描述';
|
||||
|
||||
comment on column adi_knowledge_base.is_public is '是否公开';
|
||||
|
||||
comment on column adi_knowledge_base.owner_id is '所属人id';
|
||||
|
||||
comment on column adi_knowledge_base.owner_name is '所属人名称';
|
||||
|
||||
comment on column adi_knowledge_base.create_time is '创建时间';
|
||||
|
||||
comment on column adi_knowledge_base.update_time is '更新时间';
|
||||
|
||||
comment on column adi_knowledge_base.is_deleted is '0:未删除;1:已删除';
|
||||
|
||||
create trigger trigger_kb_update_time
|
||||
before update
|
||||
on adi_knowledge_base
|
||||
for each row
|
||||
execute procedure update_modified_column();
|
||||
|
||||
create table adi_knowledge_base_item
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`parent_message_id` bigint NOT NULL DEFAULT '0' COMMENT '父级消息id',
|
||||
`conversation_id` bigint NOT NULL DEFAULT '0',
|
||||
`conversation_uuid` varchar(32) NOT NULL DEFAULT '',
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User id',
|
||||
`content` text NOT NULL COMMENT '对话的消息',
|
||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
||||
`message_role` varchar(25) NOT NULL DEFAULT '' COMMENT '产生该消息的角色:1:user,2:system,3:assistant',
|
||||
`tokens` int NOT NULL DEFAULT '0' COMMENT '消耗的token数量',
|
||||
`secret_key_type` int NOT NULL DEFAULT '1' COMMENT '1:System secret key,2:User secret key',
|
||||
`understand_context_msg_pair_num` int NOT NULL DEFAULT '0' COMMENT 'If context understanding enable, context_pair_msg_num > 0',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除,0:未删除,1:已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `udx_uuid` (`uuid`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='对话信息表';
|
||||
id bigserial primary key,
|
||||
uuid varchar(32) default ''::character varying not null,
|
||||
kb_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
kb_uuid varchar(32) default ''::character varying not null,
|
||||
source_file_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
title varchar(250) default ''::character varying not null,
|
||||
brief varchar(250) default ''::character varying not null,
|
||||
remark text default ''::character varying not null,
|
||||
is_embedded boolean default false not null,
|
||||
create_time timestamp default CURRENT_TIMESTAMP not null,
|
||||
update_time timestamp default CURRENT_TIMESTAMP not null,
|
||||
is_deleted boolean default false not null
|
||||
);
|
||||
|
||||
CREATE TABLE `adi_ai_image`
|
||||
comment on table adi_knowledge_base_item is '知识库-条目';
|
||||
|
||||
comment on column adi_knowledge_base_item.kb_id is '所属知识库id';
|
||||
|
||||
comment on column adi_knowledge_base_item.source_file_id is '来源文件id';
|
||||
|
||||
comment on column adi_knowledge_base_item.title is '条目标题';
|
||||
|
||||
comment on column adi_knowledge_base_item.brief is '条目内容摘要';
|
||||
|
||||
comment on column adi_knowledge_base_item.remark is '条目内容';
|
||||
|
||||
comment on column adi_knowledge_base_item.is_embedded is '是否已向量化,0:否,1:是';
|
||||
|
||||
comment on column adi_knowledge_base_item.create_time is '创建时间';
|
||||
|
||||
comment on column adi_knowledge_base_item.update_time is '更新时间';
|
||||
|
||||
comment on column adi_knowledge_base_item.is_deleted is '0:未删除;1:已删除';
|
||||
|
||||
create trigger trigger_kb_item_update_time
|
||||
before update
|
||||
on adi_knowledge_base_item
|
||||
for each row
|
||||
execute procedure update_modified_column();
|
||||
|
||||
create table adi_knowledge_base_qa_record
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'The user who generated the image',
|
||||
`uuid` varchar(32) NOT NULL DEFAULT '' COMMENT 'The uuid of the request of generated images',
|
||||
`prompt` varchar(1024) NOT NULL DEFAULT '' COMMENT 'The prompt for generating images',
|
||||
`generate_size` varchar(20) NOT NULL DEFAULT '' COMMENT 'The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024"',
|
||||
`generate_number` int NOT NULL DEFAULT '1' COMMENT 'The number of images to generate. Must be between 1 and 10. Defaults to 1.',
|
||||
`original_image` varchar(32) NOT NULL DEFAULT '' COMMENT 'The original image uuid,interacting_method must be 2/3',
|
||||
`mask_image` varchar(32) NOT NULL DEFAULT '' COMMENT 'The mask image uuid,interacting_method must be 2',
|
||||
`resp_images_path` varchar(2048) NOT NULL DEFAULT '' COMMENT 'The url of the generated images which from openai response,separated by commas',
|
||||
`generated_images` varchar(512) NOT NULL DEFAULT '' COMMENT 'The uuid of the generated images,separated by commas',
|
||||
`interacting_method` smallint NOT NULL DEFAULT '1' COMMENT '1:Creating images from scratch based on a text prompt;2:Creating edits of an existing image based on a new text prompt;3:Creating variations of an existing image',
|
||||
`process_status` smallint NOT NULL DEFAULT '1' COMMENT 'Process status,1:processing,2:fail,3:success',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `udx_uuid` (`uuid`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='Images generated by ai';
|
||||
id bigserial primary key,
|
||||
uuid varchar(32) default ''::character varying not null,
|
||||
kb_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||
kb_uuid varchar(32) default ''::character varying not null,
|
||||
question varchar(1000) default ''::character varying not null,
|
||||
answer text default ''::character varying not null,
|
||||
source_file_ids varchar(500) default ''::character varying not null,
|
||||
user_id bigint default '0' NOT NULL,
|
||||
create_time timestamp default CURRENT_TIMESTAMP not null,
|
||||
update_time timestamp default CURRENT_TIMESTAMP not null,
|
||||
is_deleted boolean default false not null
|
||||
);
|
||||
|
||||
CREATE TABLE `adi_user`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(45) NOT NULL DEFAULT '',
|
||||
`password` varchar(120) NOT NULL DEFAULT '',
|
||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
||||
`email` varchar(120) NOT NULL DEFAULT '',
|
||||
`active_time` datetime NULL COMMENT '激活时间',
|
||||
`secret_key` varchar(120) NOT NULL default '' COMMENT 'Custom openai secret key',
|
||||
`understand_context_msg_pair_num` int NOT NULL default '0' COMMENT '上下文理解中需要携带的消息对数量(提示词及回复)',
|
||||
`quota_by_token_daily` int NOT NULL DEFAULT '0' COMMENT 'The quota of token daily',
|
||||
`quota_by_token_monthly` int NOT NULL DEFAULT '0' COMMENT 'The quota of token monthly',
|
||||
`quota_by_request_daily` int NOT NULL DEFAULT '0' COMMENT 'The quota of http request daily',
|
||||
`quota_by_request_monthly` int NOT NULL DEFAULT '0' COMMENT 'The quota of http request monthly',
|
||||
`quota_by_image_daily` int NOT NULL DEFAULT '0' COMMENT 'The quota of generate images daily',
|
||||
`quota_by_image_monthly` int NOT NULL DEFAULT '0' COMMENT 'The quota of generate images monthly',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`user_status` smallint NOT NULL DEFAULT '1' COMMENT '用户状态,1:待验证;2:正常;3:冻结',
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '0:未删除;1:已删除',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `udx_uuid` (`uuid`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='用户表';
|
||||
comment on table adi_knowledge_base_qa_record is '知识库-提问记录';
|
||||
|
||||
CREATE TABLE `adi_user_day_cost`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL DEFAULT '0',
|
||||
`day` int NOT NULL DEFAULT '0' COMMENT '日期,用7位整数表示,如20230901',
|
||||
`requests` int NOT NULL DEFAULT '0' COMMENT 'The number of http request',
|
||||
`tokens` int NOT NULL DEFAULT '0' COMMENT 'The cost of the tokens',
|
||||
`images_number` int NOT NULL DEFAULT '0' COMMENT 'The number of images',
|
||||
`secret_key_type` int NOT NULL DEFAULT '1' COMMENT '1:System secret key,2:Custom secret key',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='用户每天消耗总量表';
|
||||
comment on column adi_knowledge_base_qa_record.kb_id is '所属知识库id';
|
||||
|
||||
comment on column adi_knowledge_base_qa_record.kb_uuid is '所属知识库uuid';
|
||||
|
||||
CREATE TABLE `adi_prompt`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '0:System,other:User',
|
||||
`act` varchar(120) NOT NULL DEFAULT '' COMMENT 'Short prompt for search/autocomplete',
|
||||
`prompt` varchar(1024) NOT NULL COMMENT 'Prompt content',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '0:Normal;1:Deleted',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_title` (`act`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='提示词';
|
||||
comment on column adi_knowledge_base_qa_record.question is '问题';
|
||||
|
||||
CREATE TABLE `adi_file`
|
||||
(
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(32) NOT NULL DEFAULT '',
|
||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
||||
`md5` varchar(128) NOT NULL DEFAULT '',
|
||||
`ext` varchar(32) NOT NULL DEFAULT '',
|
||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '0:System,other:User',
|
||||
`path` varchar(250) NOT NULL DEFAULT '',
|
||||
`ref_count` int NOT NULL DEFAULT '0' COMMENT 'The number of references to this file',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '0:Normal;1:Deleted',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_uuid` (`uuid`)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4
|
||||
COLLATE = utf8mb4_general_ci COMMENT ='提示词';
|
||||
comment on column adi_knowledge_base_qa_record.answer is '答案';
|
||||
|
||||
comment on column adi_knowledge_base_qa_record.source_file_ids is '来源文档id,以逗号隔开';
|
||||
|
||||
comment on column adi_knowledge_base_qa_record.user_id is '提问用户id';
|
||||
|
||||
comment on column adi_knowledge_base_qa_record.create_time is '创建时间';
|
||||
|
||||
comment on column adi_knowledge_base_qa_record.update_time is '更新时间';
|
||||
|
||||
comment on column adi_knowledge_base_qa_record.is_deleted is '0:未删除;1:已删除';
|
||||
|
||||
create trigger trigger_kb_qa_record_update_time
|
||||
before update
|
||||
on adi_knowledge_base_qa_record
|
||||
for each row
|
||||
execute procedure update_modified_column();
|
||||
|
|
36
pom.xml
36
pom.xml
|
@ -25,6 +25,7 @@
|
|||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<langchain4j.version>0.25.0</langchain4j.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -68,9 +69,8 @@
|
|||
<version>31.1-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.0.32</version>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.theokanning.openai-gpt3-java</groupId>
|
||||
|
@ -130,6 +130,36 @@
|
|||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai-spring-boot-starter</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-embeddings-all-minilm-l6-v2</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-document-parser-apache-poi</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
|
Loading…
Reference in New Issue