mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-08 01:07:31 +00:00
feat: 支持插件功能
This commit is contained in:
@@ -83,6 +83,9 @@
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
|
||||
@@ -41,13 +41,13 @@ public class MilvusVectorStore implements VectorStore{
|
||||
@Resource
|
||||
private ConfigService configService;
|
||||
|
||||
// @PostConstruct
|
||||
@PostConstruct
|
||||
public void loadConfig() {
|
||||
this.dimension = Integer.parseInt(configService.getConfigValue("milvus", "dimension"));
|
||||
this.collectionName = configService.getConfigValue("milvus", "collection");
|
||||
}
|
||||
|
||||
//@PostConstruct
|
||||
@PostConstruct
|
||||
public void init(){
|
||||
String milvusHost = configService.getConfigValue("milvus", "host");
|
||||
String milvausPort = configService.getConfigValue("milvus", "port");
|
||||
|
||||
@@ -6,11 +6,16 @@ import java.util.List;
|
||||
* 向量存储
|
||||
*/
|
||||
public interface VectorStore {
|
||||
void storeEmbeddings(List<String> chunkList,List<List<Double>> vectorList, String kid, String docId,List<String> fidList);
|
||||
void removeByDocId(String kid,String docId);
|
||||
|
||||
void storeEmbeddings(List<String> chunkList, List<List<Double>> vectorList, String kid, String docId, List<String> fidList);
|
||||
|
||||
void removeByDocId(String kid, String docId);
|
||||
|
||||
void removeByKid(String kid);
|
||||
List<String> nearest(List<Double> queryVector,String kid);
|
||||
List<String> nearest(String query,String kid);
|
||||
|
||||
List<String> nearest(List<Double> queryVector, String kid);
|
||||
|
||||
List<String> nearest(String query, String kid);
|
||||
|
||||
void newSchema(String kid);
|
||||
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
package org.ruoyi.knowledge.chain.vectorstore;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
|
||||
import org.ruoyi.knowledge.mapper.KnowledgeInfoMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class VectorStoreFactory {
|
||||
|
||||
private final String type = "weaviate";
|
||||
|
||||
private final WeaviateVectorStore weaviateVectorStore;
|
||||
|
||||
private final MilvusVectorStore milvusVectorStore;
|
||||
|
||||
@Resource
|
||||
private KnowledgeInfoMapper knowledgeInfoMapper;
|
||||
|
||||
public VectorStoreFactory(WeaviateVectorStore weaviateVectorStore, MilvusVectorStore milvusVectorStore) {
|
||||
this.weaviateVectorStore = weaviateVectorStore;
|
||||
this.milvusVectorStore = milvusVectorStore;
|
||||
}
|
||||
|
||||
public VectorStore getVectorStore(String kid){
|
||||
// if ("weaviate".equals(type)){
|
||||
// return weaviateVectorStore;
|
||||
// }else if ("milvus".equals(type)){
|
||||
// return milvusVectorStore;
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
return weaviateVectorStore;
|
||||
KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoMapper.selectVoById(Long.valueOf(kid));
|
||||
String vectorModel = knowledgeInfoVo.getVector();
|
||||
if ("weaviate".equals(vectorModel)){
|
||||
return weaviateVectorStore;
|
||||
}else if ("milvus".equals(vectorModel)){
|
||||
return milvusVectorStore;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,20 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@Primary
|
||||
@AllArgsConstructor
|
||||
public class VectorStoreWrapper implements VectorStore{
|
||||
public class VectorStoreWrapper implements VectorStore {
|
||||
|
||||
private final VectorStoreFactory vectorStoreFactory;
|
||||
|
||||
@Override
|
||||
public void storeEmbeddings(List<String> chunkList, List<List<Double>> vectorList, String kid, String docId, List<String> fidList) {
|
||||
VectorStore vectorStore = vectorStoreFactory.getVectorStore(kid);
|
||||
vectorStore.storeEmbeddings(chunkList, vectorList, kid, docId, fidList);
|
||||
vectorStore.storeEmbeddings(chunkList, vectorList, kid, docId, fidList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeByDocId(String kid, String docId) {
|
||||
VectorStore vectorStore = vectorStoreFactory.getVectorStore(kid);
|
||||
vectorStore.removeByDocId(kid,docId);
|
||||
vectorStore.removeByDocId(kid, docId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,7 +36,7 @@ public class VectorStoreWrapper implements VectorStore{
|
||||
@Override
|
||||
public List<String> nearest(List<Double> queryVector, String kid) {
|
||||
VectorStore vectorStore = vectorStoreFactory.getVectorStore(kid);
|
||||
return vectorStore.nearest(queryVector,kid);
|
||||
return vectorStore.nearest(queryVector, kid);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,7 +45,7 @@ public class KnowledgeInfo implements Serializable {
|
||||
private String kname;
|
||||
|
||||
/**
|
||||
* 知识库名称
|
||||
* 是否公开知识库(0 否 1是)
|
||||
*/
|
||||
private String share;
|
||||
|
||||
|
||||
@@ -49,8 +49,6 @@ public interface IKnowledgeAttachService {
|
||||
|
||||
/**
|
||||
* 删除知识附件
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
void removeKnowledgeAttach(String kid);
|
||||
void removeKnowledgeAttach(String docId);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ package org.ruoyi.knowledge.service;
|
||||
|
||||
import org.ruoyi.common.mybatis.core.page.PageQuery;
|
||||
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
|
||||
import org.ruoyi.knowledge.domain.KnowledgeAttach;
|
||||
import org.ruoyi.knowledge.domain.bo.KnowledgeAttachBo;
|
||||
import org.ruoyi.knowledge.domain.bo.KnowledgeInfoBo;
|
||||
import org.ruoyi.knowledge.domain.req.KnowledgeInfoUploadRequest;
|
||||
import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -40,7 +37,6 @@ public interface IKnowledgeInfoService {
|
||||
*/
|
||||
Boolean updateByBo(KnowledgeInfoBo bo);
|
||||
|
||||
|
||||
/**
|
||||
* 新增知识库
|
||||
*/
|
||||
|
||||
@@ -40,8 +40,7 @@ public class EmbeddingServiceImpl implements EmbeddingService {
|
||||
|
||||
@Override
|
||||
public List<Double> getQueryVector(String query, String kid) {
|
||||
List<Double> queryVector = vectorization.singleVectorization(query,kid);
|
||||
return queryVector;
|
||||
return vectorization.singleVectorization(query,kid);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -126,13 +126,9 @@ public class KnowledgeAttachServiceImpl implements IKnowledgeAttachService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeKnowledgeAttach(String kid) {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
public void removeKnowledgeAttach(String docId) {
|
||||
Map<String,Object> map = new HashMap<>();
|
||||
map.put("kid",kid);
|
||||
List<KnowledgeInfoVo> knowledgeInfoList = knowledgeInfoMapper.selectVoByMap(map);
|
||||
knowledgeInfoService.check(knowledgeInfoList);
|
||||
|
||||
map.put("doc_id",docId);
|
||||
baseMapper.deleteByMap(map);
|
||||
fragmentMapper.deleteByMap(map);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package org.ruoyi.knowledge.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.ollama4j.OllamaAPI;
|
||||
import io.github.ollama4j.models.chat.OllamaChatMessageRole;
|
||||
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
|
||||
import io.github.ollama4j.models.chat.OllamaChatRequestModel;
|
||||
import io.github.ollama4j.models.chat.OllamaChatResult;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||
@@ -30,6 +26,7 @@ import org.ruoyi.knowledge.mapper.KnowledgeInfoMapper;
|
||||
import org.ruoyi.knowledge.service.EmbeddingService;
|
||||
import org.ruoyi.knowledge.service.IKnowledgeInfoService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -41,8 +38,8 @@ import java.util.*;
|
||||
* @author Lion Li
|
||||
* @date 2024-10-21
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
|
||||
private final KnowledgeInfoMapper baseMapper;
|
||||
@@ -110,9 +107,8 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveOne(KnowledgeInfoBo bo) {
|
||||
KnowledgeInfo knowledgeInfo = MapstructUtils.convert(bo, KnowledgeInfo.class);
|
||||
if (StringUtils.isBlank(bo.getKid())){
|
||||
@@ -122,7 +118,7 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
knowledgeInfo.setUid(LoginHelper.getLoginUser().getUserId());
|
||||
}
|
||||
baseMapper.insert(knowledgeInfo);
|
||||
embeddingService.createSchema(kid);
|
||||
embeddingService.createSchema(String.valueOf(knowledgeInfo.getId()));
|
||||
}else {
|
||||
baseMapper.updateById(knowledgeInfo);
|
||||
}
|
||||
@@ -148,19 +144,23 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
try {
|
||||
content = resourceLoader.getContent(file.getInputStream());
|
||||
chunkList = resourceLoader.getChunkList(content, kid);
|
||||
for (int i = 0; i < chunkList.size(); i++) {
|
||||
String fid = RandomUtil.randomString(16);
|
||||
fids.add(fid);
|
||||
KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
|
||||
knowledgeFragment.setKid(kid);
|
||||
knowledgeFragment.setDocId(docId);
|
||||
knowledgeFragment.setFid(fid);
|
||||
knowledgeFragment.setIdx(i);
|
||||
// String text = convertTextBlockToPretrainData(chunkList.get(i));
|
||||
knowledgeFragment.setContent(chunkList.get(i));
|
||||
knowledgeFragment.setCreateTime(new Date());
|
||||
fragmentMapper.insert(knowledgeFragment);
|
||||
List<KnowledgeFragment> knowledgeFragmentList = new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(chunkList)) {
|
||||
for (int i = 0; i < chunkList.size(); i++) {
|
||||
String fid = RandomUtil.randomString(16);
|
||||
fids.add(fid);
|
||||
KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
|
||||
knowledgeFragment.setKid(kid);
|
||||
knowledgeFragment.setDocId(docId);
|
||||
knowledgeFragment.setFid(fid);
|
||||
knowledgeFragment.setIdx(i);
|
||||
// String text = convertTextBlockToPretrainData(chunkList.get(i));
|
||||
knowledgeFragment.setContent(chunkList.get(i));
|
||||
knowledgeFragment.setCreateTime(new Date());
|
||||
knowledgeFragmentList.add(knowledgeFragment);
|
||||
}
|
||||
}
|
||||
fragmentMapper.insertBatch(knowledgeFragmentList);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -171,19 +171,21 @@ public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeKnowledge(String id) {
|
||||
|
||||
Map<String,Object> map = new HashMap<>();
|
||||
map.put("kid",id);
|
||||
List<KnowledgeInfoVo> knowledgeInfoList = baseMapper.selectVoByMap(map);
|
||||
check(knowledgeInfoList);
|
||||
// 删除知识库
|
||||
baseMapper.deleteByMap(map);
|
||||
// 删除向量库信息
|
||||
knowledgeInfoList.forEach(knowledgeInfoVo -> {
|
||||
embeddingService.removeByKid(String.valueOf(knowledgeInfoVo.getId()));
|
||||
});
|
||||
// 删除附件和知识片段
|
||||
fragmentMapper.deleteByMap(map);
|
||||
attachMapper.deleteByMap(map);
|
||||
// 删除向量库信息
|
||||
embeddingService.removeByKid(id);
|
||||
// 删除知识库
|
||||
baseMapper.deleteByMap(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.system.domain.bo.SysUserBo;
|
||||
import org.ruoyi.system.domain.bo.SysUserPasswordBo;
|
||||
import org.ruoyi.system.domain.bo.SysUserProfileBo;
|
||||
import org.ruoyi.system.domain.vo.AvatarVo;
|
||||
import org.ruoyi.system.domain.vo.ProfileVo;
|
||||
@@ -75,23 +76,20 @@ public class SysProfileController extends BaseController {
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*
|
||||
* @param newPassword 旧密码
|
||||
* @param oldPassword 新密码
|
||||
*/
|
||||
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/updatePwd")
|
||||
public R<Void> updatePwd(String oldPassword, String newPassword) {
|
||||
public R<Void> updatePwd(@Validated @RequestBody SysUserPasswordBo bo) {
|
||||
SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
|
||||
String password = user.getPassword();
|
||||
if (!BCrypt.checkpw(oldPassword, password)) {
|
||||
if (!BCrypt.checkpw(bo.getOldPassword(), password)) {
|
||||
return R.fail("修改密码失败,旧密码错误");
|
||||
}
|
||||
if (BCrypt.checkpw(newPassword, password)) {
|
||||
if (BCrypt.checkpw(bo.getNewPassword(), password)) {
|
||||
return R.fail("新密码不能与旧密码相同");
|
||||
}
|
||||
|
||||
if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(newPassword)) > 0) {
|
||||
if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())) > 0) {
|
||||
return R.ok();
|
||||
}
|
||||
return R.fail("修改密码异常,请联系管理员");
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.ruoyi.system.domain.bo;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* 描述:用户密码修改bo
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2025/3/9
|
||||
*/
|
||||
@Data
|
||||
public class SysUserPasswordBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 旧密码
|
||||
*/
|
||||
@NotBlank(message = "旧密码不能为空")
|
||||
private String oldPassword;
|
||||
|
||||
/**
|
||||
* 新密码
|
||||
*/
|
||||
@NotBlank(message = "新密码不能为空")
|
||||
private String newPassword;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public class SSEEventSourceListener extends EventSourceListener {
|
||||
@Override
|
||||
public void onEvent(@NotNull EventSource eventSource, String id, String type, String data) {
|
||||
try {
|
||||
if (data.equals("[DONE]")) {
|
||||
if ("[DONE]".equals(data)) {
|
||||
//成功响应
|
||||
emitter.complete();
|
||||
if(StringUtils.isNotEmpty(modelName)){
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package org.ruoyi.system.plugin;
|
||||
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.ruoyi.common.chat.demo.ConsoleEventSourceListenerV3;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.entity.chat.Parameters;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.Tools;
|
||||
import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
|
||||
import org.ruoyi.common.chat.openai.OpenAiClient;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
|
||||
import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
|
||||
import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
|
||||
import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class WebSearchPlugin {
|
||||
|
||||
private OpenAiClient openAiClient;
|
||||
private OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
//可以为null
|
||||
// Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
||||
//!!!!千万别再生产或者测试环境打开BODY级别日志!!!!
|
||||
//!!!生产或者测试环境建议设置为这三种级别:NONE,BASIC,HEADERS,!!!
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
OkHttpClient okHttpClient = new OkHttpClient
|
||||
.Builder()
|
||||
// .proxy(proxy)
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.addInterceptor(new OpenAiResponseInterceptor())
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
openAiClient = OpenAiClient.builder()
|
||||
//支持多key传入,请求时候随机选择
|
||||
.apiKey(Arrays.asList("xx"))
|
||||
//自定义key的获取策略:默认KeyRandomStrategy
|
||||
//.keyStrategy(new KeyRandomStrategy())
|
||||
.keyStrategy(new KeyRandomStrategy())
|
||||
.okHttpClient(okHttpClient)
|
||||
//自己做了代理就传代理地址,没有可不不传,(关注公众号回复:openai ,获取免费的测试代理地址)
|
||||
.apiHost("https://open.bigmodel.cn/")
|
||||
.build();
|
||||
|
||||
openAiStreamClient = OpenAiStreamClient.builder()
|
||||
//支持多key传入,请求时候随机选择
|
||||
.apiKey(Arrays.asList("xx"))
|
||||
//自定义key的获取策略:默认KeyRandomStrategy
|
||||
.keyStrategy(new KeyRandomStrategy())
|
||||
.authInterceptor(new DynamicKeyOpenAiAuthInterceptor())
|
||||
.okHttpClient(okHttpClient)
|
||||
//自己做了代理就传代理地址,没有可不不传,(关注公众号回复:openai ,获取免费的测试代理地址)
|
||||
.apiHost("https://open.bigmodel.cn/")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Message message = Message.builder().role(Message.Role.USER).content("今天武汉天气怎么样").build();
|
||||
ChatCompletion chatCompletion = ChatCompletion
|
||||
.builder()
|
||||
.messages(Collections.singletonList(message))
|
||||
// .tools(Collections.singletonList(tools))
|
||||
.model("web-search-pro")
|
||||
.build();
|
||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
|
||||
System.out.printf("chatCompletionResponse=%s\n", JSONUtil.toJsonStr(chatCompletionResponse));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void streamToolsChat() {
|
||||
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch);
|
||||
|
||||
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语,并解释下词语对应物品的用途").build();
|
||||
//属性一
|
||||
JSONObject wordLength = new JSONObject();
|
||||
wordLength.put("type", "number");
|
||||
wordLength.put("description", "词语的长度");
|
||||
//属性二
|
||||
JSONObject language = new JSONObject();
|
||||
language.put("type", "string");
|
||||
language.put("enum", Arrays.asList("zh", "en"));
|
||||
language.put("description", "语言类型,例如:zh代表中文、en代表英语");
|
||||
//参数
|
||||
JSONObject properties = new JSONObject();
|
||||
properties.put("wordLength", wordLength);
|
||||
properties.put("language", language);
|
||||
Parameters parameters = Parameters.builder()
|
||||
.type("object")
|
||||
.properties(properties)
|
||||
.required(Collections.singletonList("wordLength")).build();
|
||||
Tools tools = Tools.builder()
|
||||
.type(Tools.Type.FUNCTION.getName())
|
||||
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
|
||||
.build();
|
||||
|
||||
ChatCompletion chatCompletion = ChatCompletion
|
||||
.builder()
|
||||
.messages(Collections.singletonList(message))
|
||||
.tools(Collections.singletonList(tools))
|
||||
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
|
||||
.build();
|
||||
openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
|
||||
|
||||
try {
|
||||
countDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls();
|
||||
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
|
||||
String oneWord = getOneWord(wordParam);
|
||||
|
||||
|
||||
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
|
||||
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
|
||||
//构造tool call
|
||||
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
|
||||
String content
|
||||
= "{ " +
|
||||
"\"wordLength\": \"3\", " +
|
||||
"\"language\": \"zh\", " +
|
||||
"\"word\": \"" + oneWord + "\"," +
|
||||
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
|
||||
"}";
|
||||
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
|
||||
List<Message> messageList = Arrays.asList(message, message2, message3);
|
||||
ChatCompletion chatCompletionV2 = ChatCompletion
|
||||
.builder()
|
||||
.messages(messageList)
|
||||
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
|
||||
.build();
|
||||
|
||||
|
||||
CountDownLatch countDownLatch1 = new CountDownLatch(1);
|
||||
openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch));
|
||||
try {
|
||||
countDownLatch1.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
countDownLatch1.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
static class WordParam {
|
||||
private int wordLength;
|
||||
@Builder.Default
|
||||
private String language = "zh";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取一个词语(根据语言和字符长度查询)
|
||||
* @param wordParam
|
||||
* @return
|
||||
*/
|
||||
public String getOneWord(WordParam wordParam) {
|
||||
|
||||
List<String> zh = Arrays.asList("大香蕉", "哈密瓜", "苹果");
|
||||
List<String> en = Arrays.asList("apple", "banana", "cantaloupe");
|
||||
if (wordParam.getLanguage().equals("zh")) {
|
||||
for (String e : zh) {
|
||||
if (e.length() == wordParam.getWordLength()) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wordParam.getLanguage().equals("en")) {
|
||||
for (String e : en) {
|
||||
if (e.length() == wordParam.getWordLength()) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "西瓜";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,16 +3,11 @@ package org.ruoyi.system.service.impl;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.azure.ai.openai.OpenAIClient;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.models.*;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import io.github.ollama4j.OllamaAPI;
|
||||
import io.github.ollama4j.models.chat.OllamaChatMessageRole;
|
||||
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
|
||||
import io.github.ollama4j.models.chat.OllamaChatRequestModel;
|
||||
import io.github.ollama4j.models.generate.OllamaStreamHandler;
|
||||
import io.github.ollama4j.utils.Options;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -33,6 +28,12 @@ import org.ruoyi.common.chat.entity.images.Item;
|
||||
import org.ruoyi.common.chat.entity.images.ResponseFormat;
|
||||
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
|
||||
import org.ruoyi.common.chat.plugin.CmdPlugin;
|
||||
import org.ruoyi.common.chat.plugin.CmdReq;
|
||||
import org.ruoyi.common.chat.plugin.SqlPlugin;
|
||||
import org.ruoyi.common.chat.plugin.SqlReq;
|
||||
import org.ruoyi.common.chat.sse.ConsoleEventSourceListener;
|
||||
import org.ruoyi.common.chat.utils.TikTokensUtil;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
@@ -43,12 +44,10 @@ import org.ruoyi.system.domain.bo.ChatMessageBo;
|
||||
import org.ruoyi.system.domain.bo.SysModelBo;
|
||||
import org.ruoyi.system.domain.request.translation.TranslationRequest;
|
||||
import org.ruoyi.system.domain.vo.SysModelVo;
|
||||
import org.ruoyi.system.domain.vo.SysUserVo;
|
||||
import org.ruoyi.system.listener.SSEEventSourceListener;
|
||||
import org.ruoyi.system.service.*;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -63,10 +62,10 @@ import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.github.ollama4j.utils.OptionsBuilder;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@@ -89,9 +88,6 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
|
||||
|
||||
private final ISysPackagePlanService sysPackagePlanService;
|
||||
|
||||
|
||||
@Override
|
||||
public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) {
|
||||
openAiStreamClient = chatConfig.getOpenAiStreamClient();
|
||||
@@ -101,12 +97,7 @@ public class SseServiceImpl implements ISseService {
|
||||
List<Message> messages = chatRequest.getMessages();
|
||||
try {
|
||||
if (StpUtil.isLogin()) {
|
||||
SysUserVo sysUserVo = userService.selectUserById(getUserId());
|
||||
// if (!checkModel(sysUserVo.getUserPlan(), chatRequest.getModel())) {
|
||||
// throw new BaseException("当前套餐不支持此模型!");
|
||||
// }
|
||||
LocalCache.CACHE.put("userId", getUserId());
|
||||
|
||||
Object content = messages.get(messages.size() - 1).getContent();
|
||||
|
||||
String chatString = "";
|
||||
@@ -161,36 +152,23 @@ public class SseServiceImpl implements ISseService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// else {
|
||||
//
|
||||
// // 初始请求次数
|
||||
// int number = 1;
|
||||
// // 获取请求IP
|
||||
// String realIp = getClientIpAddress(request);
|
||||
// // 根据IP获取次数
|
||||
// Integer requestNumber = RedisUtils.getCacheObject(realIp);
|
||||
// if (requestNumber == null) {
|
||||
// // 记录ip使用次数
|
||||
// RedisUtils.setCacheObject(realIp, number);
|
||||
// } else {
|
||||
// String configValue = configService.getConfigValue("mail", "free");
|
||||
// if (requestNumber > Integer.parseInt(configValue)) {
|
||||
// throw new BaseException("剩余次数不足,请充值后使用");
|
||||
// }
|
||||
// RedisUtils.setCacheObject(realIp, requestNumber + 1);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
.messages(messages)
|
||||
.model(chatRequest.getModel())
|
||||
.temperature(chatRequest.getTemperature())
|
||||
.topP(chatRequest.getTop_p())
|
||||
.stream(true)
|
||||
.build();
|
||||
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
|
||||
if("openCmd".equals(chatRequest.getModel())) {
|
||||
sseEmitter.send(cmdPlugin(messages));
|
||||
sseEmitter.complete();
|
||||
}else if ("sqlPlugin".equals(chatRequest.getModel())){
|
||||
sseEmitter.send(sqlPlugin(messages));
|
||||
sseEmitter.complete();
|
||||
} else {
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
.messages(messages)
|
||||
.model(chatRequest.getModel())
|
||||
.temperature(chatRequest.getTemperature())
|
||||
.topP(chatRequest.getTop_p())
|
||||
.stream(true)
|
||||
.build();
|
||||
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String message = e.getMessage();
|
||||
sendErrorEvent(sseEmitter, message);
|
||||
@@ -199,32 +177,51 @@ public class SseServiceImpl implements ISseService {
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
public String cmdPlugin(List<Message> messages) {
|
||||
CmdPlugin plugin = new CmdPlugin(CmdReq.class);
|
||||
// 插件名称
|
||||
plugin.setName("命令行工具");
|
||||
// 方法名称
|
||||
plugin.setFunction("openCmd");
|
||||
// 方法说明
|
||||
plugin.setDescription("提供一个命令行指令,比如<记事本>,指令使用中文");
|
||||
|
||||
// /**
|
||||
// * 查当前用户是否可以调用此模型
|
||||
// *
|
||||
// * @param planId
|
||||
// * @return
|
||||
// */
|
||||
// public Boolean checkModel(String planId, String modelName) {
|
||||
// SysPackagePlanBo sysPackagePlanBo = new SysPackagePlanBo();
|
||||
// if (modelName.startsWith("gpt-4-gizmo")) {
|
||||
// modelName = "gpt-4-gizmo";
|
||||
// }
|
||||
// if (StringUtils.isEmpty(planId)) {
|
||||
// sysPackagePlanBo.setName("Visitor");
|
||||
// } else if ("Visitor".equals(planId) || "Free".equals(planId)) {
|
||||
// sysPackagePlanBo.setName(planId);
|
||||
// } else {
|
||||
// // sysPackagePlanBo.setId(Long.valueOf(planId));
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// SysPackagePlanVo sysPackagePlanVo = sysPackagePlanService.queryList(sysPackagePlanBo).get(0);
|
||||
// // 将字符串转换为数组
|
||||
// String[] array = sysPackagePlanVo.getPlanDetail().split(",");
|
||||
// return Arrays.asList(array).contains(modelName);
|
||||
// }
|
||||
PluginAbstract.Arg arg = new PluginAbstract.Arg();
|
||||
// 参数名称
|
||||
arg.setName("cmd");
|
||||
// 参数说明
|
||||
arg.setDescription("命令行指令");
|
||||
// 参数类型
|
||||
arg.setType("string");
|
||||
arg.setRequired(true);
|
||||
plugin.setArgs(Collections.singletonList(arg));
|
||||
//有四个重载方法,都可以使用
|
||||
ChatCompletionResponse response = openAiStreamClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
|
||||
return response.getChoices().get(0).getMessage().getContent().toString();
|
||||
}
|
||||
|
||||
public String sqlPlugin(List<Message> messages) {
|
||||
SqlPlugin plugin = new SqlPlugin(SqlReq.class);
|
||||
// 插件名称
|
||||
plugin.setName("数据库查询插件");
|
||||
// 方法名称
|
||||
plugin.setFunction("sqlPlugin");
|
||||
// 方法说明
|
||||
plugin.setDescription("提供一个用户名称查询余额信息");
|
||||
|
||||
PluginAbstract.Arg arg = new PluginAbstract.Arg();
|
||||
// 参数名称
|
||||
arg.setName("username");
|
||||
// 参数说明
|
||||
arg.setDescription("用户名称");
|
||||
// 参数类型
|
||||
arg.setType("string");
|
||||
arg.setRequired(true);
|
||||
plugin.setArgs(Collections.singletonList(arg));
|
||||
//有四个重载方法,都可以使用
|
||||
ChatCompletionResponse response = openAiStreamClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
|
||||
return response.getChoices().get(0).getMessage().getContent().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据次数扣除余额
|
||||
@@ -295,25 +292,6 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
@Override
|
||||
public String chat(ChatRequest chatRequest, String userId) {
|
||||
// chatService.deductUserBalance(Long.valueOf(userId), 0.01);
|
||||
// // 保存消息记录
|
||||
// ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
// chatMessageBo.setUserId(Long.valueOf(userId));
|
||||
// chatMessageBo.setModelName(ChatCompletion.Model.GPT_3_5_TURBO.getName());
|
||||
// chatMessageBo.setContent(chatRequest.getPrompt());
|
||||
// chatMessageBo.setDeductCost(0.01);
|
||||
// chatMessageBo.setTotalTokens(0);
|
||||
// chatMessageService.insertByBo(chatMessageBo);
|
||||
//
|
||||
// openAiStreamClient = chatConfig.getOpenAiStreamClient();
|
||||
// Message message = Message.builder().role(Message.Role.USER).content(chatRequest.getPrompt()).build();
|
||||
// ChatCompletion chatCompletion = ChatCompletion
|
||||
// .builder()
|
||||
// .messages(Collections.singletonList(message))
|
||||
// .model(chatRequest.getModel())
|
||||
// .build();
|
||||
// ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
// return chatCompletionResponse.getChoices().get(0).getMessage().getContent();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -540,7 +518,8 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
@Override
|
||||
public String translation(TranslationRequest translationRequest) {
|
||||
|
||||
// 翻译模型固定为gpt-4o-mini
|
||||
translationRequest.setModel("gpt-4o-mini");
|
||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
chatMessageBo.setUserId(getUserId());
|
||||
chatMessageBo.setModelName(translationRequest.getModel());
|
||||
@@ -557,17 +536,12 @@ public class SseServiceImpl implements ISseService {
|
||||
"\n" +
|
||||
"请将用户输入词语翻译成{" + translationRequest.getTargetLanguage() + "}\n" +
|
||||
"\n" +
|
||||
"让我们一步一步来思考\n" +
|
||||
"==示例输出==\n" +
|
||||
"**原文** : <这里显示要翻译的原文信息>\n" +
|
||||
"**翻译** : <这里显示翻译成英语的结果>\n" +
|
||||
"\n" +
|
||||
"**造句** : What's the weather like today? Use the 'Weather Query' plugin to find out instantly! <造一个英语句子>\n" +
|
||||
"\n" +
|
||||
"**同义词** : Add-on、Extension、Module <这里显示1-3个英文的同义词>\n" +
|
||||
"\n" +
|
||||
"==示例结束==\n" +
|
||||
"\n" +
|
||||
"注意:请严格按示例进行输出").build();
|
||||
"注意:请严格按示例进行输出,返回markdown格式").build();
|
||||
messageList.add(sysMessage);
|
||||
Message message = Message.builder().role(Message.Role.USER).content(translationRequest.getPrompt()).build();
|
||||
messageList.add(message);
|
||||
@@ -646,4 +620,6 @@ public class SseServiceImpl implements ISseService {
|
||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -557,4 +557,11 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
|
||||
.select(SysUser::getUserName).eq(SysUser::getUserId, userId));
|
||||
return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String selectUserByName(String userName) {
|
||||
SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUserName, userName));
|
||||
return ObjectUtil.isNull(sysUser) ? null : sysUser.getUserBalance().toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user