概述
Plugin系统是LangChat Pro的扩展机制,允许开发者通过插件方式为AI助手添加各种工具能力,如时间查询、网络搜索、新闻获取等。设计模式
策略模式(Strategy Pattern)
Plugin系统使用策略模式来:- 统一的工具接口
- 灵活的工具注册机制
- 动态工具注入
- 热插拔支持
核心组件
DynamicToolFactory
路径:langchat-common/langchat-common-ai/src/main/java/cn/langchat/common/ai/component/DynamicToolFactory.java
接口定义:
Copy
/**
* 动态工具工厂接口
*/
public interface DynamicToolFactory {
/**
* 动态构建工具并添加到AiServices Builder
*
* @param req 聊天请求
* @param aiServices AiServices Builder
*/
void dynamicTools(LcChatReq req, AiServices<LangChatAgent> aiServices);
}
PluginToolFactory- 插件工具工厂McpToolFactory- MCP工具工厂- 自定义工具工厂
PluginToolFactory
职责
根据用户配置动态加载和注册插件工具实现流程
Copy
@Component
public class PluginToolFactory implements DynamicToolFactory {
@Resource
private AigcPluginService pluginService;
@Override
public void dynamicTools(LcChatReq req, AiServices<LangChatAgent> aiServices) {
log.debug("开始构建插件工具: userId={}, appId={}",
req.getUserId(), req.getAppId());
// 1. 获取启用的插件
var plugins = pluginService.getEnabledPlugins(
req.getUserId(),
req.getAppId()
);
if (CollUtil.isEmpty(plugins)) {
log.debug("未启用插件: userId={}, appId={}",
req.getUserId(), req.getAppId());
return;
}
// 2. 构建工具列表
var tools = plugins.stream()
.map(this::convertToTool)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollUtil.isEmpty(tools)) {
log.debug("插件工具构建为空: userId={}, appId={}",
req.getUserId(), req.getAppId());
return;
}
// 3. 添加到AiServices
aiServices.tools(tools);
log.info("插件工具构建完成: userId={}, appId={}, toolCount={}",
req.getUserId(), req.getAppId(), tools.size());
}
/**
* 将插件转换为LangChain4j Tool
*/
private Tool convertToTool(AigcPlugin plugin) {
return Tool.builder()
.name(plugin.getName())
.description(plugin.getDescription())
.addParameter("input",
JsonSchemaProperty.string()
.description("插件输入参数")
.build())
.execute((toolExecutionRequest) -> {
try {
return executePlugin(plugin, toolExecutionRequest);
} catch (Exception e) {
log.error("插件执行失败: pluginName={}", plugin.getName(), e);
return "插件执行失败: " + e.getMessage();
}
})
.build();
}
/**
* 执行插件
*/
private String executePlugin(
AigcPlugin plugin,
ToolExecutionRequest request
) {
String input = request.argumentsAsString();
log.info("执行插件: pluginName={}, input={}", plugin.getName(), input);
// 根据插件类型执行不同的逻辑
return switch (plugin.getType()) {
case "time" -> executeTimePlugin(input);
case "search" -> executeSearchPlugin(input);
case "news" -> executeNewsPlugin(input);
default -> executeCustomPlugin(plugin, input);
};
}
}
内置插件
1. 时间工具插件
路径:langchat-plugin/langchat-plugin-tool-time/
功能: 提供时间、日期相关工具
实现示例:
Copy
@Component
public class TimePlugin {
/**
* 获取当前时间
*/
public String getCurrentTime() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
/**
* 获取当前日期
*/
public String getCurrentDate() {
return LocalDate.now().toString();
}
/**
* 计算日期差
*/
public String calculateDateDiff(String startDate, String endDate) {
LocalDate start = LocalDate.parse(startDate);
LocalDate end = LocalDate.parse(endDate);
long days = ChronoUnit.DAYS.between(start, end);
return String.format("日期差: %d天", Math.abs(days));
}
}
Copy
Tool.builder()
.name("get_current_time")
.description("获取当前时间")
.addParameter("timezone",
JsonSchemaProperty.string()
.description("时区,如:Asia/Shanghai")
.required(false)
.build())
.execute(request -> getCurrentTime(request))
.build();
2. 百度搜索插件
路径:langchat-plugin/langchat-plugin-tool-baidusearch/
功能: 提供网络搜索能力
实现示例:
Copy
@Component
public class BaiduSearchPlugin {
@Value("${plugin.baidu.search.api-key}")
private String apiKey;
/**
* 执行搜索
*/
public String search(String query, int limit) {
// 调用百度搜索API
String url = String.format(
"https://api.baidu.com/api/v1/search?q=%s&limit=%d",
URLEncoder.encode(query, StandardCharsets.UTF_8),
limit
);
// 发送HTTP请求
// 返回搜索结果
}
/**
* 获取搜索摘要
*/
public String getSearchSummary(String query) {
List<SearchResult> results = search(query, 5);
StringBuilder summary = new StringBuilder();
summary.append("搜索结果:\n");
for (SearchResult result : results) {
summary.append(String.format("- %s\n", result.getTitle()));
summary.append(String.format(" %s\n\n", result.getSnippet()));
}
return summary.toString();
}
}
Copy
Tool.builder()
.name("web_search")
.description("网络搜索,获取最新信息")
.addParameter("query",
JsonSchemaProperty.string()
.description("搜索关键词")
.required(true)
.build())
.addParameter("limit",
JsonSchemaProperty.integer()
.description("返回结果数量,默认5")
.required(false)
.build())
.execute(request -> executeSearch(request))
.build();
3. 头条新闻插件
路径:langchat-plugin/langchat-plugin-tool-toutiaonews/
功能: 获取最新新闻资讯
实现示例:
Copy
@Component
public class ToutiaoNewsPlugin {
/**
* 获取最新新闻
*/
public String getLatestNews(String category, int limit) {
// 调用头条新闻API
// 返回新闻列表
}
/**
* 获取新闻详情
*/
public String getNewsDetail(String newsId) {
// 获取新闻详细内容
}
}
Copy
Tool.builder()
.name("get_latest_news")
.description("获取最新新闻")
.addParameter("category",
JsonSchemaProperty.string()
.description("新闻分类:科技、财经、娱乐等")
.required(false)
.build())
.addParameter("limit",
JsonSchemaProperty.integer()
.description("返回新闻数量,默认5")
.required(false)
.build())
.execute(request -> executeGetNews(request))
.build();
插件配置
AigcPlugin
Copy
public class AigcPlugin {
private String id;
private String name; // 插件名称
private String type; // 插件类型
private String description; // 插件描述
private String config; // 插件配置(JSON)
private Boolean enabled; // 是否启用
private Integer priority; // 优先级
}
插件配置示例
Copy
{
"id": "plugin-001",
"name": "time_plugin",
"type": "time",
"description": "时间工具插件",
"config": {
"timezone": "Asia/Shanghai",
"dateFormat": "yyyy-MM-dd HH:mm:ss"
},
"enabled": true,
"priority": 10
}
插件开发指南
步骤1: 创建插件模块
Copy
<project>
<parent>
<groupId>cn.langchat</groupId>
<artifactId>langchat-plugin</artifactId>
<version>2.3.1</version>
</parent>
<artifactId>langchat-plugin-tool-yourplugin</artifactId>
<dependencies>
<dependency>
<groupId>cn.langchat</groupId>
<artifactId>langchat-common-ai</artifactId>
</dependency>
</dependencies>
</project>
步骤2: 实现插件逻辑
Copy
@Component
public class YourPlugin {
/**
* 插件方法1
*/
public String method1(String input) {
// 实现逻辑
}
/**
* 插件方法2
*/
public String method2(String param1, String param2) {
// 实现逻辑
}
}
步骤3: 创建工具定义
Copy
@Component
public class YourPluginToolFactory implements DynamicToolFactory {
@Resource
private YourPlugin yourPlugin;
@Override
public void dynamicTools(LcChatReq req, AiServices<LangChatAgent> aiServices) {
// 判断是否启用该插件
if (!isPluginEnabled(req, "your_plugin")) {
return;
}
// 构建工具列表
List<Tool> tools = List.of(
buildTool1(),
buildTool2()
);
// 添加到AiServices
aiServices.tools(tools);
}
private Tool buildTool1() {
return Tool.builder()
.name("method1")
.description("方法1描述")
.addParameter("input",
JsonSchemaProperty.string()
.description("输入参数")
.required(true)
.build())
.execute(request -> {
String input = request.argumentsAsString();
return yourPlugin.method1(input);
})
.build();
}
private Tool buildTool2() {
return Tool.builder()
.name("method2")
.description("方法2描述")
.addParameter("param1",
JsonSchemaProperty.string()
.description("参数1")
.required(true)
.build())
.addParameter("param2",
JsonSchemaProperty.string()
.description("参数2")
.required(false)
.build())
.execute(request -> {
Map<String, Object> args = request.argumentsAsMap();
return yourPlugin.method2(
(String) args.get("param1"),
(String) args.get("param2")
);
})
.build();
}
private boolean isPluginEnabled(LcChatReq req, String pluginName) {
// 检查插件是否启用
// 可以从数据库或配置中获取
return true;
}
}
步骤4: 注册插件
使用@Component注解,Spring会自动扫描并注册
步骤5: 配置插件
在数据库中添加插件配置:Copy
INSERT INTO aigc_plugin (id, name, type, description, enabled)
VALUES (
'plugin-your-001',
'your_plugin',
'custom',
'你的自定义插件',
1
);
工具调用流程
1. AI决定调用工具
Copy
用户问题:"今天是什么天气?"
↓
LLM分析问题
↓
识别需要调用工具: "get_weather"
↓
准备工具调用请求
2. 工具执行
Copy
AiServices执行工具
↓
调用对应的Tool
↓
执行插件方法
↓
获取结果
3. 结果返回
Copy
工具执行结果
↓
返回给LLM
↓
LLM根据工具结果生成最终回答
↓
返回给用户
配置说明
application.yml
Copy
langchat:
plugin:
enabled: true # 是否启用插件系统
timeout: 30 # 插件执行超时时间(秒)
max-retries: 3 # 最大重试次数
cache-enabled: true # 是否启用结果缓存
cache-ttl: 3600 # 缓存时间(秒)
plugin:
baidu:
search:
api-key: your-api-key
time:
timezone: Asia/Shanghai
news:
toutiao:
api-key: your-api-key
最佳实践
1. 工具命名
- 使用小写字母和下划线
- 名称要清晰描述功能
- 示例:
get_weather,web_search,calculate_distance
2. 描述编写
- 简洁明了
- 说明工具的功能
- 包含使用场景
- 示例: “获取指定城市的当前天气信息”
3. 参数定义
- 明确参数类型
- 标注必需参数
- 提供参数说明
- 合理设置默认值
4. 错误处理
- 捕获所有异常
- 返回友好的错误信息
- 记录详细日志
- 考虑重试机制
5. 性能优化
- 缓存插件结果
- 使用异步执行
- 设置超时时间
- 避免阻塞操作
6. 安全考虑
- 验证输入参数
- 限制调用频率
- 过滤敏感操作
- 记录调用日志
扩展示例
HTTP请求插件
Copy
@Component
public class HttpPlugin {
@Resource
private RestTemplate restTemplate;
public String httpRequest(String url, String method, String body) {
// 执行HTTP请求
HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
HttpEntity<String> entity = new HttpEntity<>(body);
ResponseEntity<String> response = restTemplate.exchange(
url, httpMethod, entity, String.class
);
return response.getBody();
}
}
Copy
Tool.builder()
.name("http_request")
.description("执行HTTP请求")
.addParameter("url",
JsonSchemaProperty.string()
.description("请求URL")
.required(true)
.build())
.addParameter("method",
JsonSchemaProperty.string()
.description("请求方法:GET, POST, PUT, DELETE")
.required(true)
.build())
.addParameter("body",
JsonSchemaProperty.string()
.description("请求体(POST/PUT使用)")
.required(false)
.build())
.execute(request -> executeHttpRequest(request))
.build();
数据库查询插件
Copy
@Component
public class DatabasePlugin {
@Resource
private JdbcTemplate jdbcTemplate;
public String executeQuery(String sql) {
List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
return JsonUtil.toJson(results);
}
}
Copy
Tool.builder()
.name("execute_query")
.description("执行SQL查询")
.addParameter("sql",
JsonSchemaProperty.string()
.description("SQL查询语句")
.required(true)
.build())
.execute(request -> {
String sql = request.argumentsAsString();
// 安全检查,限制为SELECT语句
if (!sql.trim().toUpperCase().startsWith("SELECT")) {
throw new SecurityException("只允许SELECT语句");
}
return databasePlugin.executeQuery(sql);
})
.build();
性能优化
1. 插件缓存
Copy
@Component
public class CachedPlugin {
@Cacheable(value = "plugin", key = "#input", unless = "#result == null")
public String executeWithCache(String input) {
// 插件逻辑
}
}
2. 异步执行
Copy
@Component
public class AsyncPlugin {
@Async
public CompletableFuture<String> executeAsync(String input) {
// 异步执行
}
}
3. 批量处理
Copy
@Component
public class BatchPlugin {
public List<String> executeBatch(List<String> inputs) {
// 批量处理
}
}

