mirror of https://github.com/jeecgboot/jeecg-boot
添加McpServer和functionCall的示例
parent
47107a53c2
commit
3d4e6ba940
|
@ -188,6 +188,9 @@ public class SecurityConfig {
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/openapi/call/**")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/openapi/call/**")).permitAll()
|
||||||
// APP版本信息
|
// APP版本信息
|
||||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/version/app3version")).permitAll()
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/version/app3version")).permitAll()
|
||||||
|
// mcp接口
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/sse")).permitAll()
|
||||||
|
.requestMatchers(AntPathRequestMatcher.antMatcher("/mcp/message")).permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?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>org.jeecgframework.boot</groupId>
|
||||||
|
<artifactId>jeecg-boot-parent</artifactId>
|
||||||
|
<version>3.8.2</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>jeecg-module-mcp-server</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- SYSTEM 系统管理模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
|
<artifactId>jeecg-system-biz</artifactId>
|
||||||
|
<version>${jeecgboot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- MCP-Server -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,130 @@
|
||||||
|
package org.jeecg.modules.mcp;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||||
|
import dev.langchain4j.mcp.McpToolProvider;
|
||||||
|
import dev.langchain4j.mcp.client.DefaultMcpClient;
|
||||||
|
import dev.langchain4j.mcp.client.McpClient;
|
||||||
|
import dev.langchain4j.mcp.client.transport.McpTransport;
|
||||||
|
import dev.langchain4j.mcp.client.transport.http.HttpMcpTransport;
|
||||||
|
import dev.langchain4j.model.chat.ChatModel;
|
||||||
|
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
|
||||||
|
import dev.langchain4j.model.chat.request.json.JsonStringSchema;
|
||||||
|
import dev.langchain4j.service.AiServices;
|
||||||
|
import dev.langchain4j.service.tool.ToolExecutor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.ai.assistant.AiChatAssistant;
|
||||||
|
import org.jeecg.ai.factory.AiModelFactory;
|
||||||
|
import org.jeecg.ai.factory.AiModelOptions;
|
||||||
|
import org.jeecg.common.api.vo.Result;
|
||||||
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description: llm工具调用测试接口
|
||||||
|
* @Author: chenrui
|
||||||
|
* @Date: 2025/8/22 14:13
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/ai/tools/test")
|
||||||
|
@Slf4j
|
||||||
|
public class LlmToolsTestController {
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ISysBaseAPI sysBaseAPI;
|
||||||
|
|
||||||
|
// 根据环境构建模型配置;缺少关键项则返回 null 以便测试跳过
|
||||||
|
private static AiModelOptions buildModelOptionsFromEnv() {
|
||||||
|
String baseUrl = "https://api.gpt.ge";
|
||||||
|
String apiKey = "sk-ZLhvUUGPGyERkPya632f3f18209946F7A51d4479081a3dFb";
|
||||||
|
String modelName = "gpt-4.1-mini";
|
||||||
|
return AiModelOptions.builder()
|
||||||
|
.provider(AiModelFactory.AIMODEL_TYPE_OPENAI)
|
||||||
|
.modelName(modelName)
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.apiKey(apiKey)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(value = "/queryUser")
|
||||||
|
public Result<?> queryUser(@RequestParam(value = "prompt", required = true) String prompt) {
|
||||||
|
AiModelOptions modelOp = buildModelOptionsFromEnv();
|
||||||
|
ToolSpecification toolSpecification = ToolSpecification.builder()
|
||||||
|
.name("query_user_by_name")
|
||||||
|
.description("通过通过用户名查询用户信息,返回用户信息列表")
|
||||||
|
.parameters(JsonObjectSchema.builder()
|
||||||
|
.addProperties(Map.of(
|
||||||
|
"username", JsonStringSchema.builder()
|
||||||
|
.description("用户名,多个可以使用逗号分隔")
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
|
||||||
|
Map<String, Object> arguments = JSONObject.parseObject(toolExecutionRequest.arguments());
|
||||||
|
String username = arguments.get("username").toString();
|
||||||
|
List<JSONObject> users = sysBaseAPI.queryUsersByUsernames(username);
|
||||||
|
return JSONObject.toJSONString(users);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建同步聊天模型并通过 AiChatAssistant 发起一次非流式对话
|
||||||
|
ChatModel chatModel = AiModelFactory.createChatModel(modelOp);
|
||||||
|
AiChatAssistant bot = AiServices.builder(AiChatAssistant.class)
|
||||||
|
.chatModel(chatModel)
|
||||||
|
.tools(Map.of(toolSpecification, toolExecutor))
|
||||||
|
.tools()
|
||||||
|
.build();
|
||||||
|
String chat = bot.chat(prompt);
|
||||||
|
log.info("聊天回复: " + chat);
|
||||||
|
return Result.OK(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据环境构建 MCP 工具提供者;缺少 URL 则返回 null 以便测试跳过
|
||||||
|
private static McpToolProvider buildMcpTool(String sseUrl) {
|
||||||
|
McpTransport transport = new HttpMcpTransport.Builder()
|
||||||
|
.sseUrl(sseUrl)
|
||||||
|
.logRequests(true)
|
||||||
|
.logResponses(true)
|
||||||
|
.build();
|
||||||
|
McpClient mcpClient = new DefaultMcpClient.Builder()
|
||||||
|
.transport(transport)
|
||||||
|
.build();
|
||||||
|
return McpToolProvider.builder()
|
||||||
|
.mcpClients(List.of(mcpClient))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试JeecgMcp
|
||||||
|
* @author chenrui
|
||||||
|
* @date 2025/8/22 10:10
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/queryUserByMcp")
|
||||||
|
public Result<?> testJeecgToolProvider(@RequestParam(value = "prompt", required = true) String prompt) {
|
||||||
|
// prompt = "查询一下有没有用户名是admin的用户信息";
|
||||||
|
// prompt = "新建一个顶级部门,部门名称是:测试部门,机构类别是公司";
|
||||||
|
AiModelOptions modelOp = buildModelOptionsFromEnv();
|
||||||
|
McpToolProvider toolProvider = buildMcpTool("http://localhost:8080/jeecgboot/sse");
|
||||||
|
|
||||||
|
// 构建同步聊天模型并通过 AiChatAssistant 发起一次非流式对话
|
||||||
|
ChatModel chatModel = AiModelFactory.createChatModel(modelOp);
|
||||||
|
AiChatAssistant bot = AiServices.builder(AiChatAssistant.class)
|
||||||
|
.chatModel(chatModel)
|
||||||
|
.toolProvider(toolProvider)
|
||||||
|
.build();
|
||||||
|
String chat = bot.chat(prompt);
|
||||||
|
log.info("聊天回复: " + chat);
|
||||||
|
return Result.OK(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.jeecg.modules.mcp;
|
||||||
|
|
||||||
|
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||||
|
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description:
|
||||||
|
* @Author: chenrui
|
||||||
|
* @Date: 2025/8/21 17:19
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class McpConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ToolCallbackProvider weatherTools(UserMcpTool userMcpTool) {
|
||||||
|
return MethodToolCallbackProvider.builder().toolObjects(userMcpTool).build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.jeecg.modules.mcp;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||||
|
import org.jeecg.modules.system.entity.SysDepart;
|
||||||
|
import org.jeecg.modules.system.service.ISysDepartService;
|
||||||
|
import org.springframework.ai.tool.annotation.Tool;
|
||||||
|
import org.springframework.ai.tool.annotation.ToolParam;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description:
|
||||||
|
* @Author: chenrui
|
||||||
|
* @Date: 2025/8/21 17:10
|
||||||
|
*/
|
||||||
|
@Service("userMcpService")
|
||||||
|
public class UserMcpTool {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ISysBaseAPI sysBaseAPI;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ISysDepartService sysDepartmentService;
|
||||||
|
|
||||||
|
@Tool(name = "query_user_by_name", description = "通过通过用户名查询用户信息,返回用户信息列表")
|
||||||
|
public String queryUserByUsername(@ToolParam(description = "用户名,多个可以使用逗号分隔", required = true) String username) {
|
||||||
|
if (username == null || username.isEmpty()) {
|
||||||
|
return "Username cannot be null or empty";
|
||||||
|
}
|
||||||
|
List<JSONObject> users = sysBaseAPI.queryUsersByUsernames(username);
|
||||||
|
return JSONObject.toJSONString(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Tool(name = "add_depart", description = "新增部门信息,返回操作结果")
|
||||||
|
public String saveDepartData(@ToolParam(description = "部门信息,departName(部门名称,必填),orgCategory(机构类别,必填, 1=公司,2=组织机构,3=岗位),parentId(父部门:父级部门的id,非必填)", required = true) SysDepart sysDepart){
|
||||||
|
try {
|
||||||
|
sysDepartmentService.saveDepartData(sysDepart, "mcpService");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "创建部门失败,原因:"+e.getMessage();
|
||||||
|
}
|
||||||
|
return "创建部门成功";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,12 @@
|
||||||
<artifactId>jeecg-module-demo</artifactId>
|
<artifactId>jeecg-module-demo</artifactId>
|
||||||
<version>${jeecgboot.version}</version>
|
<version>${jeecgboot.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
|
<artifactId>jeecg-module-mcp-server</artifactId>
|
||||||
|
<version>${jeecgboot.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- flyway 数据库自动升级
|
<!-- flyway 数据库自动升级
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -25,6 +25,12 @@ management:
|
||||||
include: metrics,httpexchanges,jeecghttptrace
|
include: metrics,httpexchanges,jeecghttptrace
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
|
ai:
|
||||||
|
mcp:
|
||||||
|
server:
|
||||||
|
name: jeecg-mcp-server
|
||||||
|
version: 1.0.0
|
||||||
|
base-url: /jeecgboot
|
||||||
flyway:
|
flyway:
|
||||||
# 是否启用flyway
|
# 是否启用flyway
|
||||||
enabled: true
|
enabled: true
|
||||||
|
@ -151,7 +157,7 @@ spring:
|
||||||
slow-sql-millis: 5000
|
slow-sql-millis: 5000
|
||||||
datasource:
|
datasource:
|
||||||
master:
|
master:
|
||||||
url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
|
url: jdbc:mysql://jeecg-boot-mysql:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
|
||||||
username: root
|
username: root
|
||||||
password: root
|
password: root
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
@ -165,7 +171,7 @@ spring:
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
database: 0
|
database: 0
|
||||||
host: 127.0.0.1
|
host: jeecg-boot-redis
|
||||||
port: 6379
|
port: 6379
|
||||||
password:
|
password:
|
||||||
#mybatis plus 设置
|
#mybatis plus 设置
|
||||||
|
|
|
@ -167,6 +167,14 @@
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- spring-ai -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-bom</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- system 模块-->
|
<!-- system 模块-->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -481,7 +489,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.boot</groupId>
|
<groupId>org.jeecgframework.boot</groupId>
|
||||||
<artifactId>jeecg-boot-starter3-chatgpt</artifactId>
|
<artifactId>jeecg-boot-starter3-chatgpt</artifactId>
|
||||||
<version>${jeecgboot.version}</version>
|
<version>3.8.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
|
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
|
||||||
<!--mysql5.6,需要把版本号改成5.2.1-->
|
<!--mysql5.6,需要把版本号改成5.2.1-->
|
||||||
|
|
Loading…
Reference in New Issue