1.添加模型应用及对话内容

This commit is contained in:
ganliang5722 2025-03-05 15:01:12 +08:00
parent e36c60af92
commit 04b6793941
26 changed files with 1658 additions and 0 deletions

View File

@ -18,8 +18,10 @@ package cn.iocoder.yudao.module.langchat;
import cn.iocoder.yudao.module.langchat.config.EnableFileStorage;
import cn.iocoder.yudao.module.langchat.config.SpringFileStorageProperties;
import cn.iocoder.yudao.module.langchat.properties.ChatProps;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
@ -30,6 +32,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
@SpringBootApplication
@EnableConfigurationProperties({
SpringFileStorageProperties.class,
ChatProps.class
})
public class LangChatApplication {

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.constant;
/**
* @author tycoding
* @since 2024/1/15
*/
public interface CommonConst {
/**
* UTF-8 编码
*/
String UTF_8 = "utf-8";
/**
* 菜单类型menu
*/
String MENU_TYPE_MENU = "menu";
/**
* 菜单类型button
*/
String MENU_TYPE_BUTTON = "button";
/**
* 菜单默认Icon图标
*/
String MENU_ICON = "alert";
String LAYOUT = "Layout";
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.hutool.core.lang.Dict;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.langchat.entity.AigcApp;
import cn.iocoder.yudao.module.langchat.entity.AigcAppApi;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppApiService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.store.AppStore;
import cn.iocoder.yudao.module.langchat.utils.MybatisUtil;
import cn.iocoder.yudao.module.langchat.utils.QueryPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RestController
@RequiredArgsConstructor
@RequestMapping("/aigc/app")
public class AigcAppController {
private final AigcAppService aigcAppService;
private final AigcAppApiService aigcAppApiService;
private final AppStore appStore;
private final AigcKnowledgeService knowledgeService;
@GetMapping("/channel/api/{appId}")
@TenantIgnore
public CommonResult<AigcAppApi> getApiChanel(@PathVariable String appId) {
List<AigcAppApi> list = aigcAppApiService.list(Wrappers.<AigcAppApi>lambdaQuery().eq(AigcAppApi::getAppId, appId));
return CommonResult.success(list.isEmpty() ? null : list.get(0));
}
@GetMapping("/list")
@TenantIgnore
public CommonResult<List<AigcApp>> list(AigcApp data) {
return CommonResult.success(aigcAppService.list(data));
}
@GetMapping("/page")
@TenantIgnore
public CommonResult<Dict> page(AigcApp data, QueryPage queryPage) {
return CommonResult.success(MybatisUtil.getData(aigcAppService.page(MybatisUtil.wrap(data, queryPage),
Wrappers.<AigcApp>lambdaQuery()
.like(StringUtils.isNotEmpty(data.getName()), AigcApp::getName, data.getName())
)));
}
@GetMapping("/{id}")
@TenantIgnore
public CommonResult<AigcApp> findById(@PathVariable String id) {
AigcApp app = aigcAppService.getById(id);
return CommonResult.success(app);
}
@PostMapping
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:app:add')")
public CommonResult add(@RequestBody AigcApp data) {
data.setCreateTime(new Date());
data.setSaveTime(new Date());
aigcAppService.save(data);
appStore.init();
return CommonResult.success(null);
}
@PutMapping
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:app:update')")
public CommonResult update(@RequestBody AigcApp data) {
// 校验知识库是否是同纬度
List<String> knowledgeIds = data.getKnowledgeIds();
if (knowledgeIds != null && !knowledgeIds.isEmpty()) {
List<AigcKnowledge> list = knowledgeService.list(Wrappers.<AigcKnowledge>lambdaQuery().in(AigcKnowledge::getId, knowledgeIds));
Set<String> modelIds = new HashSet<>();
Set<String> storeIds = new HashSet<>();
list.forEach(know -> {
modelIds.add(know.getEmbedModelId());
storeIds.add(know.getEmbedStoreId());
});
if (modelIds.size() > 1) {
return CommonResult.error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请选择相同向量库数据源配置的知识库");
}
if (storeIds.size() > 1) {
return CommonResult.error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请选择相同向量模型配置的知识库");
// throw new ServiceException("请选择相同向量模型配置的知识库");
}
}
data.setSaveTime(new Date());
aigcAppService.updateById(data);
appStore.init();
return CommonResult.success(null);
}
@DeleteMapping("/{id}")
@TenantIgnore
@PreAuthorize("@ss.hasPermission('aigc:app:delete')")
public CommonResult delete(@PathVariable String id) {
aigcAppService.removeById(id);
appStore.init();
return CommonResult.success(null);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.controller.admin.aigc;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.langchat.entity.AigcOss;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcOssService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @author tycoding
* @since 2024/1/19
*/
@RequestMapping("/aigc/oss")
@RestController
@AllArgsConstructor
public class AigcOssController {
private final AigcOssService aigcOssService;
@GetMapping("/list")
public CommonResult list() {
List<AigcOss> list = aigcOssService.list(Wrappers.<AigcOss>lambdaQuery()
.eq(AigcOss::getUserId, SecurityFrameworkUtils.getLoginUserId())
.orderByDesc(AigcOss::getCreateTime)
);
return CommonResult.success(null);
}
@PostMapping("/upload")
public CommonResult upload(MultipartFile file) {
return CommonResult.success(aigcOssService.upload(file, String.valueOf(SecurityFrameworkUtils.getLoginUserId())));
}
@PutMapping
public CommonResult update(@RequestBody AigcOss data) {
aigcOssService.updateById(data);
return CommonResult.success(null);
}
@DeleteMapping("/{id}")
public CommonResult delete(@PathVariable String id) {
aigcOssService.removeById(id);
return CommonResult.success(null);
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.controller.admin.endpoint;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatRes;
import cn.iocoder.yudao.module.langchat.dal.dto.ImageR;
import cn.iocoder.yudao.module.langchat.dal.dto.PromptConst;
import cn.iocoder.yudao.module.langchat.entity.AigcApp;
import cn.iocoder.yudao.module.langchat.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.enums.RoleEnum;
import cn.iocoder.yudao.module.langchat.properties.ChatProps;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcMessageService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import cn.iocoder.yudao.module.langchat.service.core.ChatService;
import cn.iocoder.yudao.module.langchat.service.core.impl.PersistentChatMemoryStore;
import cn.iocoder.yudao.module.langchat.utils.PromptUtil;
import cn.iocoder.yudao.module.langchat.utils.StreamEmitter;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author tycoding
* @since 2024/1/30
*/
@Slf4j
@RequestMapping("/aigc")
@RestController
@AllArgsConstructor
public class ChatEndpoint {
private final ChatService chatService;
private final AigcMessageService messageService;
private final AigcModelService aigcModelService;
private final AigcAppService appService;
private final ChatProps chatProps;
@PostMapping("/chat/completions")
public SseEmitter chat(@RequestBody ChatReq req) {
StreamEmitter emitter = new StreamEmitter();
req.setEmitter(emitter);
req.setUserId(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
req.setUsername(SecurityFrameworkUtils.getLoginUserNickname());
ExecutorService executor = Executors.newSingleThreadExecutor();
req.setExecutor(executor);
return emitter.streaming(executor, () -> {
chatService.chat(req);
});
}
@GetMapping("/app/info")
public CommonResult<AigcApp> appInfo(@RequestParam String appId, String conversationId) {
AigcApp app = appService.getById(appId);
if (StrUtil.isBlank(conversationId)) {
conversationId = app.getId();
}
if (StrUtil.isNotBlank(app.getPrompt())) {
// initialize chat memory
SystemMessage message = new SystemMessage(app.getPrompt());
PersistentChatMemoryStore.init(conversationId, message);
}
return CommonResult.success(app);
}
@GetMapping("/chat/messages/{conversationId}")
public CommonResult messages(@PathVariable String conversationId) {
List<AigcMessage> list = messageService.getMessages(conversationId, String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
// initialize chat memory
List<ChatMessage> chatMessages = new ArrayList<>();
list.forEach(item -> {
if (chatMessages.size() >= chatProps.getMemoryMaxMessage()) {
return;
}
if (item.getRole().equals(RoleEnum.ASSISTANT.getName())) {
chatMessages.add(new AiMessage(item.getMessage()));
} else {
chatMessages.add(new UserMessage(item.getMessage()));
}
});
PersistentChatMemoryStore.init(conversationId, chatMessages);
return CommonResult.success(list);
}
@DeleteMapping("/chat/messages/clean/{conversationId}")
public CommonResult cleanMessage(@PathVariable String conversationId) {
messageService.clearMessage(conversationId);
// clean chat memory
PersistentChatMemoryStore.clean(conversationId);
return CommonResult.success(null);
}
@PostMapping("/chat/mindmap")
public CommonResult mindmap(@RequestBody ChatReq req) {
req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.MINDMAP));
return CommonResult.success(new ChatRes(chatService.text(req)));
}
@PostMapping("/chat/image")
public CommonResult image(@RequestBody ImageR req) {
req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.IMAGE));
return CommonResult.success(chatService.image(req));
}
@GetMapping("/chat/getImageModels")
public CommonResult<List<AigcModel>> getImageModels() {
List<AigcModel> list = aigcModelService.getImageModels();
list.forEach(i -> {
i.setApiKey(null);
i.setSecretKey(null);
});
return CommonResult.success(list);
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.dal.dto;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author tycoding
* @since 2024/1/29
*/
@Data
@Accessors(chain = true)
public class ChatRes {
private boolean isDone = false;
private String message;
private Integer usedToken;
private long time;
public ChatRes(String message) {
this.message = message;
}
public ChatRes(Integer usedToken, long startTime) {
this.isDone = true;
this.usedToken = usedToken;
this.time = System.currentTimeMillis() - startTime;
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.dal.dto;
import dev.langchain4j.model.input.Prompt;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author tycoding
* @since 2024/1/6
*/
@Data
@Accessors(chain = true)
public class ImageR {
private String modelId;
private String modelName;
private String modelProvider;
private Prompt prompt;
/**
* 内容
*/
private String message;
/**
* 质量
*/
private String quality;
/**
* 尺寸
*/
private String size;
/**
* 风格
*/
private String style;
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.dal.dto;
/**
* @author tycoding
* @since 2024/3/1
*/
public interface PromptConst {
String QUESTION = "question";
String EMPTY = """
------
{{question}}
------
""";
String DOCUMENT = "You are good at analyzing documents. Please analyze my questions according to the following documents, question: [{{question}}], [docs]";
String MINDMAP = """
# Role
You are a Markdown outline format engineer who focuses on answering user questions. You can quickly and accurately convert user questions into refined Markdown outline titles, and refine the specific details of each title.
## Skills
### Skill 1: Identify user question intent
- Accurately understand the specific content and needs of user questions.
### Skill 2: Convert to Markdown outline
- Simplify user questions into Markdown outline-style titles.
### Skill 3: Return to user
- Return the optimized outline to the user.
## Constraints
- Only return the organized Markdown format content, without other explanation information
- Answer the question in the language used by the user.
- Return the answer in Markdown style, keep the main title as concise as possible; and refine the specific step information of each main title in the subtitle.
""";
String WRITE = """
# 角色
你是一名专业文案撰写师你擅长运用行业领域相关知识以专业的视角为用户生成Markdown文档
## 技能
### 技能 1: 写作
- 提取用户输入的主题和关键信息
### 技能 2: 专业知识应用
- 了解相关行业的相关知识
- 在撰写内容时运用专业的语言和视角
### 技能 3: 遵循Markdown格式
- 拆分文档内容以Markdown大纲格式分段内容更易于用户阅读
## 限制
- 只讨论写作相关的话题不要返回其他任何内容和解释
- 始终以用户输入的信息为主题撰写内容
""";
String IMAGE = """
Please generate the corresponding pictures according to the following requirements.
""";
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author tycoding
* @since 2024/8/21
*/
@Data
@ConfigurationProperties("langchat.chat")
public class ChatProps {
/**
* 上下文的长度
*/
private Integer memoryMaxMessage = 20;
/**
* 前端渲染的消息长度过长会导致页面渲染卡顿
*/
private Integer previewMaxMessage = 100;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.provider;
import cn.hutool.core.util.ObjectUtil;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.image.ImageModel;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author tycoding
* @since 2024/3/8
*/
@Component
@AllArgsConstructor
public class ModelProvider {
private final ModelStoreFactory modelStoreFactory;
public StreamingChatLanguageModel stream(String modelId) {
StreamingChatLanguageModel streamingChatModel = modelStoreFactory.getStreamingChatModel(modelId);
if (ObjectUtil.isNotEmpty(streamingChatModel)) {
return streamingChatModel;
}
throw new RuntimeException("没有匹配到模型,请检查模型配置!");
}
public ChatLanguageModel text(String modelId) {
ChatLanguageModel chatLanguageModel = modelStoreFactory.getChatLanguageModel(modelId);
if (ObjectUtil.isNotEmpty(chatLanguageModel)) {
return chatLanguageModel;
}
throw new RuntimeException("没有匹配到模型,请检查模型配置!");
}
public ImageModel image(String modelId) {
ImageModel imageModel = modelStoreFactory.getImageModel(modelId);
if (ObjectUtil.isNotEmpty(imageModel)) {
return imageModel;
}
throw new RuntimeException("没有匹配到模型,请检查模型配置!");
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.aigc.impl;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.entity.AigcApp;
import cn.iocoder.yudao.module.langchat.entity.AigcKnowledge;
import cn.iocoder.yudao.module.langchat.entity.AigcModel;
import cn.iocoder.yudao.module.langchat.mapper.AigcAppMapper;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcKnowledgeService;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcModelService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author tycoding
* @since 2024/7/26
*/
@RequiredArgsConstructor
@Service
public class AigcAppServiceImpl extends ServiceImpl<AigcAppMapper, AigcApp> implements AigcAppService {
private final AigcModelService aigcModelService;
private final AigcKnowledgeService aigcKnowledgeService;
@Override
public List<AigcApp> list(AigcApp data) {
List<AigcApp> list = baseMapper.selectList(Wrappers.<AigcApp>lambdaQuery()
.like(StrUtil.isNotBlank(data.getName()), AigcApp::getName, data.getName()));
Map<String, List<AigcModel>> modelMap = aigcModelService.list(new AigcModel()).stream().collect(Collectors.groupingBy(AigcModel::getId));
Map<String, List<AigcKnowledge>> knowledgeMap = aigcKnowledgeService.list().stream().collect(Collectors.groupingBy(AigcKnowledge::getId));
list.forEach(i -> {
List<AigcModel> models = modelMap.get(i.getModelId());
if (models != null) {
i.setModel(models.get(0));
}
if (i.getKnowledgeIds() != null) {
List<AigcKnowledge> knowledges = new ArrayList<>();
i.getKnowledgeIds().forEach(k -> {
List<AigcKnowledge> items = knowledgeMap.get(k);
if (items != null) {
knowledges.add(items.get(0));
}
});
i.setKnowledges(knowledges);
}
});
return list;
}
@Override
public AigcApp getById(String id) {
AigcApp app = baseMapper.selectById(id);
if (app != null) {
String modelId = app.getModelId();
if (modelId != null) {
app.setModel(aigcModelService.selectById(modelId));
}
List<String> knowledgeIds = app.getKnowledgeIds();
if (knowledgeIds != null && !knowledgeIds.isEmpty()) {
app.setKnowledges(aigcKnowledgeService.list(Wrappers.<AigcKnowledge>lambdaQuery().in(AigcKnowledge::getId, knowledgeIds)));
}
}
return app;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,135 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core.impl;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatRes;
import cn.iocoder.yudao.module.langchat.dal.dto.ImageR;
import cn.iocoder.yudao.module.langchat.entity.AigcApp;
import cn.iocoder.yudao.module.langchat.entity.AigcMessage;
import cn.iocoder.yudao.module.langchat.entity.AigcOss;
import cn.iocoder.yudao.module.langchat.enums.RoleEnum;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcMessageService;
import cn.iocoder.yudao.module.langchat.service.core.ChatService;
import cn.iocoder.yudao.module.langchat.service.core.LangChatService;
import cn.iocoder.yudao.module.langchat.store.AppStore;
import cn.iocoder.yudao.module.langchat.utils.ServletUtil;
import cn.iocoder.yudao.module.langchat.utils.StreamEmitter;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.model.output.TokenUsage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
* @author tycoding
* @since 2024/1/4
*/
@Slf4j
@Service
@AllArgsConstructor
public class ChatServiceImpl implements ChatService {
private final LangChatService langChatService;
private final AigcMessageService aigcMessageService;
private final AppStore appStore;
@Override
public void chat(ChatReq req) {
StreamEmitter emitter = req.getEmitter();
long startTime = System.currentTimeMillis();
StringBuilder text = new StringBuilder();
if (StrUtil.isNotBlank(req.getAppId())) {
AigcApp app = appStore.get(req.getAppId());
if (app != null) {
req.setModelId(app.getModelId());
req.setPromptText(app.getPrompt());
req.setKnowledgeIds(app.getKnowledgeIds());
}
}
// save user message
req.setRole(RoleEnum.USER.getName());
saveMessage(req, 0, 0);
try {
langChatService
.chat(req)
.onNext(e -> {
text.append(e);
emitter.send(new ChatRes(e));
})
.onComplete((e) -> {
TokenUsage tokenUsage = e.tokenUsage();
ChatRes res = new ChatRes(tokenUsage.totalTokenCount(), startTime);
emitter.send(res);
emitter.complete();
// save assistant message
req.setMessage(text.toString());
req.setRole(RoleEnum.ASSISTANT.getName());
saveMessage(req, tokenUsage.inputTokenCount(), tokenUsage.outputTokenCount());
})
.onError((e) -> {
emitter.error(e.getMessage());
throw new RuntimeException(e.getMessage());
})
.start();
} catch (Exception e) {
e.printStackTrace();
emitter.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
private void saveMessage(ChatReq req, Integer inputToken, Integer outputToken) {
if (req.getConversationId() != null) {
AigcMessage message = new AigcMessage();
BeanUtils.copyProperties(req, message);
message.setIp(ServletUtil.getIpAddr());
message.setPromptTokens(inputToken);
message.setTokens(outputToken);
aigcMessageService.addMessage(message);
}
}
@Override
public String text(ChatReq req) {
String text;
try {
text = langChatService.text(req);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
return text;
}
@Override
public AigcOss image(ImageR req) {
Response<Image> res = langChatService.image(req);
String path = res.content().url().toString();
AigcOss oss = new AigcOss();
oss.setUrl(path);
return oss;
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.langchat.dal.dto.ChatReq;
import cn.iocoder.yudao.module.langchat.dal.dto.ImageR;
import cn.iocoder.yudao.module.langchat.properties.ChatProps;
import cn.iocoder.yudao.module.langchat.provider.EmbeddingProvider;
import cn.iocoder.yudao.module.langchat.provider.ModelProvider;
import cn.iocoder.yudao.module.langchat.service.core.Agent;
import cn.iocoder.yudao.module.langchat.service.core.LangChatService;
import cn.iocoder.yudao.module.langchat.utils.PromptUtil;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.image.ImageModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.Query;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.store.embedding.filter.Filter;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.util.function.Function;
import static cn.iocoder.yudao.module.langchat.enums.EmbedConst.KNOWLEDGE;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
/**
* @author tycoding
* @since 2024/3/8
*/
@Slf4j
@Service
@AllArgsConstructor
public class LangChatServiceImpl implements LangChatService {
private final ModelProvider provider;
private final EmbeddingProvider embeddingProvider;
private final ChatProps chatProps;
private AiServices<Agent> build(StreamingChatLanguageModel streamModel, ChatLanguageModel model, ChatReq req) {
AiServices<Agent> aiServices = AiServices.builder(Agent.class)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder()
.id(req.getConversationId())
.chatMemoryStore(new PersistentChatMemoryStore())
.maxMessages(chatProps.getMemoryMaxMessage())
.build());
if (StrUtil.isNotBlank(req.getPromptText())) {
aiServices.systemMessageProvider(memoryId -> req.getPromptText());
}
if (streamModel != null) {
aiServices.streamingChatLanguageModel(streamModel);
}
if (model != null) {
aiServices.chatLanguageModel(model);
}
return aiServices;
}
@Override
public TokenStream chat(ChatReq req) {
StreamingChatLanguageModel model = provider.stream(req.getModelId());
if (StrUtil.isBlank(req.getConversationId())) {
req.setConversationId(IdUtil.simpleUUID());
}
AiServices<Agent> aiServices = build(model, null, req);
if (StrUtil.isNotBlank(req.getKnowledgeId())) {
req.getKnowledgeIds().add(req.getKnowledgeId());
}
if (req.getKnowledgeIds() != null && !req.getKnowledgeIds().isEmpty()) {
Function<Query, Filter> filter = (query) -> metadataKey(KNOWLEDGE).isIn(req.getKnowledgeIds());
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingProvider.getEmbeddingStore(req.getKnowledgeIds()))
.embeddingModel(embeddingProvider.getEmbeddingModel(req.getKnowledgeIds()))
.dynamicFilter(filter)
.build();
aiServices.retrievalAugmentor(DefaultRetrievalAugmentor
.builder()
.contentRetriever(contentRetriever)
.build());
}
Agent agent = aiServices.build();
return agent.stream(req.getConversationId(), req.getMessage());
}
@Override
public TokenStream singleChat(ChatReq req) {
StreamingChatLanguageModel model = provider.stream(req.getModelId());
if (StrUtil.isBlank(req.getConversationId())) {
req.setConversationId(IdUtil.simpleUUID());
}
Agent agent = build(model, null, req).build();
if (req.getPrompt() == null) {
req.setPrompt(PromptUtil.build(req.getMessage(), req.getPromptText()));
}
return agent.stream(req.getConversationId(), req.getPrompt().text());
}
@Override
public String text(ChatReq req) {
if (StrUtil.isBlank(req.getConversationId())) {
req.setConversationId(IdUtil.simpleUUID());
}
try {
ChatLanguageModel model = provider.text(req.getModelId());
Agent agent = build(null, model, req).build();
String text = agent.text(req.getConversationId(), req.getMessage());
return text;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Response<Image> image(ImageR req) {
try {
ImageModel model = provider.image(req.getModelId());
return model.generate(req.getPrompt().text());
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "图片生成失败");
}
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.service.core.impl;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author tycoding
* @since 2024/8/15
*/
@Slf4j
public class PersistentChatMemoryStore implements ChatMemoryStore {
private static final Map<Object, List<ChatMessage>> store = new HashMap<>();
private static final Map<Object, Boolean> initSystemMessageStore = new HashMap<>();
private static final Map<Object, Boolean> initMessageStore = new HashMap<>();
public static void clean(Object memoryId) {
log.info("clean message memory store to: {}", memoryId);
store.remove(memoryId);
}
public static void init(Object memoryId, SystemMessage message) {
Boolean isInitSystemMessage = initSystemMessageStore.get(memoryId);
if (isInitSystemMessage != null && isInitSystemMessage) {
return;
}
List<ChatMessage> list = store.get(memoryId);
if (list == null) {
store.put(memoryId, new ArrayList<>(List.of(message)));
} else {
list.add(message);
}
initSystemMessageStore.put(memoryId, true);
}
public static void init(Object memoryId, List<ChatMessage> messages) {
log.info("initialize message memory store to: {}", memoryId);
Boolean isInitMessage = initMessageStore.get(memoryId);
if (isInitMessage != null && isInitMessage) {
return;
}
List<ChatMessage> list = store.get(memoryId);
if (list == null) {
store.put(memoryId, messages);
} else {
list.addAll(messages);
}
initMessageStore.put(memoryId, true);
}
@Override
public List<ChatMessage> getMessages(Object memoryId) {
List<ChatMessage> list = store.get(memoryId);
if (list == null) {
return new ArrayList<>();
}
return list;
}
@Override
public void updateMessages(Object memoryId, List<ChatMessage> messages) {
store.put(memoryId, messages);
}
@Override
public void deleteMessages(Object memoryId) {
store.remove(memoryId);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.store;
import cn.iocoder.yudao.module.langchat.entity.AigcApp;
import cn.iocoder.yudao.module.langchat.service.aigc.AigcAppService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author tycoding
* @since 2024/8/8
*/
@Slf4j
@Component
@AllArgsConstructor
public class AppStore {
private static final Map<String, AigcApp> appMap = new HashMap<>();
private final AigcAppService aigcAppService;
@PostConstruct
public void init() {
log.info("initialize app config list...");
List<AigcApp> list = aigcAppService.list();
list.forEach(i -> appMap.put(i.getId(), i));
}
public AigcApp get(String appId) {
return appMap.get(appId);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.module.langchat.dal.dto.PromptConst;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import java.util.Map;
/**
* @author tycoding
* @since 2024/3/1
*/
public class PromptUtil {
public static Prompt build(String message) {
return new Prompt(message);
}
public static Prompt build(String message, String promptText) {
return new PromptTemplate(promptText + PromptConst.EMPTY).apply(Map.of(PromptConst.QUESTION, message));
}
public static Prompt build(String message, String promptText, Object param) {
Map<String, Object> params = BeanUtil.beanToMap(param, false, true);
params.put(PromptConst.QUESTION, message);
return new PromptTemplate(promptText).apply(params);
}
public static Prompt buildDocs(String message) {
return new PromptTemplate(PromptConst.DOCUMENT).apply(Map.of(PromptConst.QUESTION, message));
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2024 LangChat. TyCoding All Rights Reserved.
*
* Licensed under the GNU Affero General Public License, Version 3 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/agpl-3.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.iocoder.yudao.module.langchat.utils;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.langchat.constant.CommonConst;
import lombok.SneakyThrows;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author tycoding
* @since 2024/1/2
*/
public class ServletUtil {
@SneakyThrows
public static void write(HttpServletResponse response, CommonResult data) {
response.setStatus(data.getCode());
response.setHeader("Content-type", "application/json;charset=" + CommonConst.UTF_8);
response.setCharacterEncoding(CommonConst.UTF_8);
response.getWriter().write(JSONUtil.toJsonStr(data));
}
@SneakyThrows
public static void write(HttpServletResponse response, int status, CommonResult data) {
response.setStatus(status);
response.setHeader("Content-type", "application/json;charset=" + CommonConst.UTF_8);
response.setCharacterEncoding(CommonConst.UTF_8);
response.getWriter().write(JSONUtil.toJsonStr(data));
}
public static HttpServletRequest getRequest() {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletRequestAttributes != null) {
return servletRequestAttributes.getRequest();
}
return null;
}
public static String getAuthorizationToken() {
String token = getRequest().getHeader("Authorization");
if (token != null && token.toLowerCase().startsWith("bearer")) {
return token.toLowerCase().replace("bearer", "").trim();
}
return null;
}
public static String getToken(String token) {
if (token != null && token.toLowerCase().startsWith("bearer")) {
return token.replace("bearer", "").trim();
}
return token;
}
public static String getIpAddr() {
HttpServletRequest request = getRequest();
if (request == null) {
return "unknown";
} else {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ip)) {
ip = "127.0.0.1";
}
if (ip.contains(",")) {
ip = ip.split(",")[0];
}
return ip;
}
}
}