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()
|
||||
// APP版本信息
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sys/version/app3version")).permitAll()
|
||||
// mcp接口
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/sse")).permitAll()
|
||||
.requestMatchers(AntPathRequestMatcher.antMatcher("/mcp/message")).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.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 "创建部门成功";
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,12 @@
|
|||
<version>${jeecgboot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-module-mcp-server</artifactId>
|
||||
<version>${jeecgboot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- flyway 数据库自动升级
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
|
|
|
@ -25,6 +25,12 @@ management:
|
|||
include: metrics,httpexchanges,jeecghttptrace
|
||||
|
||||
spring:
|
||||
ai:
|
||||
mcp:
|
||||
server:
|
||||
name: jeecg-mcp-server
|
||||
version: 1.0.0
|
||||
base-url: /jeecgboot
|
||||
flyway:
|
||||
# 是否启用flyway
|
||||
enabled: true
|
||||
|
@ -151,7 +157,7 @@ spring:
|
|||
slow-sql-millis: 5000
|
||||
datasource:
|
||||
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
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
@ -165,7 +171,7 @@ spring:
|
|||
data:
|
||||
redis:
|
||||
database: 0
|
||||
host: 127.0.0.1
|
||||
host: jeecg-boot-redis
|
||||
port: 6379
|
||||
password:
|
||||
#mybatis plus 设置
|
||||
|
|
|
@ -167,6 +167,14 @@
|
|||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</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 模块-->
|
||||
<dependency>
|
||||
|
@ -481,7 +489,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-starter3-chatgpt</artifactId>
|
||||
<version>${jeecgboot.version}</version>
|
||||
<version>3.8.3</version>
|
||||
</dependency>
|
||||
<!--flyway 支持 mysql5.7+、MariaDB10.3.16-->
|
||||
<!--mysql5.6,需要把版本号改成5.2.1-->
|
||||
|
|
Loading…
Reference in New Issue