拆分 jeesite-ai-tools 模块,工具调用保持会话控制权限,如当前用户只能查询有权限的数据
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
* No deletion without permission, or be held responsible to law.
|
* No deletion without permission, or be held responsible to law.
|
||||||
*/
|
*/
|
||||||
package com.jeesite.modules.cms.ai.service;
|
package com.jeesite.modules.ai.cms.service;
|
||||||
|
|
||||||
import com.jeesite.common.cache.CacheUtils;
|
import com.jeesite.common.cache.CacheUtils;
|
||||||
import com.jeesite.common.collect.ListUtils;
|
import com.jeesite.common.collect.ListUtils;
|
||||||
@@ -13,11 +13,12 @@ import com.jeesite.common.lang.DateUtils;
|
|||||||
import com.jeesite.common.lang.StringUtils;
|
import com.jeesite.common.lang.StringUtils;
|
||||||
import com.jeesite.common.mapper.JsonMapper;
|
import com.jeesite.common.mapper.JsonMapper;
|
||||||
import com.jeesite.common.service.BaseService;
|
import com.jeesite.common.service.BaseService;
|
||||||
import com.jeesite.modules.cms.ai.properties.CmsAiProperties;
|
import com.jeesite.modules.ai.cms.properties.AiCmsProperties;
|
||||||
import com.jeesite.modules.sys.entity.Area;
|
import com.jeesite.modules.sys.entity.Area;
|
||||||
import com.jeesite.modules.sys.utils.AreaUtils;
|
import com.jeesite.modules.sys.utils.AreaUtils;
|
||||||
import com.jeesite.modules.sys.utils.UserUtils;
|
import com.jeesite.modules.sys.utils.UserUtils;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||||
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
|
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
|
||||||
@@ -51,7 +52,7 @@ import java.util.Map;
|
|||||||
* @author ThinkGem
|
* @author ThinkGem
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class CmsAiChatService extends BaseService {
|
public class AiCmsChatService extends BaseService {
|
||||||
|
|
||||||
private static final String CMS_CHAT_CACHE = "cmsChatCache";
|
private static final String CMS_CHAT_CACHE = "cmsChatCache";
|
||||||
private static final String[] USER_MESSAGE_SEARCH = new String[]{"{", "}"};
|
private static final String[] USER_MESSAGE_SEARCH = new String[]{"{", "}"};
|
||||||
@@ -60,12 +61,12 @@ public class CmsAiChatService extends BaseService {
|
|||||||
private final ChatClient chatClient;
|
private final ChatClient chatClient;
|
||||||
private final ChatMemory chatMemory;
|
private final ChatMemory chatMemory;
|
||||||
private final VectorStore vectorStore;
|
private final VectorStore vectorStore;
|
||||||
private final CmsAiProperties properties;
|
private final AiCmsProperties properties;
|
||||||
|
|
||||||
public CmsAiChatService(ChatClient chatClient,
|
public AiCmsChatService(ChatClient chatClient,
|
||||||
ChatMemory chatMemory,
|
ChatMemory chatMemory,
|
||||||
ObjectProvider<VectorStore> vectorStore,
|
ObjectProvider<VectorStore> vectorStore,
|
||||||
CmsAiProperties properties) {
|
AiCmsProperties properties) {
|
||||||
this.chatClient = chatClient;
|
this.chatClient = chatClient;
|
||||||
this.chatMemory = chatMemory;
|
this.chatMemory = chatMemory;
|
||||||
this.vectorStore = vectorStore.getIfAvailable();
|
this.vectorStore = vectorStore.getIfAvailable();
|
||||||
@@ -150,6 +151,7 @@ public class CmsAiChatService extends BaseService {
|
|||||||
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
|
.promptTemplate(new PromptTemplate(properties.getDefaultPromptTemplate()))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
spec.toolContext(Map.of("subject", ThreadContext.getSubject()));
|
||||||
return spec.stream()
|
return spec.stream()
|
||||||
.chatResponse()
|
.chatResponse()
|
||||||
.doOnNext(response -> {
|
.doOnNext(response -> {
|
||||||
22
modules/ai/ai-tools/bin/deploy.bat
Normal file
22
modules/ai/ai-tools/bin/deploy.bat
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@echo off
|
||||||
|
rem /**
|
||||||
|
rem * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
rem * No deletion without permission, or be held responsible to law.
|
||||||
|
rem *
|
||||||
|
rem * Author: ThinkGem@163.com
|
||||||
|
rem */
|
||||||
|
echo.
|
||||||
|
echo [<5B><>Ϣ] <20><><EFBFBD>̵<F0B9A4B3>Maven<65><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
echo.
|
||||||
|
|
||||||
|
%~d0
|
||||||
|
cd %~dp0
|
||||||
|
|
||||||
|
call mvn -v
|
||||||
|
echo.
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
call mvn clean deploy -Dmaven.test.skip=true -Pdeploy
|
||||||
|
|
||||||
|
cd bin
|
||||||
|
pause
|
||||||
18
modules/ai/ai-tools/bin/deploy.sh
Normal file
18
modules/ai/ai-tools/bin/deploy.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# /**
|
||||||
|
# * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
# * No deletion without permission, or be held responsible to law.
|
||||||
|
# *
|
||||||
|
# * Author: ThinkGem@163.com
|
||||||
|
# */
|
||||||
|
echo ""
|
||||||
|
echo "[信息] 部署工程到Maven服务器。"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
mvn -v
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mvn clean deploy -Dmaven.test.skip=true -Pdeploy
|
||||||
|
|
||||||
|
cd bin
|
||||||
22
modules/ai/ai-tools/bin/package.bat
Normal file
22
modules/ai/ai-tools/bin/package.bat
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@echo off
|
||||||
|
rem /**
|
||||||
|
rem * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
rem * No deletion without permission, or be held responsible to law.
|
||||||
|
rem *
|
||||||
|
rem * Author: ThinkGem@163.com
|
||||||
|
rem */
|
||||||
|
echo.
|
||||||
|
echo [<5B><>Ϣ] <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><D7B0><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD>jar<61><72><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
|
||||||
|
echo.
|
||||||
|
|
||||||
|
%~d0
|
||||||
|
cd %~dp0
|
||||||
|
|
||||||
|
call mvn -v
|
||||||
|
echo.
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
call mvn clean install -Dmaven.test.skip=true -Ppackage
|
||||||
|
|
||||||
|
cd bin
|
||||||
|
pause
|
||||||
18
modules/ai/ai-tools/bin/package.sh
Normal file
18
modules/ai/ai-tools/bin/package.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# /**
|
||||||
|
# * Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
# * No deletion without permission, or be held responsible to law.
|
||||||
|
# *
|
||||||
|
# * Author: ThinkGem@163.com
|
||||||
|
# */
|
||||||
|
echo ""
|
||||||
|
echo "[信息] 打包安装工程,生成jar包文件。"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
mvn -v
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
mvn clean install -Dmaven.test.skip=true -Ppackage
|
||||||
|
|
||||||
|
cd bin
|
||||||
69
modules/ai/ai-tools/pom.xml
Normal file
69
modules/ai/ai-tools/pom.xml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.jeesite</groupId>
|
||||||
|
<artifactId>jeesite-parent-ai</artifactId>
|
||||||
|
<version>5.14.0.springboot3-SNAPSHOT</version>
|
||||||
|
<relativePath>../../../parent/ai/pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>jeesite-module-ai-tools</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>JeeSite Module AI Tools</name>
|
||||||
|
<url>http://jeesite.com</url>
|
||||||
|
<inceptionYear>2013-Now</inceptionYear>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- 核心模块 --><!--suppress VulnerableLibrariesLocal -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jeesite</groupId>
|
||||||
|
<artifactId>jeesite-module-core</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 内容管理模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.jeesite</groupId>
|
||||||
|
<artifactId>jeesite-module-cms</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AI Model -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-model</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AI MCP -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-mcp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>thinkgem</id>
|
||||||
|
<name>WangZhen</name>
|
||||||
|
<email>thinkgem at 163.com</email>
|
||||||
|
<roles><role>Project lead</role></roles>
|
||||||
|
<timezone>+8</timezone>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<organization>
|
||||||
|
<name>JeeSite</name>
|
||||||
|
<url>http://jeesite.com</url>
|
||||||
|
</organization>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
* No deletion without permission, or be held responsible to law.
|
||||||
|
*/
|
||||||
|
package com.jeesite.modules.ai.tools;
|
||||||
|
|
||||||
|
import com.jeesite.common.lang.DateUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.ai.tool.annotation.Tool;
|
||||||
|
import org.springframework.ai.tool.annotation.ToolParam;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI MCP 工具调用
|
||||||
|
* @author ThinkGem
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class TestAiTools {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(TestAiTools.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器时间
|
||||||
|
*/
|
||||||
|
@Tool(name="获取服务器时间", description = "获取当前的日期和时间,格式为 yyyy-MM-dd HH:mm:ss。")
|
||||||
|
public String getCurrentDateTime() {
|
||||||
|
String dateTime = "当前日期时间:" + DateUtils.getDateTime();
|
||||||
|
logger.info("当前日期时间 ============== {}", dateTime);
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开关房间的灯
|
||||||
|
*/
|
||||||
|
@Tool(
|
||||||
|
name = "房间灯光开关",
|
||||||
|
description = "控制指定房间的灯光开关。需要提供房间名称(如 '客厅'、'卧室')和目标状态(true 表示开灯,false 表示关灯)。"
|
||||||
|
)
|
||||||
|
public String roomLightSwitch(
|
||||||
|
@ToolParam(description = "要控制的房间名称,例如:'客厅'、'卧室'、'餐厅'、'厨房'") String roomName,
|
||||||
|
@ToolParam(description = "灯光目标状态:true 表示打开灯,false 表示关闭灯") boolean on) {
|
||||||
|
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
|
||||||
|
logger.info("房间灯光开关 ============== {}", message);
|
||||||
|
return String.format("""
|
||||||
|
{
|
||||||
|
"message": "%s",
|
||||||
|
"roomName": "%s",
|
||||||
|
"on": %s
|
||||||
|
}
|
||||||
|
""", message, roomName, on);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
* No deletion without permission, or be held responsible to law.
|
||||||
|
*/
|
||||||
|
package com.jeesite.modules.ai.tools;
|
||||||
|
|
||||||
|
import com.jeesite.common.lang.StringUtils;
|
||||||
|
import com.jeesite.common.mapper.JsonMapper;
|
||||||
|
import com.jeesite.common.mybatis.mapper.query.QueryType;
|
||||||
|
import com.jeesite.modules.sys.entity.EmpUser;
|
||||||
|
import com.jeesite.modules.sys.entity.User;
|
||||||
|
import com.jeesite.modules.sys.service.EmpUserService;
|
||||||
|
import com.jeesite.modules.sys.utils.UserUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.ai.chat.model.ToolContext;
|
||||||
|
import org.springframework.ai.tool.annotation.Tool;
|
||||||
|
import org.springframework.ai.tool.annotation.ToolParam;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI MCP 工具调用
|
||||||
|
* @author ThinkGem
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class UserAITools {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(UserAITools.class);
|
||||||
|
|
||||||
|
private final EmpUserService empUserService;
|
||||||
|
|
||||||
|
public UserAITools(EmpUserService empUserService) {
|
||||||
|
this.empUserService = empUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前会话的用户信息
|
||||||
|
*/
|
||||||
|
@Tool(name="当前用户信息", description = "无条件获取当前用户信息")
|
||||||
|
public String getCurrentUser(ToolContext toolContext) {
|
||||||
|
User currentUser = UserUtils.getUser();
|
||||||
|
if (StringUtils.isBlank(currentUser.getUserCode())) {
|
||||||
|
logger.info("当前用户信息 ============== 当前用户未登录。");
|
||||||
|
return "当前用户未登录。";
|
||||||
|
}
|
||||||
|
String result = JsonMapper.toJson(currentUser);
|
||||||
|
logger.info("当前用户信息 ============== 查询结果:{}", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询用户信息
|
||||||
|
*/
|
||||||
|
@Tool(name="查询用户信息", description = "根据用户名(登录账号)或员工姓名模糊查询用户信息。" +
|
||||||
|
"结果以表格形式展示,包含用户名userName、姓名empUser、部门officeName等基本信息。")
|
||||||
|
public String findEmpUserInfo(ToolContext toolContext,
|
||||||
|
@ToolParam(description = "用户的登录名或员工的真实姓名,支持模糊匹配") String userName
|
||||||
|
) {
|
||||||
|
EmpUser where = new EmpUser();
|
||||||
|
where.sqlMap().getWhere().and(w -> w
|
||||||
|
.or("a.user_name", QueryType.LIKE, userName)
|
||||||
|
.or("e.emp_name", QueryType.LIKE, userName));
|
||||||
|
// 权限控制,只能查询当前用户能查询的用户信息
|
||||||
|
logger.info("获取用户信息 ============== 当前用户: {}", where.currentUser().getUserCode());
|
||||||
|
empUserService.addDataScopeFilter(where);
|
||||||
|
List<EmpUser> list = empUserService.findList(where);
|
||||||
|
String result = JsonMapper.toJson(list);
|
||||||
|
logger.info("获取用户信息 ============== 查询结果: {}", result);
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return "未找到符合条件的用户信息。";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
||||||
|
* No deletion without permission, or be held responsible to law.
|
||||||
|
*/
|
||||||
|
package com.jeesite.modules.ai.tools.aspect;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.springframework.ai.chat.model.ToolContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool 上下文用户信息注入切面
|
||||||
|
* @author ThinkGem
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class ToolContextAspect {
|
||||||
|
|
||||||
|
@Around("@annotation(org.springframework.ai.tool.annotation.Tool)")
|
||||||
|
public Object handleThreadContext(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
|
Object[] args = joinPoint.getArgs();
|
||||||
|
ToolContext toolContext = null;
|
||||||
|
for (Object arg : args) {
|
||||||
|
if (arg instanceof ToolContext) {
|
||||||
|
toolContext = (ToolContext) arg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toolContext != null) {
|
||||||
|
Map<String, Object> context = toolContext.getContext();
|
||||||
|
if (context.containsKey("subject")) {
|
||||||
|
ThreadContext.bind((Subject) context.get("subject"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return joinPoint.proceed();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
|
|
||||||
* No deletion without permission, or be held responsible to law.
|
|
||||||
*/
|
|
||||||
package com.jeesite.modules.cms.ai.tools;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.ai.tool.annotation.Tool;
|
|
||||||
import org.springframework.ai.tool.annotation.ToolParam;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI 工具调用、Tool calling(需选择支持 Tools 的模型)
|
|
||||||
* @author ThinkGem
|
|
||||||
*/
|
|
||||||
public class CmsAiTools {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(CmsAiTools.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 未联网搜索的时候,可获取到服务器时间
|
|
||||||
*/
|
|
||||||
@Tool(description = "当前时间,当前日期,几点了")
|
|
||||||
public String getCurrentDateTime() {
|
|
||||||
String dateTime = "当前日期时间:" + LocalDateTime.now();
|
|
||||||
logger.info(dateTime + " ============== ");
|
|
||||||
return dateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 你可以询问:打开客厅的灯,关闭卧室的灯(需创建新对话)
|
|
||||||
*/
|
|
||||||
@Tool(description = "房间里的灯打开或关闭")
|
|
||||||
public String turnLight(@ToolParam(description = "房间") String roomName, @ToolParam(description = "开关") boolean on) {
|
|
||||||
String message = roomName + " 房间里的灯被 " + (on ? "打开" : "关闭");
|
|
||||||
logger.info(message + " ============== ");
|
|
||||||
return String.format("""
|
|
||||||
message: %s
|
|
||||||
roomName: %s
|
|
||||||
on: %s
|
|
||||||
""",
|
|
||||||
roomName, on, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user