增加RAG大模型知识库
This commit is contained in:
parent
a2c1ed5390
commit
029d6b9d50
22
README.md
22
README.md
|
@ -1,4 +1,4 @@
|
||||||
# Getting Started
|
## Getting Started
|
||||||
|
|
||||||
> 声明:此项目只发布于 Github,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号等行为,谨防受骗。
|
> 声明:此项目只发布于 Github,基于 MIT 协议,免费且作为开源学习使用。并且不会有任何形式的卖号等行为,谨防受骗。
|
||||||
|
|
||||||
|
@ -20,19 +20,19 @@
|
||||||
|
|
||||||
#### 初始化
|
#### 初始化
|
||||||
|
|
||||||
* 初始化数据库
|
初始化数据库
|
||||||
|
|
||||||
* 创建数据库aideepin
|
* 创建数据库aideepin
|
||||||
* 执行docs/create.sql
|
* 执行docs/create.sql
|
||||||
* 填充openai的secret_key
|
* 填充openai的secret\_key
|
||||||
|
|
||||||
```
|
```plaintext
|
||||||
update adi_sys_config set value = 'my_chatgpt_secret_key' where name = 'secret_key'
|
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
|
* redis: application-[dev|prod].xml中的spring.data.redis
|
||||||
* mail: application.xml中的spring.mail
|
* 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
|
cd aideepin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -50,18 +50,18 @@ cd aideepin
|
||||||
mvn clean package -Dmaven.test.skip=true
|
mvn clean package -Dmaven.test.skip=true
|
||||||
```
|
```
|
||||||
|
|
||||||
* 运行
|
* 运行
|
||||||
|
|
||||||
a. jar包启动:
|
a. jar包启动:
|
||||||
|
|
||||||
```
|
```plaintext
|
||||||
cd adi-chat/target
|
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 &
|
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启动
|
b. docker启动
|
||||||
|
|
||||||
```
|
```plaintext
|
||||||
cd adi-chat
|
cd adi-chat
|
||||||
docker build . -t aideepin:0.0.1
|
docker build . -t aideepin:0.0.1
|
||||||
docker run -d \
|
docker run -d \
|
||||||
|
|
|
@ -11,18 +11,7 @@
|
||||||
|
|
||||||
<artifactId>adi-admin</artifactId>
|
<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>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>com.moyz</groupId>
|
|
||||||
<artifactId>adi-bootstrap</artifactId>
|
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.moyz</groupId>
|
<groupId>com.moyz</groupId>
|
||||||
<artifactId>adi-common</artifactId>
|
<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>
|
<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>
|
<build>
|
||||||
<resources>
|
<resources>
|
||||||
<resource>
|
<resource>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
url: jdbc:postgresql://172.17.18.164:5432/aideepin2?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
||||||
username: root
|
username: postgres
|
||||||
password: 123456
|
password: 123456
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: org.postgresql.Driver
|
||||||
url: jdbc:mysql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
url: jdbc:postgresql://localhost:3306/aideepin?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&tinyInt1isBit=false&allowMultiQueries=true
|
||||||
username: your-mysql-account
|
username: your-db-account
|
||||||
password: your-mysql-password
|
password: your-db-password
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: localhost
|
host: localhost
|
||||||
|
|
|
@ -12,11 +12,6 @@
|
||||||
<artifactId>adi-chat</artifactId>
|
<artifactId>adi-chat</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>com.moyz</groupId>
|
|
||||||
<artifactId>adi-bootstrap</artifactId>
|
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.moyz</groupId>
|
<groupId>com.moyz</groupId>
|
||||||
<artifactId>adi-common</artifactId>
|
<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)
|
@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) {
|
public Map<String, String> upload(@RequestPart(value = "file") MultipartFile file) {
|
||||||
Map<String, String> result = new HashMap<>();
|
Map<String, String> result = new HashMap<>();
|
||||||
result.put("uuid", fileService.writeToLocal(file));
|
result.put("uuid", fileService.writeToLocal(file).getUuid());
|
||||||
return result;
|
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 = "用户信息")
|
@Operation(summary = "用户信息")
|
||||||
@GetMapping("/{uuid}")
|
@GetMapping("/{uuid}")
|
||||||
public void login(@Validated @PathVariable String uuid) {
|
public void info(@Validated @PathVariable String uuid) {
|
||||||
log.info(uuid);
|
log.info(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.util.Collections;
|
||||||
|
|
||||||
public class CodeGenerator {
|
public class CodeGenerator {
|
||||||
public static void main(String[] args) {
|
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 -> {
|
.globalConfig(builder -> {
|
||||||
builder.author("moyz") // 设置作者
|
builder.author("moyz") // 设置作者
|
||||||
.enableSwagger() // 开启 swagger 模式
|
.enableSwagger() // 开启 swagger 模式
|
||||||
|
@ -35,7 +35,7 @@ public class CodeGenerator {
|
||||||
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatisplus-generatorcode")); // 设置mapperXml生成路径
|
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatisplus-generatorcode")); // 设置mapperXml生成路径
|
||||||
})
|
})
|
||||||
.strategyConfig(builder -> {
|
.strategyConfig(builder -> {
|
||||||
builder.addInclude("adi_user,adi_conversation,adi_conversation_message") // 设置需要生成的表名
|
builder.addInclude("adi_knowledge_base_qa_record") // 设置需要生成的表名
|
||||||
.addTablePrefix("adi_");
|
.addTablePrefix("adi_");
|
||||||
builder.mapperBuilder().enableBaseResultMap().enableMapperAnnotation().build();
|
builder.mapperBuilder().enableBaseResultMap().enableMapperAnnotation().build();
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
package com.moyz.adi.common.config;
|
package com.moyz.adi.common.config;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
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.MybatisPlusInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
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.plugins.inner.PaginationInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||||
|
import com.pgvector.PGvector;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.ibatis.session.SqlSessionFactory;
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -34,7 +34,7 @@ public class BeanConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RestTemplate restTemplate() {
|
public RestTemplate restTemplate() {
|
||||||
log.info("Configuration==create restTemplate");
|
log.info("Configuration:create restTemplate");
|
||||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||||
// 设置建立连接超时时间 毫秒
|
// 设置建立连接超时时间 毫秒
|
||||||
requestFactory.setConnectTimeout(60000);
|
requestFactory.setConnectTimeout(60000);
|
||||||
|
@ -50,9 +50,9 @@ public class BeanConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
public ObjectMapper objectMapper() {
|
||||||
log.info("Configuration==create objectMapper");
|
log.info("Configuration:create objectMapper");
|
||||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder().createXmlMapper(false).build();
|
||||||
objectMapper.registerModules(LocalDateTimeUtil.getSimpleModule(), new JavaTimeModule(), new Jdk8Module());
|
objectMapper.registerModules(LocalDateTimeUtil.getSimpleModule(), new JavaTimeModule(), new Jdk8Module());
|
||||||
//设置null值不参与序列化(字段不被显示)
|
//设置null值不参与序列化(字段不被显示)
|
||||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
@ -88,12 +88,18 @@ public class BeanConfig {
|
||||||
bean.setDataSource(dataSource);
|
bean.setDataSource(dataSource);
|
||||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
// 分页插件
|
// 分页插件
|
||||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
|
||||||
// 防止全表更新
|
// 防止全表更新
|
||||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||||
bean.setPlugins(interceptor);
|
bean.setPlugins(interceptor);
|
||||||
bean.setMapperLocations(
|
bean.setMapperLocations(
|
||||||
new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));
|
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();
|
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 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_GENERATE_IMAGE = 1;
|
||||||
public static final int INTERACTING_METHOD_EDIT_IMAGE = 2;
|
public static final int INTERACTING_METHOD_EDIT_IMAGE = 2;
|
||||||
public static final int INTERACTING_METHOD_VARIATION = 3;
|
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 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,用于校验后续流程中的重置密码使用
|
* 值: 用户id,用于校验后续流程中的重置密码使用
|
||||||
*/
|
*/
|
||||||
public static final String FIND_MY_PASSWORD = "user:find:password:{0}";
|
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;
|
private Long parentMessageId;
|
||||||
|
|
||||||
@Schema(title = "对话的消息")
|
@Schema(title = "对话的消息")
|
||||||
@TableField("content")
|
@TableField("remark")
|
||||||
private String content;
|
private String remark;
|
||||||
|
|
||||||
@Schema(title = "产生该消息的角色:1: 用户,2:系统,3:助手")
|
@Schema(title = "产生该消息的角色:1: 用户,2:系统,3:助手")
|
||||||
private String messageRole;
|
private Integer messageRole;
|
||||||
|
|
||||||
@Schema(title = "消耗的token数量")
|
@Schema(title = "消耗的token数量")
|
||||||
private Integer tokens;
|
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")
|
@TableField(value = "ref_count")
|
||||||
private Integer refCount;
|
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")
|
@TableField("process_status")
|
||||||
private Integer processStatus;
|
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.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import lombok.Getter;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Setter;
|
import lombok.Data;
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Getter
|
@Data
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public class BaseEntity implements Serializable {
|
public class BaseEntity implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
@ -26,4 +23,7 @@ public class BaseEntity implements Serializable {
|
||||||
@TableField(value = "update_time")
|
@TableField(value = "update_time")
|
||||||
private LocalDateTime updateTime;
|
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")
|
@Schema(title = "set the system message to ai, ig: you are a lawyer")
|
||||||
@TableField("ai_system_message")
|
@TableField("ai_system_message")
|
||||||
private String aiSystemMessage;
|
private String aiSystemMessage;
|
||||||
|
|
||||||
@TableField(value = "is_delete")
|
|
||||||
private Boolean isDelete;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,12 @@ public class ConversationMessage extends BaseEntity {
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
@Schema(title = "对话的消息")
|
@Schema(title = "对话的消息")
|
||||||
@TableField("content")
|
@TableField("remark")
|
||||||
private String content;
|
private String remark;
|
||||||
|
|
||||||
@Schema(title = "产生该消息的角色:1: 用户,2:系统,3:助手")
|
@Schema(title = "产生该消息的角色:1: 用户,2:系统,3:助手")
|
||||||
@TableField("message_role")
|
@TableField("message_role")
|
||||||
private String messageRole;
|
private Integer messageRole;
|
||||||
|
|
||||||
@Schema(title = "消耗的token数量")
|
@Schema(title = "消耗的token数量")
|
||||||
@TableField("tokens")
|
@TableField("tokens")
|
||||||
|
@ -57,7 +57,4 @@ public class ConversationMessage extends BaseEntity {
|
||||||
@Schema(name = "上下文理解中携带的消息对数量(提示词及回复)")
|
@Schema(name = "上下文理解中携带的消息对数量(提示词及回复)")
|
||||||
@TableField("understand_context_msg_pair_num")
|
@TableField("understand_context_msg_pair_num")
|
||||||
private Integer understandContextMsgPairNum;
|
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")
|
@TableField(value = "prompt")
|
||||||
private String 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 = "配置项的值")
|
@Schema(title = "配置项的值")
|
||||||
private String value;
|
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")
|
@TableField("active_time")
|
||||||
private LocalDateTime activeTime;
|
private LocalDateTime activeTime;
|
||||||
|
|
||||||
@TableField("is_delete")
|
@Schema(title = "是否管理员(0:否,1:是)")
|
||||||
private Boolean isDelete;
|
@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_REGISTER_USER_EXIST("A0013", "账号已经存在,请使用账号密码登录"),
|
||||||
A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
|
A_FIND_PASSWORD_CODE_ERROR("A0014", "重置码已过期或不存在"),
|
||||||
A_USER_WAIT_CONFIRM("A0015", "用户未激活"),
|
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_UNCAUGHT_ERROR("B0001", "未捕捉异常"),
|
||||||
B_COMMON_ERROR("B0002", "业务出错"),
|
B_COMMON_ERROR("B0002", "业务出错"),
|
||||||
B_GLOBAL_ERROR("B0003", "全局异常"),
|
B_GLOBAL_ERROR("B0003", "全局异常"),
|
||||||
|
|
|
@ -29,7 +29,7 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
||||||
public class TokenFilter extends OncePerRequestFilter {
|
public class TokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
public static final String[] EXCLUDE_API = {
|
public static final String[] EXCLUDE_API = {
|
||||||
"/auth/",
|
"/auth/"
|
||||||
};
|
};
|
||||||
|
|
||||||
@Resource
|
@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;
|
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.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.AiImage;
|
||||||
import com.moyz.adi.common.entity.User;
|
import com.moyz.adi.common.entity.User;
|
||||||
import com.moyz.adi.common.enums.ErrorEnum;
|
import com.moyz.adi.common.enums.ErrorEnum;
|
||||||
import com.moyz.adi.common.exception.BaseException;
|
import com.moyz.adi.common.exception.BaseException;
|
||||||
import com.moyz.adi.common.model.AnswerMeta;
|
import com.moyz.adi.common.interfaces.IChatAssistant;
|
||||||
import com.moyz.adi.common.model.ChatMeta;
|
|
||||||
import com.moyz.adi.common.model.QuestionMeta;
|
|
||||||
import com.moyz.adi.common.service.FileService;
|
import com.moyz.adi.common.service.FileService;
|
||||||
import com.moyz.adi.common.service.SysConfigService;
|
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.ImageUtil;
|
||||||
import com.moyz.adi.common.util.JsonUtil;
|
import com.moyz.adi.common.util.JsonUtil;
|
||||||
import com.moyz.adi.common.util.TriConsumer;
|
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.OpenAiApi;
|
||||||
import com.theokanning.openai.completion.chat.ChatCompletionChoice;
|
import com.theokanning.openai.image.CreateImageEditRequest;
|
||||||
import com.theokanning.openai.completion.chat.ChatCompletionRequest;
|
import com.theokanning.openai.image.CreateImageVariationRequest;
|
||||||
import com.theokanning.openai.completion.chat.ChatMessage;
|
import com.theokanning.openai.image.ImageResult;
|
||||||
import com.theokanning.openai.image.*;
|
|
||||||
import com.theokanning.openai.service.OpenAiService;
|
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 jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
@ -35,19 +39,23 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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_RESP_FORMATS_URL;
|
||||||
import static com.moyz.adi.common.cosntant.AdiConstant.OPENAI_CREATE_IMAGE_SIZES;
|
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.defaultClient;
|
||||||
import static com.theokanning.openai.service.OpenAiService.defaultRetrofit;
|
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
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
@ -62,21 +70,23 @@ public class OpenAiHelper {
|
||||||
@Value("${openai.proxy.http-port:0}")
|
@Value("${openai.proxy.http-port:0}")
|
||||||
private int proxyHttpPort;
|
private int proxyHttpPort;
|
||||||
|
|
||||||
@Value("${local.images}")
|
|
||||||
private String localImagesPath;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private FileService fileService;
|
private FileService fileService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
public OpenAiService getOpenAiService(User user) {
|
public String getSecretKey() {
|
||||||
String secretKey = SysConfigService.getSecretKey();
|
String secretKey = SysConfigService.getSecretKey();
|
||||||
String userSecretKey = user.getSecretKey();
|
User user = ThreadContext.getCurrentUser();
|
||||||
if (StringUtils.isNotBlank(userSecretKey)) {
|
if (null != user && StringUtils.isNotBlank(user.getSecretKey())) {
|
||||||
secretKey = userSecretKey;
|
secretKey = user.getSecretKey();
|
||||||
}
|
}
|
||||||
|
return secretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenAiService getOpenAiService() {
|
||||||
|
String secretKey = getSecretKey();
|
||||||
if (proxyEnable) {
|
if (proxyEnable) {
|
||||||
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
||||||
OkHttpClient client = defaultClient(secretKey, Duration.of(60, ChronoUnit.SECONDS))
|
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));
|
return new OpenAiService(secretKey, Duration.of(60, ChronoUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public IChatAssistant getChatAssistant(ChatMemory chatMemory) {
|
||||||
* Send http request to openai server <br/>
|
String secretKey = getSecretKey();
|
||||||
* Calculate token
|
OpenAiStreamingChatModel.OpenAiStreamingChatModelBuilder builder = OpenAiStreamingChatModel.builder();
|
||||||
*
|
if (proxyEnable) {
|
||||||
* @param user
|
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyHttpPort));
|
||||||
* @param regenerateQuestionUuid
|
builder.proxy(proxy);
|
||||||
* @param chatMessageList
|
}
|
||||||
* @param sseEmitter
|
builder.apiKey(secretKey).timeout(Duration.of(60, ChronoUnit.SECONDS));
|
||||||
* @param consumer
|
AiServices<IChatAssistant> serviceBuilder = AiServices.builder(IChatAssistant.class)
|
||||||
*/
|
.streamingChatLanguageModel(builder.build());
|
||||||
public void sseAsk(User user, String regenerateQuestionUuid, List<ChatMessage> chatMessageList, SseEmitter sseEmitter, TriConsumer<String, QuestionMeta, AnswerMeta> consumer) {
|
if (null != chatMemory) {
|
||||||
final int[] answerTokens = {0};
|
serviceBuilder.chatMemory(chatMemory);
|
||||||
StringBuilder response = new StringBuilder();
|
}
|
||||||
OpenAiService service = getOpenAiService(user);
|
return serviceBuilder.build();
|
||||||
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
|
|
||||||
.builder()
|
|
||||||
.model(AdiConstant.DEFAULT_MODEL)
|
|
||||||
.messages(chatMessageList)
|
|
||||||
.n(1)
|
|
||||||
.logitBias(new HashMap<>())
|
|
||||||
.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();
|
|
||||||
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;
|
|
||||||
try {
|
|
||||||
questionTokens = TokenCount.fromMessages(chatMessageList, tokenizer, ChatFormatDescriptor.forModel(AdiConstant.DEFAULT_MODEL));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
log.error("该模型的token无法统计,model:{}", AdiConstant.DEFAULT_MODEL);
|
|
||||||
}
|
|
||||||
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("-", ""));
|
|
||||||
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);
|
|
||||||
// close eventSourceEmitter after tokens was calculated
|
|
||||||
sseEmitter.complete();
|
|
||||||
consumer.accept(response.toString(), questionMeta, answerMeta);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//加空格配合前端的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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Image> createImage(User user, AiImage aiImage) {
|
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();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
//加空格配合前端的fetchEventSource进行解析,见https://github.com/Azure/fetch-event-source/blob/45ac3cfffd30b05b79fbf95c21e67d4ef59aa56a/src/parse.ts#L129-L133
|
||||||
|
try {
|
||||||
|
params.getSseEmitter().send(" " + content);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("stream onNext error", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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", "");
|
||||||
|
log.info("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
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
params.getSseEmitter().complete();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> createImage(User user, AiImage aiImage) {
|
||||||
if (aiImage.getGenerateNumber() < 1 || aiImage.getGenerateNumber() > 10) {
|
if (aiImage.getGenerateNumber() < 1 || aiImage.getGenerateNumber() > 10) {
|
||||||
throw new BaseException(ErrorEnum.A_IMAGE_NUMBER_ERROR);
|
throw new BaseException(ErrorEnum.A_IMAGE_NUMBER_ERROR);
|
||||||
}
|
}
|
||||||
if (!OPENAI_CREATE_IMAGE_SIZES.contains(aiImage.getGenerateSize())) {
|
if (!OPENAI_CREATE_IMAGE_SIZES.contains(aiImage.getGenerateSize())) {
|
||||||
throw new BaseException(ErrorEnum.A_IMAGE_SIZE_ERROR);
|
throw new BaseException(ErrorEnum.A_IMAGE_SIZE_ERROR);
|
||||||
}
|
}
|
||||||
OpenAiService service = getOpenAiService(user);
|
ImageModel imageModel = getImageModel(user, aiImage.getGenerateSize());
|
||||||
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());
|
|
||||||
try {
|
try {
|
||||||
ImageResult imageResult = service.createImage(createImageRequest);
|
Response<List<Image>> response = imageModel.generate(aiImage.getPrompt(), aiImage.getGenerateNumber());
|
||||||
log.info("createImage response:{}", imageResult);
|
log.info("createImage response:{}", response);
|
||||||
return imageResult.getData();
|
return response.content().stream().map(item -> item.url().toString()).collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("create image error", e);
|
log.error("create image error", e);
|
||||||
}
|
}
|
||||||
return Collections.emptyList();
|
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 originalFile = new File(fileService.getImagePath(aiImage.getOriginalImage()));
|
||||||
File maskFile = null;
|
File maskFile = null;
|
||||||
if (StringUtils.isNotBlank(aiImage.getMaskImage())) {
|
if (StringUtils.isNotBlank(aiImage.getMaskImage())) {
|
||||||
|
@ -193,7 +222,7 @@ public class OpenAiHelper {
|
||||||
}
|
}
|
||||||
//如果不是RGBA类型的图片,先转成RGBA
|
//如果不是RGBA类型的图片,先转成RGBA
|
||||||
File rgbaOriginalImage = ImageUtil.rgbConvertToRgba(originalFile, fileService.getTmpImagesPath(aiImage.getOriginalImage()));
|
File rgbaOriginalImage = ImageUtil.rgbConvertToRgba(originalFile, fileService.getTmpImagesPath(aiImage.getOriginalImage()));
|
||||||
OpenAiService service = getOpenAiService(user);
|
OpenAiService service = getOpenAiService();
|
||||||
CreateImageEditRequest request = new CreateImageEditRequest();
|
CreateImageEditRequest request = new CreateImageEditRequest();
|
||||||
request.setPrompt(aiImage.getPrompt());
|
request.setPrompt(aiImage.getPrompt());
|
||||||
request.setN(aiImage.getGenerateNumber());
|
request.setN(aiImage.getGenerateNumber());
|
||||||
|
@ -203,16 +232,16 @@ public class OpenAiHelper {
|
||||||
try {
|
try {
|
||||||
ImageResult imageResult = service.createImageEdit(request, rgbaOriginalImage, maskFile);
|
ImageResult imageResult = service.createImageEdit(request, rgbaOriginalImage, maskFile);
|
||||||
log.info("editImage response:{}", imageResult);
|
log.info("editImage response:{}", imageResult);
|
||||||
return imageResult.getData();
|
return imageResult.getData().stream().map(item -> item.getUrl()).collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("edit image error", e);
|
log.error("edit image error", e);
|
||||||
}
|
}
|
||||||
return Collections.emptyList();
|
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()));
|
File imagePath = new File(fileService.getImagePath(aiImage.getOriginalImage()));
|
||||||
OpenAiService service = getOpenAiService(user);
|
OpenAiService service = getOpenAiService();
|
||||||
CreateImageVariationRequest request = new CreateImageVariationRequest();
|
CreateImageVariationRequest request = new CreateImageVariationRequest();
|
||||||
request.setN(aiImage.getGenerateNumber());
|
request.setN(aiImage.getGenerateNumber());
|
||||||
request.setSize(aiImage.getGenerateSize());
|
request.setSize(aiImage.getGenerateSize());
|
||||||
|
@ -221,7 +250,7 @@ public class OpenAiHelper {
|
||||||
try {
|
try {
|
||||||
ImageResult imageResult = service.createImageVariation(request, imagePath);
|
ImageResult imageResult = service.createImageVariation(request, imagePath);
|
||||||
log.info("createImageVariation response:{}", imageResult);
|
log.info("createImageVariation response:{}", imageResult);
|
||||||
return imageResult.getData();
|
return imageResult.getData().stream().map(item -> item.getUrl()).collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("image variation error", 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.entity.User;
|
||||||
import com.moyz.adi.common.enums.ErrorEnum;
|
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 com.moyz.adi.common.service.UserDayCostService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package com.moyz.adi.common.helper;
|
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 jakarta.annotation.Resource;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
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.LocalCache;
|
||||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||||
import com.moyz.adi.common.util.UserUtil;
|
import com.moyz.adi.common.util.UserUtil;
|
||||||
import com.theokanning.openai.image.Image;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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());
|
String requestTimesKey = MessageFormat.format(RedisKeyConstant.USER_REQUEST_TEXT_TIMES, user.getId());
|
||||||
rateLimitHelper.increaseRequestTimes(requestTimesKey, LocalCache.IMAGE_RATE_LIMIT_CONFIG);
|
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) {
|
if (aiImage.getInteractingMethod() == INTERACTING_METHOD_GENERATE_IMAGE) {
|
||||||
images = openAiHelper.createImage(user, aiImage);
|
images = openAiHelper.createImage(user, aiImage);
|
||||||
} else if (aiImage.getInteractingMethod() == INTERACTING_METHOD_EDIT_IMAGE) {
|
} else if (aiImage.getInteractingMethod() == INTERACTING_METHOD_EDIT_IMAGE) {
|
||||||
|
@ -183,8 +182,8 @@ public class AiImageService extends ServiceImpl<AiImageMapper, AiImage> {
|
||||||
images = openAiHelper.createImageVariation(user, aiImage);
|
images = openAiHelper.createImageVariation(user, aiImage);
|
||||||
}
|
}
|
||||||
List<String> imageUuids = new ArrayList();
|
List<String> imageUuids = new ArrayList();
|
||||||
images.forEach(image -> {
|
images.forEach(imageUrl -> {
|
||||||
String imageUuid = fileService.saveToLocal(user, image.getUrl());
|
String imageUuid = fileService.saveToLocal(user, imageUrl);
|
||||||
imageUuids.add(imageUuid);
|
imageUuids.add(imageUuid);
|
||||||
});
|
});
|
||||||
String imageUuidsJoin = imageUuids.stream().collect(Collectors.joining(","));
|
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();
|
_this.lambdaUpdate().eq(AiImage::getId, aiImage.getId()).set(AiImage::getProcessStatus, STATUS_FAIL).update();
|
||||||
return;
|
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);
|
updateAiImageStatus(aiImage.getId(), respImagesPath, imageUuidsJoin, STATUS_SUCCESS);
|
||||||
|
|
||||||
//Update the cost of current user
|
//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) {
|
public AiImagesListResp listAll(@RequestParam Long maxId, @RequestParam int pageSize) {
|
||||||
List<AiImage> list = this.lambdaQuery()
|
List<AiImage> list = this.lambdaQuery()
|
||||||
.eq(AiImage::getUserId, ThreadContext.getCurrentUserId())
|
.eq(AiImage::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(AiImage::getIsDelete, false)
|
.eq(AiImage::getIsDeleted, false)
|
||||||
.lt(AiImage::getId, maxId)
|
.lt(AiImage::getId, maxId)
|
||||||
.orderByDesc(AiImage::getId)
|
.orderByDesc(AiImage::getId)
|
||||||
.last("limit " + pageSize)
|
.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.ConversationMessage;
|
||||||
import com.moyz.adi.common.entity.User;
|
import com.moyz.adi.common.entity.User;
|
||||||
import com.moyz.adi.common.entity.UserDayCost;
|
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.enums.ErrorEnum;
|
||||||
import com.moyz.adi.common.exception.BaseException;
|
import com.moyz.adi.common.exception.BaseException;
|
||||||
import com.moyz.adi.common.helper.OpenAiHelper;
|
import com.moyz.adi.common.helper.OpenAiHelper;
|
||||||
import com.moyz.adi.common.helper.QuotaHelper;
|
import com.moyz.adi.common.helper.QuotaHelper;
|
||||||
import com.moyz.adi.common.helper.RateLimitHelper;
|
import com.moyz.adi.common.helper.RateLimitHelper;
|
||||||
import com.moyz.adi.common.mapper.ConversationMessageMapper;
|
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.LocalCache;
|
||||||
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
import com.moyz.adi.common.util.LocalDateTimeUtil;
|
||||||
import com.moyz.adi.common.util.UserUtil;
|
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 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 jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.io.IOException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static com.moyz.adi.common.enums.ErrorEnum.B_MESSAGE_NOT_FOUND;
|
import static com.moyz.adi.common.enums.ErrorEnum.B_MESSAGE_NOT_FOUND;
|
||||||
|
import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
@ -90,7 +96,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
//check 2: the conversation has been deleted
|
//check 2: the conversation has been deleted
|
||||||
Conversation delConv = conversationService.lambdaQuery()
|
Conversation delConv = conversationService.lambdaQuery()
|
||||||
.eq(Conversation::getUuid, askReq.getConversationUuid())
|
.eq(Conversation::getUuid, askReq.getConversationUuid())
|
||||||
.eq(Conversation::getIsDelete, true)
|
.eq(Conversation::getIsDeleted, true)
|
||||||
.one();
|
.one();
|
||||||
if (null != delConv) {
|
if (null != delConv) {
|
||||||
sendErrorMsg(sseEmitter, "该对话已经删除");
|
sendErrorMsg(sseEmitter, "该对话已经删除");
|
||||||
|
@ -100,7 +106,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
//check 3: conversation quota
|
//check 3: conversation quota
|
||||||
Long convsCount = conversationService.lambdaQuery()
|
Long convsCount = conversationService.lambdaQuery()
|
||||||
.eq(Conversation::getUserId, user.getId())
|
.eq(Conversation::getUserId, user.getId())
|
||||||
.eq(Conversation::getIsDelete, false)
|
.eq(Conversation::getIsDeleted, false)
|
||||||
.count();
|
.count();
|
||||||
long convsMax = Integer.parseInt(LocalCache.CONFIGS.get(AdiConstant.SysConfigKey.CONVERSATION_MAX_NUM));
|
long convsMax = Integer.parseInt(LocalCache.CONFIGS.get(AdiConstant.SysConfigKey.CONVERSATION_MAX_NUM));
|
||||||
if (convsCount >= convsMax) {
|
if (convsCount >= convsMax) {
|
||||||
|
@ -175,12 +181,16 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
SseAskParams sseAskParams = new SseAskParams();
|
||||||
String prompt = askReq.getPrompt();
|
String prompt = askReq.getPrompt();
|
||||||
if (StringUtils.isNotBlank(askReq.getRegenerateQuestionUuid())) {
|
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
|
//questions
|
||||||
final List<ChatMessage> chatMessageList = new ArrayList<>();
|
|
||||||
//system message
|
//system message
|
||||||
Conversation conversation = conversationService.lambdaQuery()
|
Conversation conversation = conversationService.lambdaQuery()
|
||||||
.eq(Conversation::getUuid, askReq.getConversationUuid())
|
.eq(Conversation::getUuid, askReq.getConversationUuid())
|
||||||
|
@ -188,8 +198,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (null != conversation) {
|
if (null != conversation) {
|
||||||
if (StringUtils.isNotBlank(conversation.getAiSystemMessage())) {
|
if (StringUtils.isNotBlank(conversation.getAiSystemMessage())) {
|
||||||
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), conversation.getAiSystemMessage());
|
sseAskParams.setSystemMessage(conversation.getAiSystemMessage());
|
||||||
chatMessageList.add(chatMessage);
|
|
||||||
}
|
}
|
||||||
//history message
|
//history message
|
||||||
if (Boolean.TRUE.equals(conversation.getUnderstandContextEnable()) && user.getUnderstandContextMsgPairNum() > 0) {
|
if (Boolean.TRUE.equals(conversation.getUnderstandContextEnable()) && user.getUnderstandContextMsgPairNum() > 0) {
|
||||||
|
@ -200,19 +209,23 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
.last("limit " + user.getUnderstandContextMsgPairNum() * 2)
|
.last("limit " + user.getUnderstandContextMsgPairNum() * 2)
|
||||||
.list();
|
.list();
|
||||||
if (!historyMsgList.isEmpty()) {
|
if (!historyMsgList.isEmpty()) {
|
||||||
|
ChatMemory chatMemory = TokenWindowChatMemory.withMaxTokens(1000, new OpenAiTokenizer(GPT_3_5_TURBO));
|
||||||
historyMsgList.sort(Comparator.comparing(ConversationMessage::getId));
|
historyMsgList.sort(Comparator.comparing(ConversationMessage::getId));
|
||||||
for (ConversationMessage historyMsg : historyMsgList) {
|
for (ConversationMessage historyMsg : historyMsgList) {
|
||||||
ChatMessage chatMessage = new ChatMessage(historyMsg.getMessageRole(), historyMsg.getContent());
|
if (ChatMessageRole.USER.value().equals(historyMsg.getMessageRole())) {
|
||||||
chatMessageList.add(chatMessage);
|
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
|
openAiHelper.sseAsk(sseAskParams, (response, questionMeta, answerMeta) -> {
|
||||||
ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt);
|
|
||||||
chatMessageList.add(userMessage);
|
|
||||||
openAiHelper.sseAsk(user, askReq.getRegenerateQuestionUuid(), chatMessageList, sseEmitter, (response, questionMeta, answerMeta) -> {
|
|
||||||
try {
|
try {
|
||||||
_this.saveAfterAiResponse(user, askReq, response, questionMeta, answerMeta);
|
_this.saveAfterAiResponse(user, askReq, response, questionMeta, answerMeta);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -228,7 +241,7 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
queryWrapper.eq(ConversationMessage::getConversationId, convId);
|
queryWrapper.eq(ConversationMessage::getConversationId, convId);
|
||||||
queryWrapper.eq(ConversationMessage::getParentMessageId, 0);
|
queryWrapper.eq(ConversationMessage::getParentMessageId, 0);
|
||||||
queryWrapper.lt(ConversationMessage::getId, maxId);
|
queryWrapper.lt(ConversationMessage::getId, maxId);
|
||||||
queryWrapper.eq(ConversationMessage::getIsDelete, false);
|
queryWrapper.eq(ConversationMessage::getIsDeleted, false);
|
||||||
queryWrapper.last("limit " + pageSize);
|
queryWrapper.last("limit " + pageSize);
|
||||||
queryWrapper.orderByDesc(ConversationMessage::getId);
|
queryWrapper.orderByDesc(ConversationMessage::getId);
|
||||||
return getBaseMapper().selectList(queryWrapper);
|
return getBaseMapper().selectList(queryWrapper);
|
||||||
|
@ -257,8 +270,8 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
question.setUuid(questionMeta.getUuid());
|
question.setUuid(questionMeta.getUuid());
|
||||||
question.setConversationId(conversation.getId());
|
question.setConversationId(conversation.getId());
|
||||||
question.setConversationUuid(convUuid);
|
question.setConversationUuid(convUuid);
|
||||||
question.setMessageRole(ChatMessageRole.USER.value());
|
question.setMessageRole(ChatMessageRoleEnum.USER.getValue());
|
||||||
question.setContent(prompt);
|
question.setRemark(prompt);
|
||||||
question.setTokens(questionMeta.getTokens());
|
question.setTokens(questionMeta.getTokens());
|
||||||
question.setSecretKeyType(secretKeyType);
|
question.setSecretKeyType(secretKeyType);
|
||||||
question.setUnderstandContextMsgPairNum(user.getUnderstandContextMsgPairNum());
|
question.setUnderstandContextMsgPairNum(user.getUnderstandContextMsgPairNum());
|
||||||
|
@ -273,8 +286,8 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
aiAnswer.setUuid(answerMeta.getUuid());
|
aiAnswer.setUuid(answerMeta.getUuid());
|
||||||
aiAnswer.setConversationId(conversation.getId());
|
aiAnswer.setConversationId(conversation.getId());
|
||||||
aiAnswer.setConversationUuid(convUuid);
|
aiAnswer.setConversationUuid(convUuid);
|
||||||
aiAnswer.setMessageRole(ChatMessageRole.ASSISTANT.value());
|
aiAnswer.setMessageRole(ChatMessageRoleEnum.ASSISTANT.getValue());
|
||||||
aiAnswer.setContent(response);
|
aiAnswer.setRemark(response);
|
||||||
aiAnswer.setTokens(answerMeta.getTokens());
|
aiAnswer.setTokens(answerMeta.getTokens());
|
||||||
aiAnswer.setParentMessageId(promptMsg.getId());
|
aiAnswer.setParentMessageId(promptMsg.getId());
|
||||||
aiAnswer.setSecretKeyType(secretKeyType);
|
aiAnswer.setSecretKeyType(secretKeyType);
|
||||||
|
@ -321,8 +334,8 @@ public class ConversationMessageService extends ServiceImpl<ConversationMessageM
|
||||||
return this.lambdaUpdate()
|
return this.lambdaUpdate()
|
||||||
.eq(ConversationMessage::getUuid, uuid)
|
.eq(ConversationMessage::getUuid, uuid)
|
||||||
.eq(ConversationMessage::getUserId, ThreadContext.getCurrentUserId())
|
.eq(ConversationMessage::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(ConversationMessage::getIsDelete, false)
|
.eq(ConversationMessage::getIsDeleted, false)
|
||||||
.set(ConversationMessage::getIsDelete, true)
|
.set(ConversationMessage::getIsDeleted, true)
|
||||||
.update();
|
.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
||||||
public List<ConvDto> listByUser() {
|
public List<ConvDto> listByUser() {
|
||||||
List<Conversation> list = this.lambdaQuery()
|
List<Conversation> list = this.lambdaQuery()
|
||||||
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Conversation::getIsDelete, false)
|
.eq(Conversation::getIsDeleted, false)
|
||||||
.orderByDesc(Conversation::getId)
|
.orderByDesc(Conversation::getId)
|
||||||
.last("limit " + sysConfigService.getConversationMaxNum())
|
.last("limit " + sysConfigService.getConversationMaxNum())
|
||||||
.list();
|
.list();
|
||||||
|
@ -61,7 +61,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
||||||
ConversationMessage maxMsg = conversationMessageService.lambdaQuery()
|
ConversationMessage maxMsg = conversationMessageService.lambdaQuery()
|
||||||
.select(ConversationMessage::getId)
|
.select(ConversationMessage::getId)
|
||||||
.eq(ConversationMessage::getUuid, maxMsgUuid)
|
.eq(ConversationMessage::getUuid, maxMsgUuid)
|
||||||
.eq(ConversationMessage::getIsDelete, false)
|
.eq(ConversationMessage::getIsDeleted, false)
|
||||||
.one();
|
.one();
|
||||||
if (null == maxMsg) {
|
if (null == maxMsg) {
|
||||||
throw new RuntimeException("找不到对应的消息");
|
throw new RuntimeException("找不到对应的消息");
|
||||||
|
@ -88,7 +88,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
||||||
List<ConversationMessage> childMessages = conversationMessageService
|
List<ConversationMessage> childMessages = conversationMessageService
|
||||||
.lambdaQuery()
|
.lambdaQuery()
|
||||||
.in(ConversationMessage::getParentMessageId, parentIds)
|
.in(ConversationMessage::getParentMessageId, parentIds)
|
||||||
.eq(ConversationMessage::getIsDelete, false)
|
.eq(ConversationMessage::getIsDeleted, false)
|
||||||
.list();
|
.list();
|
||||||
Map<Long, List<ConversationMessage>> idToMessages = childMessages.stream().collect(Collectors.groupingBy(ConversationMessage::getParentMessageId));
|
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()
|
Conversation conversation = this.lambdaQuery()
|
||||||
.eq(Conversation::getUuid, uuid)
|
.eq(Conversation::getUuid, uuid)
|
||||||
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Conversation::getIsDelete, false)
|
.eq(Conversation::getIsDeleted, false)
|
||||||
.one();
|
.one();
|
||||||
if (null == conversation) {
|
if (null == conversation) {
|
||||||
throw new BaseException(A_CONVERSATION_NOT_EXIST);
|
throw new BaseException(A_CONVERSATION_NOT_EXIST);
|
||||||
|
@ -144,7 +144,7 @@ public class ConversationService extends ServiceImpl<ConversationMapper, Convers
|
||||||
return this.lambdaUpdate()
|
return this.lambdaUpdate()
|
||||||
.eq(Conversation::getUuid, uuid)
|
.eq(Conversation::getUuid, uuid)
|
||||||
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Conversation::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.set(Conversation::getIsDelete, true)
|
.set(Conversation::getIsDeleted, true)
|
||||||
.update();
|
.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,14 +38,14 @@ public class FileService extends ServiceImpl<FileMapper, AdiFile> {
|
||||||
@Value("${local.tmp_images}")
|
@Value("${local.tmp_images}")
|
||||||
private String tmpImagesPath;
|
private String tmpImagesPath;
|
||||||
|
|
||||||
public String writeToLocal(MultipartFile file) {
|
public AdiFile writeToLocal(MultipartFile file) {
|
||||||
String md5 = MD5Utils.md5ByMultipartFile(file);
|
String md5 = MD5Utils.md5ByMultipartFile(file);
|
||||||
Optional<AdiFile> existFile = this.lambdaQuery()
|
Optional<AdiFile> existFile = this.lambdaQuery()
|
||||||
.eq(AdiFile::getMd5, md5)
|
.eq(AdiFile::getMd5, md5)
|
||||||
.eq(AdiFile::getIsDelete, false)
|
.eq(AdiFile::getIsDeleted, false)
|
||||||
.oneOpt();
|
.oneOpt();
|
||||||
if (existFile.isPresent()) {
|
if (existFile.isPresent()) {
|
||||||
return existFile.get().getUuid();
|
return existFile.get();
|
||||||
}
|
}
|
||||||
String uuid = UUID.randomUUID().toString().replace("-", "");
|
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||||
Pair<String, String> originalFile = FileUtil.saveToLocal(file, imagePath, uuid);
|
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.setExt(originalFile.getRight());
|
||||||
adiFile.setUserId(ThreadContext.getCurrentUserId());
|
adiFile.setUserId(ThreadContext.getCurrentUserId());
|
||||||
this.getBaseMapper().insert(adiFile);
|
this.getBaseMapper().insert(adiFile);
|
||||||
return uuid;
|
return adiFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String saveToLocal(User user, String sourceImageUrl) {
|
public String saveToLocal(User user, String sourceImageUrl) {
|
||||||
|
@ -83,7 +83,7 @@ public class FileService extends ServiceImpl<FileMapper, AdiFile> {
|
||||||
return this.lambdaUpdate()
|
return this.lambdaUpdate()
|
||||||
.eq(AdiFile::getUserId, ThreadContext.getCurrentUserId())
|
.eq(AdiFile::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(AdiFile::getUuid, uuid)
|
.eq(AdiFile::getUuid, uuid)
|
||||||
.set(AdiFile::getIsDelete, true)
|
.set(AdiFile::getIsDeleted, true)
|
||||||
.update();
|
.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 class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||||
|
|
||||||
public List<PromptDto> getAll(long userId) {
|
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);
|
return MPPageUtil.convertTo(prompts, PromptDto.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,13 +34,13 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||||
if (StringUtils.isNotBlank(keyword)) {
|
if (StringUtils.isNotBlank(keyword)) {
|
||||||
promptPage = this.lambdaQuery()
|
promptPage = this.lambdaQuery()
|
||||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.like(Prompt::getAct, keyword)
|
.like(Prompt::getAct, keyword)
|
||||||
.page(new Page<>(currentPage, pageSize));
|
.page(new Page<>(currentPage, pageSize));
|
||||||
} else {
|
} else {
|
||||||
promptPage = this.lambdaQuery()
|
promptPage = this.lambdaQuery()
|
||||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.page(new Page<>(currentPage, pageSize));
|
.page(new Page<>(currentPage, pageSize));
|
||||||
}
|
}
|
||||||
return MPPageUtil.convertTo(promptPage, new Page<>(), PromptDto.class);
|
return MPPageUtil.convertTo(promptPage, new Page<>(), PromptDto.class);
|
||||||
|
@ -51,14 +51,14 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||||
if (StringUtils.isNotBlank(keyword)) {
|
if (StringUtils.isNotBlank(keyword)) {
|
||||||
promptPage = this.lambdaQuery()
|
promptPage = this.lambdaQuery()
|
||||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.like(Prompt::getAct, keyword)
|
.like(Prompt::getAct, keyword)
|
||||||
.last("limit 10")
|
.last("limit 10")
|
||||||
.list();
|
.list();
|
||||||
} else {
|
} else {
|
||||||
promptPage = this.lambdaQuery()
|
promptPage = this.lambdaQuery()
|
||||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.last("limit 10")
|
.last("limit 10")
|
||||||
.list();
|
.list();
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||||
Prompt existOne = this.lambdaQuery()
|
Prompt existOne = this.lambdaQuery()
|
||||||
.eq(Prompt::getUserId, userId)
|
.eq(Prompt::getUserId, userId)
|
||||||
.eq(Prompt::getAct, title)
|
.eq(Prompt::getAct, title)
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.one();
|
.one();
|
||||||
if (null != existOne) {
|
if (null != existOne) {
|
||||||
//modify
|
//modify
|
||||||
|
@ -137,14 +137,14 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||||
Prompt prompt = this.lambdaQuery()
|
Prompt prompt = this.lambdaQuery()
|
||||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Prompt::getId, id)
|
.eq(Prompt::getId, id)
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.one();
|
.one();
|
||||||
if (null == prompt) {
|
if (null == prompt) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Prompt updateOne = new Prompt();
|
Prompt updateOne = new Prompt();
|
||||||
updateOne.setId(id);
|
updateOne.setId(id);
|
||||||
updateOne.setIsDelete(true);
|
updateOne.setIsDeleted(true);
|
||||||
return this.updateById(updateOne);
|
return this.updateById(updateOne);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ public class PromptService extends ServiceImpl<PromptMapper, Prompt> {
|
||||||
Prompt prompt = this.lambdaQuery()
|
Prompt prompt = this.lambdaQuery()
|
||||||
.eq(Prompt::getId, id)
|
.eq(Prompt::getId, id)
|
||||||
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
.eq(Prompt::getUserId, ThreadContext.getCurrentUserId())
|
||||||
.eq(Prompt::getIsDelete, false)
|
.eq(Prompt::getIsDeleted, false)
|
||||||
.one();
|
.one();
|
||||||
if (null == prompt) {
|
if (null == prompt) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
package com.moyz.adi.common.service;
|
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.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.entity.SysConfig;
|
||||||
import com.moyz.adi.common.mapper.SysConfigMapper;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -21,7 +24,7 @@ public class SysConfigService extends ServiceImpl<SysConfigMapper, SysConfig> {
|
||||||
@Scheduled(fixedDelay = 20 * 60 * 1000)
|
@Scheduled(fixedDelay = 20 * 60 * 1000)
|
||||||
public void reload() {
|
public void reload() {
|
||||||
log.info("reload system config");
|
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()) {
|
if (LocalCache.CONFIGS.isEmpty()) {
|
||||||
configsFromDB.stream().forEach(item -> LocalCache.CONFIGS.put(item.getName(), item.getValue()));
|
configsFromDB.stream().forEach(item -> LocalCache.CONFIGS.put(item.getName(), item.getValue()));
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,4 +61,24 @@ public class SysConfigService extends ServiceImpl<SysConfigMapper, SysConfig> {
|
||||||
return LocalCache.CONFIGS.get(AdiConstant.SysConfigKey.SECRET_KEY);
|
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.util.LocalDateTimeUtil;
|
||||||
import com.moyz.adi.common.entity.UserDayCost;
|
import com.moyz.adi.common.entity.UserDayCost;
|
||||||
import com.moyz.adi.common.mapper.UserDayCostMapper;
|
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.moyz.adi.common.util.UserUtil;
|
import com.moyz.adi.common.util.UserUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
|
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.base.ThreadContext;
|
||||||
|
import com.moyz.adi.common.cosntant.AdiConstant;
|
||||||
import com.moyz.adi.common.cosntant.RedisKeyConstant;
|
import com.moyz.adi.common.cosntant.RedisKeyConstant;
|
||||||
import com.moyz.adi.common.dto.ConfigResp;
|
import com.moyz.adi.common.dto.ConfigResp;
|
||||||
import com.moyz.adi.common.dto.LoginReq;
|
import com.moyz.adi.common.dto.LoginReq;
|
||||||
import com.moyz.adi.common.dto.LoginResp;
|
import com.moyz.adi.common.dto.LoginResp;
|
||||||
import com.moyz.adi.common.dto.UserUpdateReq;
|
import com.moyz.adi.common.dto.UserUpdateReq;
|
||||||
import com.moyz.adi.common.entity.User;
|
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.helper.AdiMailSender;
|
||||||
import com.moyz.adi.common.mapper.UserMapper;
|
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 jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -71,7 +71,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
||||||
}
|
}
|
||||||
return this.lambdaQuery()
|
return this.lambdaQuery()
|
||||||
.eq(User::getEmail, email)
|
.eq(User::getEmail, email)
|
||||||
.eq(User::getIsDelete, false)
|
.eq(User::getIsDeleted, false)
|
||||||
.oneOpt()
|
.oneOpt()
|
||||||
.orElseThrow(() -> new BaseException(A_USER_NOT_EXIST));
|
.orElseThrow(() -> new BaseException(A_USER_NOT_EXIST));
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
||||||
stringRedisTemplate.delete(captchaInCache);
|
stringRedisTemplate.delete(captchaInCache);
|
||||||
|
|
||||||
User user = ChainWrappers.lambdaQueryChain(baseMapper)
|
User user = ChainWrappers.lambdaQueryChain(baseMapper)
|
||||||
.eq(User::getIsDelete, false)
|
.eq(User::getIsDeleted, false)
|
||||||
.eq(User::getEmail, email)
|
.eq(User::getEmail, email)
|
||||||
.one();
|
.one();
|
||||||
if (null != user && user.getUserStatus() == UserStatusEnum.NORMAL) {
|
if (null != user && user.getUserStatus() == UserStatusEnum.NORMAL) {
|
||||||
|
@ -112,7 +112,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
||||||
|
|
||||||
//创建用户
|
//创建用户
|
||||||
User newOne = new User();
|
User newOne = new User();
|
||||||
newOne.setName(email.substring(0, email.indexOf("@")));
|
newOne.setName(StringUtils.substringBetween(email, "@"));
|
||||||
newOne.setUuid(UUID.randomUUID().toString().replace("-", ""));
|
newOne.setUuid(UUID.randomUUID().toString().replace("-", ""));
|
||||||
newOne.setEmail(email);
|
newOne.setEmail(email);
|
||||||
newOne.setPassword(hashed);
|
newOne.setPassword(hashed);
|
||||||
|
@ -157,7 +157,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
||||||
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
User user = this.lambdaQuery()
|
User user = this.lambdaQuery()
|
||||||
.eq(User::getEmail, email)
|
.eq(User::getEmail, email)
|
||||||
.eq(User::getIsDelete, false)
|
.eq(User::getIsDeleted, false)
|
||||||
.oneOpt()
|
.oneOpt()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
if (null == user) {
|
if (null == user) {
|
||||||
|
@ -201,7 +201,7 @@ public class UserService extends ServiceImpl<UserMapper, User> {
|
||||||
//captcha check end
|
//captcha check end
|
||||||
|
|
||||||
User user = this.lambdaQuery()
|
User user = this.lambdaQuery()
|
||||||
.eq(User::getIsDelete, false)
|
.eq(User::getIsDeleted, false)
|
||||||
.eq(User::getEmail, loginReq.getEmail())
|
.eq(User::getEmail, loginReq.getEmail())
|
||||||
.oneOpt()
|
.oneOpt()
|
||||||
.orElseThrow(() -> new BaseException(ErrorEnum.A_USER_NOT_EXIST));
|
.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;
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
|
@ -9,11 +9,16 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MPPageUtil {
|
public class MPPageUtil {
|
||||||
|
|
||||||
public static <T, U> Page<U> convertTo(Page<T> source, Page<U> target, Class<U> targetRecordClass) {
|
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);
|
BeanUtils.copyProperties(source, target);
|
||||||
List<U> records = new ArrayList<>();
|
List<U> records = new ArrayList<>();
|
||||||
target.setRecords(records);
|
target.setRecords(records);
|
||||||
|
@ -21,6 +26,9 @@ public class MPPageUtil {
|
||||||
for (T t : source.getRecords()) {
|
for (T t : source.getRecords()) {
|
||||||
U u = targetRecordClass.getDeclaredConstructor().newInstance();
|
U u = targetRecordClass.getDeclaredConstructor().newInstance();
|
||||||
BeanUtils.copyProperties(t, u);
|
BeanUtils.copyProperties(t, u);
|
||||||
|
if (null != biFunction) {
|
||||||
|
biFunction.apply(t, u);
|
||||||
|
}
|
||||||
records.add(u);
|
records.add(u);
|
||||||
}
|
}
|
||||||
} catch (NoSuchMethodException e1) {
|
} 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`
|
-- 需要先安装pgvector这个扩展(https://github.com/pgvector/pgvector)
|
||||||
(
|
-- CREATE EXTENSION vector;
|
||||||
`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模型';
|
|
||||||
|
|
||||||
CREATE TABLE `adi_sys_config`
|
SET client_encoding = 'UTF8';
|
||||||
(
|
CREATE SCHEMA public;
|
||||||
`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 ='系统配置表';
|
|
||||||
|
|
||||||
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', '');
|
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}');
|
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}');
|
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');
|
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');
|
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');
|
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');
|
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');
|
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');
|
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');
|
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,
|
id bigserial primary key,
|
||||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '用户id',
|
uuid varchar(32) default ''::character varying not null,
|
||||||
`title` varchar(45) NOT NULL DEFAULT '' COMMENT '对话标题',
|
title varchar(250) default ''::character varying not null,
|
||||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
remark text default ''::character varying not null,
|
||||||
`understand_context_enable` tinyint NOT NULL default '0' COMMENT '是否开启上下文理解',
|
is_public boolean default false not null,
|
||||||
`ai_model` varchar(45) NOT NULL DEFAULT '' COMMENT 'ai model',
|
owner_id bigint default 0 not null,
|
||||||
`ai_system_message` varchar(1024) NOT NULL DEFAULT '' COMMENT 'set the system message to ai, ig: you are a lawyer',
|
owner_name varchar(45) default ''::character varying not null,
|
||||||
`tokens` int NOT NULL DEFAULT '0' COMMENT '消耗token数量',
|
create_time timestamp default CURRENT_TIMESTAMP not null,
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
update_time timestamp default CURRENT_TIMESTAMP not null,
|
||||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
is_deleted boolean default false not null
|
||||||
`is_delete` tinyint NOT NULL DEFAULT '0',
|
);
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE = InnoDB
|
|
||||||
DEFAULT CHARSET = utf8mb4
|
|
||||||
COLLATE = utf8mb4_general_ci COMMENT ='对话表';
|
|
||||||
|
|
||||||
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,
|
id bigserial primary key,
|
||||||
`parent_message_id` bigint NOT NULL DEFAULT '0' COMMENT '父级消息id',
|
uuid varchar(32) default ''::character varying not null,
|
||||||
`conversation_id` bigint NOT NULL DEFAULT '0',
|
kb_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||||
`conversation_uuid` varchar(32) NOT NULL DEFAULT '',
|
kb_uuid varchar(32) default ''::character varying not null,
|
||||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User id',
|
source_file_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||||
`content` text NOT NULL COMMENT '对话的消息',
|
title varchar(250) default ''::character varying not null,
|
||||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
brief varchar(250) default ''::character varying not null,
|
||||||
`message_role` varchar(25) NOT NULL DEFAULT '' COMMENT '产生该消息的角色:1:user,2:system,3:assistant',
|
remark text default ''::character varying not null,
|
||||||
`tokens` int NOT NULL DEFAULT '0' COMMENT '消耗的token数量',
|
is_embedded boolean default false not null,
|
||||||
`secret_key_type` int NOT NULL DEFAULT '1' COMMENT '1:System secret key,2:User secret key',
|
create_time timestamp default CURRENT_TIMESTAMP not null,
|
||||||
`understand_context_msg_pair_num` int NOT NULL DEFAULT '0' COMMENT 'If context understanding enable, context_pair_msg_num > 0',
|
update_time timestamp default CURRENT_TIMESTAMP not null,
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
is_deleted boolean default false not null
|
||||||
`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 ='对话信息表';
|
|
||||||
|
|
||||||
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,
|
id bigserial primary key,
|
||||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'The user who generated the image',
|
uuid varchar(32) default ''::character varying not null,
|
||||||
`uuid` varchar(32) NOT NULL DEFAULT '' COMMENT 'The uuid of the request of generated images',
|
kb_id bigint DEFAULT '0'::bigint NOT NULL,
|
||||||
`prompt` varchar(1024) NOT NULL DEFAULT '' COMMENT 'The prompt for generating images',
|
kb_uuid varchar(32) default ''::character varying not null,
|
||||||
`generate_size` varchar(20) NOT NULL DEFAULT '' COMMENT 'The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024"',
|
question varchar(1000) default ''::character varying not null,
|
||||||
`generate_number` int NOT NULL DEFAULT '1' COMMENT 'The number of images to generate. Must be between 1 and 10. Defaults to 1.',
|
answer text default ''::character varying not null,
|
||||||
`original_image` varchar(32) NOT NULL DEFAULT '' COMMENT 'The original image uuid,interacting_method must be 2/3',
|
source_file_ids varchar(500) default ''::character varying not null,
|
||||||
`mask_image` varchar(32) NOT NULL DEFAULT '' COMMENT 'The mask image uuid,interacting_method must be 2',
|
user_id bigint default '0' NOT NULL,
|
||||||
`resp_images_path` varchar(2048) NOT NULL DEFAULT '' COMMENT 'The url of the generated images which from openai response,separated by commas',
|
create_time timestamp default CURRENT_TIMESTAMP not null,
|
||||||
`generated_images` varchar(512) NOT NULL DEFAULT '' COMMENT 'The uuid of the generated images,separated by commas',
|
update_time timestamp default CURRENT_TIMESTAMP not null,
|
||||||
`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',
|
is_deleted boolean default false not null
|
||||||
`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';
|
|
||||||
|
|
||||||
CREATE TABLE `adi_user`
|
comment on table adi_knowledge_base_qa_record is '知识库-提问记录';
|
||||||
(
|
|
||||||
`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 ='用户表';
|
|
||||||
|
|
||||||
CREATE TABLE `adi_user_day_cost`
|
comment on column adi_knowledge_base_qa_record.kb_id is '所属知识库id';
|
||||||
(
|
|
||||||
`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_uuid is '所属知识库uuid';
|
||||||
|
|
||||||
CREATE TABLE `adi_prompt`
|
comment on column adi_knowledge_base_qa_record.question is '问题';
|
||||||
(
|
|
||||||
`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 ='提示词';
|
|
||||||
|
|
||||||
CREATE TABLE `adi_file`
|
comment on column adi_knowledge_base_qa_record.answer is '答案';
|
||||||
(
|
|
||||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
comment on column adi_knowledge_base_qa_record.source_file_ids is '来源文档id,以逗号隔开';
|
||||||
`name` varchar(32) NOT NULL DEFAULT '',
|
|
||||||
`uuid` varchar(32) NOT NULL DEFAULT '',
|
comment on column adi_knowledge_base_qa_record.user_id is '提问用户id';
|
||||||
`md5` varchar(128) NOT NULL DEFAULT '',
|
|
||||||
`ext` varchar(32) NOT NULL DEFAULT '',
|
comment on column adi_knowledge_base_qa_record.create_time is '创建时间';
|
||||||
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '0:System,other:User',
|
|
||||||
`path` varchar(250) NOT NULL DEFAULT '',
|
comment on column adi_knowledge_base_qa_record.update_time is '更新时间';
|
||||||
`ref_count` int NOT NULL DEFAULT '0' COMMENT 'The number of references to this file',
|
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
comment on column adi_knowledge_base_qa_record.is_deleted is '0:未删除;1:已删除';
|
||||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
||||||
`is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '0:Normal;1:Deleted',
|
create trigger trigger_kb_qa_record_update_time
|
||||||
PRIMARY KEY (`id`),
|
before update
|
||||||
KEY `idx_uuid` (`uuid`)
|
on adi_knowledge_base_qa_record
|
||||||
) ENGINE = InnoDB
|
for each row
|
||||||
DEFAULT CHARSET = utf8mb4
|
execute procedure update_modified_column();
|
||||||
COLLATE = utf8mb4_general_ci COMMENT ='提示词';
|
|
||||||
|
|
36
pom.xml
36
pom.xml
|
@ -25,6 +25,7 @@
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<langchain4j.version>0.25.0</langchain4j.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -68,9 +69,8 @@
|
||||||
<version>31.1-jre</version>
|
<version>31.1-jre</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mysql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>8.0.32</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.theokanning.openai-gpt3-java</groupId>
|
<groupId>com.theokanning.openai-gpt3-java</groupId>
|
||||||
|
@ -130,6 +130,36 @@
|
||||||
<artifactId>velocity-engine-core</artifactId>
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
<version>2.3</version>
|
<version>2.3</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue