mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-13 21:27:19 +08:00
Merge branch 'dev'
This commit is contained in:
@@ -64,6 +64,8 @@ dependencies {
|
||||
implementation("dev.langchain4j:langchain4j-open-ai:1.0.0")
|
||||
implementation("dev.langchain4j:langchain4j-pgvector:1.0.1-beta6")
|
||||
implementation("dev.langchain4j:langchain4j-community-zhipu-ai:1.0.1-beta6")
|
||||
implementation("dev.langchain4j:langchain4j-document-parser-apache-tika:1.1.0-beta7")
|
||||
implementation("dev.langchain4j:langchain4j-document-loader-amazon-s3:1.1.0-beta7")
|
||||
implementation("io.projectreactor:reactor-core:3.7.6")
|
||||
testImplementation("org.testcontainers:junit-jupiter:$testcontainersVersion")
|
||||
testImplementation("org.testcontainers:postgresql:$testcontainersVersion")
|
||||
@@ -168,14 +170,8 @@ jooq {
|
||||
}
|
||||
forcedTypes {
|
||||
forcedType {
|
||||
name = "varchar"
|
||||
includeExpression = ".*"
|
||||
includeTypes = "JSONB?"
|
||||
}
|
||||
forcedType {
|
||||
name = "varchar"
|
||||
includeExpression = ".*"
|
||||
includeTypes = "INET"
|
||||
isJsonConverter = true
|
||||
includeTypes = "(?i:JSON|JSONB)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.zl.mjga;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@EnableAsync
|
||||
@SpringBootApplication(scanBasePackages = {"com.zl.mjga", "org.jooq.generated"})
|
||||
public class ApplicationService {
|
||||
|
||||
|
||||
35
backend/src/main/java/com/zl/mjga/config/JacksonConfig.java
Normal file
35
backend/src/main/java/com/zl/mjga/config/JacksonConfig.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.zl.mjga.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import java.io.IOException;
|
||||
import org.jooq.JSON;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
|
||||
return builder ->
|
||||
builder
|
||||
.serializationInclusion(JsonInclude.Include.USE_DEFAULTS)
|
||||
.serializers(new JooqJsonSerializer());
|
||||
}
|
||||
|
||||
private static class JooqJsonSerializer extends StdSerializer<JSON> {
|
||||
public JooqJsonSerializer() {
|
||||
super(JSON.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(JSON value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
gen.writeRawValue(value.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package com.zl.mjga.config.ai;
|
||||
|
||||
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
|
||||
|
||||
import com.zl.mjga.component.PromptConfiguration;
|
||||
import com.zl.mjga.service.LlmService;
|
||||
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
|
||||
import dev.langchain4j.service.AiServices;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jooq.generated.mjga.enums.LlmCodeEnum;
|
||||
import org.jooq.generated.mjga.tables.pojos.AiLlmConfig;
|
||||
@@ -54,11 +60,26 @@ public class ChatModelInitializer {
|
||||
|
||||
@Bean
|
||||
@DependsOn("flywayInitializer")
|
||||
public AiChatAssistant zhiPuChatAssistant(ZhipuAiStreamingChatModel zhipuChatModel) {
|
||||
public AiChatAssistant zhiPuChatAssistant(
|
||||
ZhipuAiStreamingChatModel zhipuChatModel,
|
||||
EmbeddingStore<TextSegment> zhiPuLibraryEmbeddingStore,
|
||||
EmbeddingModel zhipuEmbeddingModel) {
|
||||
return AiServices.builder(AiChatAssistant.class)
|
||||
.streamingChatModel(zhipuChatModel)
|
||||
.systemMessageProvider(chatMemoryId -> promptConfiguration.getSystem())
|
||||
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))
|
||||
.contentRetriever(
|
||||
EmbeddingStoreContentRetriever.builder()
|
||||
.embeddingStore(zhiPuLibraryEmbeddingStore)
|
||||
.embeddingModel(zhipuEmbeddingModel)
|
||||
.minScore(0.75)
|
||||
.maxResults(5)
|
||||
.dynamicFilter(
|
||||
query -> {
|
||||
String libraryId = (String) query.metadata().chatMemoryId();
|
||||
return metadataKey("libraryId").isEqualTo(libraryId);
|
||||
})
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.zl.mjga.config.ai;
|
||||
|
||||
import com.zl.mjga.config.minio.MinIoConfig;
|
||||
import com.zl.mjga.service.LlmService;
|
||||
import dev.langchain4j.community.model.zhipu.ZhipuAiEmbeddingModel;
|
||||
import dev.langchain4j.data.document.loader.amazon.s3.AmazonS3DocumentLoader;
|
||||
import dev.langchain4j.data.document.loader.amazon.s3.AwsCredentials;
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||
@@ -42,7 +45,7 @@ public class EmbeddingInitializer {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EmbeddingStore<TextSegment> zhiPuEmbeddingStore(EmbeddingModel zhipuEmbeddingModel) {
|
||||
public EmbeddingStore<TextSegment> zhiPuEmbeddingStore() {
|
||||
String hostPort = env.getProperty("DATABASE_HOST_PORT");
|
||||
String host = hostPort.split(":")[0];
|
||||
return PgVectorEmbeddingStore.builder()
|
||||
@@ -55,4 +58,28 @@ public class EmbeddingInitializer {
|
||||
.dimension(2048)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EmbeddingStore<TextSegment> zhiPuLibraryEmbeddingStore() {
|
||||
String hostPort = env.getProperty("DATABASE_HOST_PORT");
|
||||
String host = hostPort.split(":")[0];
|
||||
return PgVectorEmbeddingStore.builder()
|
||||
.host(host)
|
||||
.port(env.getProperty("DATABASE_EXPOSE_PORT", Integer.class))
|
||||
.database(env.getProperty("DATABASE_DB"))
|
||||
.user(env.getProperty("DATABASE_USER"))
|
||||
.password(env.getProperty("DATABASE_PASSWORD"))
|
||||
.table("mjga.zhipu_library_embedding_store")
|
||||
.dimension(2048)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AmazonS3DocumentLoader amazonS3DocumentLoader(MinIoConfig minIoConfig) {
|
||||
return AmazonS3DocumentLoader.builder()
|
||||
.endpointUrl(minIoConfig.getEndpoint())
|
||||
.forcePathStyle(true)
|
||||
.awsCredentials(new AwsCredentials(minIoConfig.getAccessKey(), minIoConfig.getSecretKey()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public class WebSecurityConfig {
|
||||
new AntPathRequestMatcher("/auth/sign-up", HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher("/v3/api-docs/**", HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher("/swagger-ui/**", HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher("/ai/library/upload", HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher("/swagger-ui.html", HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher("/error"));
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package com.zl.mjga.controller;
|
||||
|
||||
import com.zl.mjga.dto.PageRequestDto;
|
||||
import com.zl.mjga.dto.PageResponseDto;
|
||||
import com.zl.mjga.dto.ai.ChatDto;
|
||||
import com.zl.mjga.dto.ai.LlmQueryDto;
|
||||
import com.zl.mjga.dto.ai.LlmVm;
|
||||
import com.zl.mjga.exception.BusinessException;
|
||||
import com.zl.mjga.repository.*;
|
||||
import com.zl.mjga.service.AiChatService;
|
||||
import com.zl.mjga.service.EmbeddingService;
|
||||
import com.zl.mjga.service.LlmService;
|
||||
import com.zl.mjga.service.RagService;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import jakarta.validation.Valid;
|
||||
import java.security.Principal;
|
||||
@@ -35,7 +36,7 @@ public class AiController {
|
||||
|
||||
private final AiChatService aiChatService;
|
||||
private final LlmService llmService;
|
||||
private final EmbeddingService embeddingService;
|
||||
private final RagService ragService;
|
||||
private final UserRepository userRepository;
|
||||
private final DepartmentRepository departmentRepository;
|
||||
private final PositionRepository positionRepository;
|
||||
@@ -72,9 +73,9 @@ public class AiController {
|
||||
}
|
||||
|
||||
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> chat(Principal principal, @RequestBody String userMessage) {
|
||||
public Flux<String> chat(Principal principal, @RequestBody ChatDto chatDto) {
|
||||
Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
TokenStream chat = aiChatService.chatPrecedenceLlmWith(principal.getName(), userMessage);
|
||||
TokenStream chat = aiChatService.chat(principal.getName(), chatDto);
|
||||
chat.onPartialResponse(
|
||||
text ->
|
||||
sink.tryEmitNext(
|
||||
@@ -109,7 +110,7 @@ public class AiController {
|
||||
if (!aiLlmConfig.getEnable()) {
|
||||
throw new BusinessException("命令模型未启用,请开启后再试。");
|
||||
}
|
||||
return embeddingService.searchAction(message);
|
||||
return ragService.searchAction(message);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.zl.mjga.controller;
|
||||
|
||||
import com.zl.mjga.config.minio.MinIoConfig;
|
||||
import com.zl.mjga.dto.PageRequestDto;
|
||||
import com.zl.mjga.dto.PageResponseDto;
|
||||
import com.zl.mjga.dto.department.DepartmentBindDto;
|
||||
@@ -13,17 +12,11 @@ import com.zl.mjga.repository.PermissionRepository;
|
||||
import com.zl.mjga.repository.RoleRepository;
|
||||
import com.zl.mjga.repository.UserRepository;
|
||||
import com.zl.mjga.service.IdentityAccessService;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import com.zl.mjga.service.UploadService;
|
||||
import jakarta.validation.Valid;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jooq.generated.mjga.tables.pojos.User;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -41,8 +34,7 @@ public class IdentityAccessController {
|
||||
private final UserRepository userRepository;
|
||||
private final RoleRepository roleRepository;
|
||||
private final PermissionRepository permissionRepository;
|
||||
private final MinioClient minioClient;
|
||||
private final MinIoConfig minIoConfig;
|
||||
private final UploadService uploadService;
|
||||
|
||||
@PreAuthorize("hasAuthority(T(com.zl.mjga.model.urp.EPermission).WRITE_USER_ROLE_PERMISSION)")
|
||||
@PostMapping(
|
||||
@@ -50,40 +42,7 @@ public class IdentityAccessController {
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public String uploadAvatar(@RequestPart("file") MultipartFile multipartFile) throws Exception {
|
||||
String originalFilename = multipartFile.getOriginalFilename();
|
||||
if (StringUtils.isEmpty(originalFilename)) {
|
||||
throw new BusinessException("文件名不能为空");
|
||||
}
|
||||
String contentType = multipartFile.getContentType();
|
||||
String extension = "";
|
||||
if ("image/jpeg".equals(contentType)) {
|
||||
extension = ".jpg";
|
||||
} else if ("image/png".equals(contentType)) {
|
||||
extension = ".png";
|
||||
}
|
||||
String objectName =
|
||||
String.format(
|
||||
"/avatar/%d%s%s",
|
||||
Instant.now().toEpochMilli(),
|
||||
RandomStringUtils.insecure().nextAlphabetic(6),
|
||||
extension);
|
||||
if (multipartFile.isEmpty()) {
|
||||
throw new BusinessException("上传的文件不能为空");
|
||||
}
|
||||
long size = multipartFile.getSize();
|
||||
if (size > 200 * 1024) {
|
||||
throw new BusinessException("头像文件大小不能超过200KB");
|
||||
}
|
||||
BufferedImage img = ImageIO.read(multipartFile.getInputStream());
|
||||
if (img == null) {
|
||||
throw new BusinessException("非法的上传文件");
|
||||
}
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder().bucket(minIoConfig.getDefaultBucket()).object(objectName).stream(
|
||||
multipartFile.getInputStream(), size, -1)
|
||||
.contentType(multipartFile.getContentType())
|
||||
.build());
|
||||
return objectName;
|
||||
return uploadService.uploadAvatarFile(multipartFile);
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.zl.mjga.controller;
|
||||
|
||||
import com.zl.mjga.dto.knowledge.DocUpdateDto;
|
||||
import com.zl.mjga.dto.knowledge.LibraryUpsertDto;
|
||||
import com.zl.mjga.repository.LibraryDocRepository;
|
||||
import com.zl.mjga.repository.LibraryDocSegmentRepository;
|
||||
import com.zl.mjga.repository.LibraryRepository;
|
||||
import com.zl.mjga.service.RagService;
|
||||
import com.zl.mjga.service.UploadService;
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jooq.generated.mjga.tables.pojos.Library;
|
||||
import org.jooq.generated.mjga.tables.pojos.LibraryDoc;
|
||||
import org.jooq.generated.mjga.tables.pojos.LibraryDocSegment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/knowledge")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class LibraryController {
|
||||
|
||||
private final UploadService uploadService;
|
||||
private final RagService ragService;
|
||||
private final LibraryRepository libraryRepository;
|
||||
private final LibraryDocRepository libraryDocRepository;
|
||||
private final LibraryDocSegmentRepository libraryDocSegmentRepository;
|
||||
|
||||
@GetMapping("/libraries")
|
||||
public List<Library> queryLibraries() {
|
||||
return libraryRepository.findAll().stream()
|
||||
.sorted(Comparator.comparing(Library::getId).reversed())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@GetMapping("/docs")
|
||||
public List<LibraryDoc> queryLibraryDocs(@RequestParam Long libraryId) {
|
||||
return libraryDocRepository.fetchByLibId(libraryId).stream()
|
||||
.sorted(Comparator.comparing(LibraryDoc::getId).reversed())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@GetMapping("/segments")
|
||||
public List<LibraryDocSegment> queryLibraryDocSegments(@RequestParam Long libraryDocId) {
|
||||
return libraryDocSegmentRepository.fetchByDocId(libraryDocId);
|
||||
}
|
||||
|
||||
@PostMapping("/library")
|
||||
public void upsertLibrary(@RequestBody @Valid LibraryUpsertDto libraryUpsertDto) {
|
||||
Library library = new Library();
|
||||
library.setId(libraryUpsertDto.id());
|
||||
library.setName(libraryUpsertDto.name());
|
||||
library.setDescription(libraryUpsertDto.description());
|
||||
libraryRepository.merge(library);
|
||||
}
|
||||
|
||||
@DeleteMapping("/library")
|
||||
public void deleteLibrary(@RequestParam Long libraryId) {
|
||||
ragService.deleteLibraryBy(libraryId);
|
||||
}
|
||||
|
||||
@DeleteMapping("/doc")
|
||||
public void deleteLibraryDoc(@RequestParam Long libraryDocId) {
|
||||
ragService.deleteDocBy(libraryDocId);
|
||||
}
|
||||
|
||||
@PutMapping("/doc")
|
||||
public void updateLibraryDoc(@RequestBody @Valid DocUpdateDto docUpdateDto) {
|
||||
LibraryDoc exist = libraryDocRepository.fetchOneById(docUpdateDto.id());
|
||||
exist.setEnable(docUpdateDto.enable());
|
||||
libraryDocRepository.merge(exist);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/doc/upload", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public String uploadLibraryDoc(
|
||||
@RequestPart("libraryId") String libraryId, @RequestPart("file") MultipartFile multipartFile)
|
||||
throws Exception {
|
||||
String objectName = uploadService.uploadLibraryDoc(multipartFile);
|
||||
Long libraryDocId =
|
||||
ragService.createLibraryDocBy(
|
||||
Long.valueOf(libraryId), objectName, multipartFile.getOriginalFilename());
|
||||
ragService.embeddingAndCreateDocSegment(Long.valueOf(libraryId), libraryDocId, objectName);
|
||||
return objectName;
|
||||
}
|
||||
}
|
||||
7
backend/src/main/java/com/zl/mjga/dto/ai/ChatDto.java
Normal file
7
backend/src/main/java/com/zl/mjga/dto/ai/ChatDto.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.zl.mjga.dto.ai;
|
||||
|
||||
import com.zl.mjga.model.urp.ChatMode;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record ChatDto(@NotNull ChatMode mode, Long libraryId, @NotEmpty String message) {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.zl.mjga.dto.knowledge;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record DocUpdateDto(@NotNull Long id, @NotNull Long libId, @NotNull Boolean enable) {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.zl.mjga.dto.knowledge;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
public record LibraryUpsertDto(Long id, @NotEmpty String name, String description) {}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.zl.mjga.model.urp;
|
||||
|
||||
public enum ChatMode {
|
||||
NORMAL,
|
||||
WITH_LIBRARY
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.zl.mjga.repository;
|
||||
|
||||
import org.jooq.Configuration;
|
||||
import org.jooq.generated.mjga.tables.daos.LibraryDocDao;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class LibraryDocRepository extends LibraryDocDao {
|
||||
@Autowired
|
||||
public LibraryDocRepository(Configuration configuration) {
|
||||
super(configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.zl.mjga.repository;
|
||||
|
||||
import org.jooq.Configuration;
|
||||
import org.jooq.generated.mjga.tables.daos.LibraryDocSegmentDao;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class LibraryDocSegmentRepository extends LibraryDocSegmentDao {
|
||||
@Autowired
|
||||
public LibraryDocSegmentRepository(Configuration configuration) {
|
||||
super(configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.zl.mjga.repository;
|
||||
|
||||
import org.jooq.Configuration;
|
||||
import org.jooq.generated.mjga.tables.daos.LibraryDao;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class LibraryRepository extends LibraryDao {
|
||||
|
||||
@Autowired
|
||||
public LibraryRepository(Configuration configuration) {
|
||||
super(configuration);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.zl.mjga.service;
|
||||
|
||||
import com.zl.mjga.config.ai.AiChatAssistant;
|
||||
import com.zl.mjga.config.ai.SystemToolAssistant;
|
||||
import com.zl.mjga.dto.ai.ChatDto;
|
||||
import com.zl.mjga.exception.BusinessException;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import java.util.Optional;
|
||||
@@ -39,8 +40,20 @@ public class AiChatService {
|
||||
};
|
||||
}
|
||||
|
||||
public TokenStream chatPrecedenceLlmWith(String sessionIdentifier, String userMessage) {
|
||||
public TokenStream chat(String sessionIdentifier, ChatDto chatDto) {
|
||||
return switch (chatDto.mode()) {
|
||||
case NORMAL -> chatWithPrecedenceLlm(sessionIdentifier, chatDto);
|
||||
case WITH_LIBRARY -> chatWithLibrary(chatDto.libraryId(), chatDto);
|
||||
};
|
||||
}
|
||||
|
||||
public TokenStream chatWithLibrary(Long libraryId, ChatDto chatDto) {
|
||||
return zhiPuChatAssistant.chat(String.valueOf(libraryId), chatDto.message());
|
||||
}
|
||||
|
||||
public TokenStream chatWithPrecedenceLlm(String sessionIdentifier, ChatDto chatDto) {
|
||||
LlmCodeEnum code = getPrecedenceLlmCode();
|
||||
String userMessage = chatDto.message();
|
||||
return switch (code) {
|
||||
case ZHI_PU -> zhiPuChatAssistant.chat(sessionIdentifier, userMessage);
|
||||
case DEEP_SEEK -> deepSeekChatAssistant.chat(sessionIdentifier, userMessage);
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.zl.mjga.service;
|
||||
|
||||
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
|
||||
|
||||
import com.zl.mjga.config.ai.ZhiPuEmbeddingModelConfig;
|
||||
import com.zl.mjga.model.urp.Actions;
|
||||
import dev.langchain4j.data.document.Metadata;
|
||||
import dev.langchain4j.data.embedding.Embedding;
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
|
||||
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
|
||||
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||
import dev.langchain4j.store.embedding.filter.Filter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class EmbeddingService {
|
||||
|
||||
private final EmbeddingModel zhipuEmbeddingModel;
|
||||
|
||||
private final EmbeddingStore<TextSegment> zhiPuEmbeddingStore;
|
||||
|
||||
private final ZhiPuEmbeddingModelConfig zhiPuEmbeddingModelConfig;
|
||||
|
||||
public Map<String, String> searchAction(String message) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
EmbeddingSearchRequest embeddingSearchRequest =
|
||||
EmbeddingSearchRequest.builder()
|
||||
.queryEmbedding(zhipuEmbeddingModel.embed(message).content())
|
||||
.minScore(0.89)
|
||||
.build();
|
||||
EmbeddingSearchResult<TextSegment> embeddingSearchResult =
|
||||
zhiPuEmbeddingStore.search(embeddingSearchRequest);
|
||||
if (!embeddingSearchResult.matches().isEmpty()) {
|
||||
Metadata metadata = embeddingSearchResult.matches().getFirst().embedded().metadata();
|
||||
result.put(Actions.INDEX_KEY, metadata.getString(Actions.INDEX_KEY));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initActionIndex() {
|
||||
if (!zhiPuEmbeddingModelConfig.getEnable()) {
|
||||
return;
|
||||
}
|
||||
for (Actions action : Actions.values()) {
|
||||
Embedding queryEmbedding = zhipuEmbeddingModel.embed(action.getContent()).content();
|
||||
Filter createUserFilter = metadataKey(Actions.INDEX_KEY).isEqualTo(action.getCode());
|
||||
EmbeddingSearchRequest embeddingSearchRequest =
|
||||
EmbeddingSearchRequest.builder()
|
||||
.queryEmbedding(queryEmbedding)
|
||||
.filter(createUserFilter)
|
||||
.build();
|
||||
EmbeddingSearchResult<TextSegment> embeddingSearchResult =
|
||||
zhiPuEmbeddingStore.search(embeddingSearchRequest);
|
||||
if (embeddingSearchResult.matches().isEmpty()) {
|
||||
TextSegment segment =
|
||||
TextSegment.from(
|
||||
action.getContent(), Metadata.metadata(Actions.INDEX_KEY, action.getCode()));
|
||||
Embedding embedding = zhipuEmbeddingModel.embed(segment).content();
|
||||
zhiPuEmbeddingStore.add(embedding, segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
181
backend/src/main/java/com/zl/mjga/service/RagService.java
Normal file
181
backend/src/main/java/com/zl/mjga/service/RagService.java
Normal file
@@ -0,0 +1,181 @@
|
||||
package com.zl.mjga.service;
|
||||
|
||||
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.zl.mjga.config.ai.ZhiPuEmbeddingModelConfig;
|
||||
import com.zl.mjga.config.minio.MinIoConfig;
|
||||
import com.zl.mjga.model.urp.Actions;
|
||||
import com.zl.mjga.repository.LibraryDocRepository;
|
||||
import com.zl.mjga.repository.LibraryRepository;
|
||||
import dev.langchain4j.data.document.Document;
|
||||
import dev.langchain4j.data.document.Metadata;
|
||||
import dev.langchain4j.data.document.loader.amazon.s3.AmazonS3DocumentLoader;
|
||||
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
|
||||
import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
|
||||
import dev.langchain4j.data.embedding.Embedding;
|
||||
import dev.langchain4j.data.segment.TextSegment;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.model.output.Response;
|
||||
import dev.langchain4j.store.embedding.*;
|
||||
import dev.langchain4j.store.embedding.filter.Filter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.jooq.JSON;
|
||||
import org.jooq.generated.mjga.enums.LibraryDocStatusEnum;
|
||||
import org.jooq.generated.mjga.tables.daos.LibraryDocSegmentDao;
|
||||
import org.jooq.generated.mjga.tables.pojos.LibraryDoc;
|
||||
import org.jooq.generated.mjga.tables.pojos.LibraryDocSegment;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@Slf4j
|
||||
public class RagService {
|
||||
|
||||
private final EmbeddingModel zhipuEmbeddingModel;
|
||||
|
||||
private final EmbeddingStore<TextSegment> zhiPuEmbeddingStore;
|
||||
|
||||
private final EmbeddingStore<TextSegment> zhiPuLibraryEmbeddingStore;
|
||||
|
||||
private final ZhiPuEmbeddingModelConfig zhiPuEmbeddingModelConfig;
|
||||
|
||||
private final AmazonS3DocumentLoader amazonS3DocumentLoader;
|
||||
|
||||
private final MinIoConfig minIoConfig;
|
||||
|
||||
private final LibraryRepository libraryRepository;
|
||||
|
||||
private final LibraryDocRepository libraryDocRepository;
|
||||
|
||||
private final LibraryDocSegmentDao libraryDocSegmentDao;
|
||||
|
||||
public void deleteLibraryBy(Long libraryId) {
|
||||
List<LibraryDoc> libraryDocs = libraryDocRepository.fetchByLibId(libraryId);
|
||||
List<Long> docIds = libraryDocs.stream().map(LibraryDoc::getId).toList();
|
||||
for (Long docId : docIds) {
|
||||
deleteDocBy(docId);
|
||||
}
|
||||
libraryRepository.deleteById(libraryId);
|
||||
}
|
||||
|
||||
public void deleteDocBy(Long docId) {
|
||||
List<LibraryDocSegment> libraryDocSegments = libraryDocSegmentDao.fetchByDocId(docId);
|
||||
List<String> embeddingIdList =
|
||||
libraryDocSegments.stream().map(LibraryDocSegment::getEmbeddingId).toList();
|
||||
if (CollectionUtils.isNotEmpty(embeddingIdList)) {
|
||||
zhiPuLibraryEmbeddingStore.removeAll(embeddingIdList);
|
||||
}
|
||||
libraryDocRepository.deleteById(docId);
|
||||
}
|
||||
|
||||
public Long createLibraryDocBy(Long libraryId, String objectName, String originalName)
|
||||
throws JsonProcessingException {
|
||||
String username = SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
String identify =
|
||||
String.format(
|
||||
"%d%s_%s",
|
||||
Instant.now().toEpochMilli(),
|
||||
RandomStringUtils.insecure().nextAlphabetic(6),
|
||||
originalName);
|
||||
Map<String, String> meta = new HashMap<>();
|
||||
meta.put("uploader", username);
|
||||
LibraryDoc libraryDoc = new LibraryDoc();
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String metaJson = objectMapper.writeValueAsString(meta);
|
||||
libraryDoc.setMeta(JSON.valueOf(metaJson));
|
||||
libraryDoc.setPath(objectName);
|
||||
libraryDoc.setName(originalName);
|
||||
libraryDoc.setIdentify(identify);
|
||||
libraryDoc.setLibId(libraryId);
|
||||
libraryDoc.setStatus(LibraryDocStatusEnum.INDEXING);
|
||||
libraryDoc.setEnable(Boolean.TRUE);
|
||||
libraryDocRepository.insert(libraryDoc);
|
||||
return libraryDocRepository.fetchOneByIdentify(identify).getId();
|
||||
}
|
||||
|
||||
@Async
|
||||
public void embeddingAndCreateDocSegment(Long libraryId, Long libraryDocId, String objectName) {
|
||||
Document document =
|
||||
amazonS3DocumentLoader.loadDocument(
|
||||
minIoConfig.getDefaultBucket(), objectName, new ApacheTikaDocumentParser());
|
||||
List<LibraryDocSegment> libraryDocSegments = new ArrayList<>();
|
||||
DocumentByParagraphSplitter documentByParagraphSplitter =
|
||||
new DocumentByParagraphSplitter(500, 150);
|
||||
documentByParagraphSplitter
|
||||
.split(document)
|
||||
.forEach(
|
||||
textSegment -> {
|
||||
Response<Embedding> embed = zhipuEmbeddingModel.embed(textSegment);
|
||||
Integer tokenUsage = embed.tokenUsage().totalTokenCount();
|
||||
Embedding vector = embed.content();
|
||||
textSegment.metadata().put("libraryId", libraryId);
|
||||
String embeddingId = zhiPuLibraryEmbeddingStore.add(vector, textSegment);
|
||||
LibraryDocSegment libraryDocSegment = new LibraryDocSegment();
|
||||
libraryDocSegment.setEmbeddingId(embeddingId);
|
||||
libraryDocSegment.setContent(textSegment.text());
|
||||
libraryDocSegment.setTokenUsage(tokenUsage);
|
||||
libraryDocSegment.setDocId(libraryDocId);
|
||||
libraryDocSegments.add(libraryDocSegment);
|
||||
});
|
||||
libraryDocSegmentDao.insert(libraryDocSegments);
|
||||
LibraryDoc libraryDoc = libraryDocRepository.fetchOneById(libraryDocId);
|
||||
libraryDoc.setStatus(LibraryDocStatusEnum.SUCCESS);
|
||||
libraryDocRepository.update(libraryDoc);
|
||||
}
|
||||
|
||||
public Map<String, String> searchAction(String message) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
EmbeddingSearchRequest embeddingSearchRequest =
|
||||
EmbeddingSearchRequest.builder()
|
||||
.queryEmbedding(zhipuEmbeddingModel.embed(message).content())
|
||||
.minScore(0.89)
|
||||
.build();
|
||||
EmbeddingSearchResult<TextSegment> embeddingSearchResult =
|
||||
zhiPuEmbeddingStore.search(embeddingSearchRequest);
|
||||
if (!embeddingSearchResult.matches().isEmpty()) {
|
||||
Metadata metadata = embeddingSearchResult.matches().getFirst().embedded().metadata();
|
||||
result.put(Actions.INDEX_KEY, metadata.getString(Actions.INDEX_KEY));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void initActionIndex() {
|
||||
if (!zhiPuEmbeddingModelConfig.getEnable()) {
|
||||
return;
|
||||
}
|
||||
for (Actions action : Actions.values()) {
|
||||
Embedding queryEmbedding = zhipuEmbeddingModel.embed(action.getContent()).content();
|
||||
Filter createUserFilter = metadataKey(Actions.INDEX_KEY).isEqualTo(action.getCode());
|
||||
EmbeddingSearchRequest embeddingSearchRequest =
|
||||
EmbeddingSearchRequest.builder()
|
||||
.queryEmbedding(queryEmbedding)
|
||||
.filter(createUserFilter)
|
||||
.build();
|
||||
EmbeddingSearchResult<TextSegment> embeddingSearchResult =
|
||||
zhiPuEmbeddingStore.search(embeddingSearchRequest);
|
||||
if (embeddingSearchResult.matches().isEmpty()) {
|
||||
TextSegment segment =
|
||||
TextSegment.from(
|
||||
action.getContent(), Metadata.metadata(Actions.INDEX_KEY, action.getCode()));
|
||||
Embedding embedding = zhipuEmbeddingModel.embed(segment).content();
|
||||
zhiPuEmbeddingStore.add(embedding, segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
backend/src/main/java/com/zl/mjga/service/UploadService.java
Normal file
81
backend/src/main/java/com/zl/mjga/service/UploadService.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package com.zl.mjga.service;
|
||||
|
||||
import com.zl.mjga.config.minio.MinIoConfig;
|
||||
import com.zl.mjga.exception.BusinessException;
|
||||
import io.minio.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.time.Instant;
|
||||
import javax.imageio.ImageIO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class UploadService {
|
||||
|
||||
private final MinioClient minioClient;
|
||||
private final MinIoConfig minIoConfig;
|
||||
|
||||
public String uploadAvatarFile(MultipartFile multipartFile) throws Exception {
|
||||
String originalFilename = multipartFile.getOriginalFilename();
|
||||
if (StringUtils.isEmpty(originalFilename)) {
|
||||
throw new BusinessException("文件名不能为空");
|
||||
}
|
||||
String contentType = multipartFile.getContentType();
|
||||
String extension = "";
|
||||
if ("image/jpeg".equals(contentType)) {
|
||||
extension = ".jpg";
|
||||
} else if ("image/png".equals(contentType)) {
|
||||
extension = ".png";
|
||||
}
|
||||
String objectName =
|
||||
String.format(
|
||||
"/library/%d%s%s",
|
||||
Instant.now().toEpochMilli(),
|
||||
RandomStringUtils.insecure().nextAlphabetic(6),
|
||||
extension);
|
||||
if (multipartFile.isEmpty()) {
|
||||
throw new BusinessException("上传的文件不能为空");
|
||||
}
|
||||
long size = multipartFile.getSize();
|
||||
if (size > 200 * 1024) {
|
||||
throw new BusinessException("头像大小不能超过200KB");
|
||||
}
|
||||
BufferedImage img = ImageIO.read(multipartFile.getInputStream());
|
||||
if (img == null) {
|
||||
throw new BusinessException("非法的上传文件");
|
||||
}
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder().bucket(minIoConfig.getDefaultBucket()).object(objectName).stream(
|
||||
multipartFile.getInputStream(), size, -1)
|
||||
.contentType(multipartFile.getContentType())
|
||||
.build());
|
||||
return objectName;
|
||||
}
|
||||
|
||||
public String uploadLibraryDoc(MultipartFile multipartFile) throws Exception {
|
||||
String originalFilename = multipartFile.getOriginalFilename();
|
||||
if (StringUtils.isEmpty(originalFilename)) {
|
||||
throw new BusinessException("文件名不能为空");
|
||||
}
|
||||
String objectName = String.format("/library/%s", originalFilename);
|
||||
if (multipartFile.isEmpty()) {
|
||||
throw new BusinessException("上传的文件不能为空");
|
||||
}
|
||||
long size = multipartFile.getSize();
|
||||
if (size > 1024 * 1024) {
|
||||
throw new BusinessException("知识库文档大小不能超过1MB");
|
||||
}
|
||||
minioClient.putObject(
|
||||
PutObjectArgs.builder().bucket(minIoConfig.getDefaultBucket()).object(objectName).stream(
|
||||
multipartFile.getInputStream(), size, -1)
|
||||
.contentType(multipartFile.getContentType())
|
||||
.build());
|
||||
return objectName;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ CREATE TABLE mjga.user (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR NOT NULL UNIQUE,
|
||||
avatar VARCHAR,
|
||||
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
password VARCHAR NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
@@ -39,7 +39,7 @@ CREATE TABLE mjga.user_role_map (
|
||||
|
||||
CREATE TABLE mjga.department (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR NOT NULL UNIQUE,
|
||||
parent_id BIGINT,
|
||||
FOREIGN KEY (parent_id)
|
||||
REFERENCES mjga.department(id)
|
||||
@@ -56,7 +56,7 @@ CREATE TABLE mjga.user_department_map (
|
||||
|
||||
CREATE TABLE mjga.position (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE
|
||||
name VARCHAR NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE mjga.user_position_map (
|
||||
@@ -80,12 +80,12 @@ CREATE TYPE "llm_type_enum" AS ENUM (
|
||||
|
||||
CREATE TABLE mjga.ai_llm_config (
|
||||
id BIGSERIAL NOT NULL UNIQUE,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR NOT NULL UNIQUE,
|
||||
code mjga.llm_code_enum NOT NULL UNIQUE,
|
||||
model_name VARCHAR(255) NOT NULL,
|
||||
model_name VARCHAR NOT NULL,
|
||||
type LLM_TYPE_ENUM NOT NULL,
|
||||
api_key VARCHAR(255) NOT NULL,
|
||||
url VARCHAR(255) NOT NULL,
|
||||
api_key VARCHAR NOT NULL,
|
||||
url VARCHAR NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT true,
|
||||
priority SMALLINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(id)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE mjga.library (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL UNIQUE,
|
||||
description VARCHAR,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TYPE mjga.library_doc_status_enum AS ENUM (
|
||||
'SUCCESS',
|
||||
'INDEXING'
|
||||
);
|
||||
|
||||
CREATE TABLE mjga.library_doc (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
lib_id BIGINT NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
identify VARCHAR NOT NULL UNIQUE,
|
||||
path VARCHAR NOT NULL,
|
||||
meta JSON NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT true,
|
||||
status mjga.library_doc_status_enum NOT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time TIMESTAMPTZ,
|
||||
FOREIGN KEY (lib_id) REFERENCES mjga.library (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE mjga.library_doc_segment (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
doc_id BIGINT NOT NULL,
|
||||
embedding_id VARCHAR NOT NULL UNIQUE,
|
||||
content TEXT,
|
||||
token_usage INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (doc_id) REFERENCES mjga.library_doc (id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -4,7 +4,7 @@ CREATE TABLE mjga.user (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR NOT NULL UNIQUE,
|
||||
avatar VARCHAR,
|
||||
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
password VARCHAR NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
@@ -39,7 +39,7 @@ CREATE TABLE mjga.user_role_map (
|
||||
|
||||
CREATE TABLE mjga.department (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR NOT NULL UNIQUE,
|
||||
parent_id BIGINT,
|
||||
FOREIGN KEY (parent_id)
|
||||
REFERENCES mjga.department(id)
|
||||
@@ -56,7 +56,7 @@ CREATE TABLE mjga.user_department_map (
|
||||
|
||||
CREATE TABLE mjga.position (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE
|
||||
name VARCHAR NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE mjga.user_position_map (
|
||||
@@ -80,12 +80,12 @@ CREATE TYPE "llm_type_enum" AS ENUM (
|
||||
|
||||
CREATE TABLE mjga.ai_llm_config (
|
||||
id BIGSERIAL NOT NULL UNIQUE,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR NOT NULL UNIQUE,
|
||||
code mjga.llm_code_enum NOT NULL UNIQUE,
|
||||
model_name VARCHAR(255) NOT NULL,
|
||||
model_name VARCHAR NOT NULL,
|
||||
type LLM_TYPE_ENUM NOT NULL,
|
||||
api_key VARCHAR(255) NOT NULL,
|
||||
url VARCHAR(255) NOT NULL,
|
||||
api_key VARCHAR NOT NULL,
|
||||
url VARCHAR NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT true,
|
||||
priority SMALLINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(id)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE mjga.library (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR NOT NULL UNIQUE,
|
||||
data_count INTEGER NOT NULL DEFAULT 0,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE mjga.library_doc (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
lib_id BIGINT NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
identify VARCHAR NOT NULL UNIQUE,
|
||||
path VARCHAR NOT NULL,
|
||||
meta JSON NOT NULL,
|
||||
enable BOOLEAN NOT NULL DEFAULT true,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time TIMESTAMPTZ,
|
||||
FOREIGN KEY (lib_id) REFERENCES mjga.library (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE mjga.library_doc_segment (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
doc_id BIGINT NOT NULL,
|
||||
embedding_id VARCHAR NOT NULL UNIQUE,
|
||||
content TEXT,
|
||||
token_usage INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (doc_id) REFERENCES mjga.library_doc (id) ON DELETE CASCADE
|
||||
);
|
||||
Reference in New Issue
Block a user