工具开发
本文档介绍如何开发业务工具,让 LLM 能够调用后端业务能力。
基本结构
工具类示例
java
@Slf4j
@Component
@RequiredArgsConstructor
public class XiuxianUserTools extends BaseToolSupport {
private final XiuxianUserService userService;
@Tool(name = "query_user_basic", description = "查询修士的基本信息,包括境界、灵根、资质等")
public String queryUserBasic(@ToolParam(description = "修士的道号或姓名") String userName) {
if (!StringUtils.hasText(userName)) {
return error("请提供修士的道号或姓名");
}
notifyProgress("正在查阅【%s】的基本信息...", userName);
XiuxianUserEntity user = userService.fuzzyGetByName(userName);
if (user == null) {
return error("未找到修士【" + bold(userName) + "】");
}
notifyProgress("成功查阅到【%s】的基本信息", user.getName());
return render(USER_INFO_TEMPLATE, buildUserParams(user));
}
}继承 BaseToolSupport
| 方法 | 说明 |
|---|---|
render(template, params) | Velocity 模板渲染 |
notifyProgress(message) | 发送进度通知 |
error(message) | 返回错误响应 |
success(message) | 返回成功响应 |
executePageQuery(...) | 分页查询模板 |
batchQuery(...) | 批量查询模板 |
注解说明
@Tool
标记方法为可调用工具:
java
@Tool(
name = "query_user_basic", // 工具名称(唯一标识)
description = "查询修士的基本信息" // 工具描述(LLM 理解用途)
)@ToolParam
标记方法参数:
java
@ToolParam(
description = "修士的道号或姓名", // 参数描述
required = true // 是否必填(默认 true)
)
String userName@RequiresRole
权限控制注解:
java
@RequiresRole(value = XiuxianRole.PEAK_MASTER, message = "需要峰主及以上权限")
public String queryUserDetail(String userName) {
// ...
}参数类型
基本类型
java
@Tool(description = "更新修士状态")
public String updateUserStatus(
@ToolParam(description = "修士道号") String userName,
@ToolParam(description = "新状态:1-正常,2-闭关") Integer status,
@ToolParam(description = "原因", required = false) String reason
) {
// ...
}DTO 类型
复杂参数使用 DTO:
java
@Tool(description = "分页查询修士列表")
public String pageUsers(@ToolParam(description = "查询条件") PageUserDto query) {
// ...
}
@Data
public class PageUserDto {
private String name;
private String realm;
private Integer pageNum;
private Integer pageSize;
// ...
}枚举类型
java
@Tool(description = "按状态查询任务")
public String queryTasksByStatus(
@ToolParam(description = "任务状态:PENDING/IN_PROGRESS/COMPLETED") TaskStatus status
) {
// ...
}输出格式
模板定义
使用 Velocity 模板定义输出格式:
java
private static final String USER_INFO = preprocess("""
### 【${name}】修士信息
| 属性 | 值 |
|------|------|
| 境界 | ${realm} |
| 灵根类型 | ${spiritRootType} |
| 灵根属性 | ${spiritRootAttr} |
| 资质 | ${aptitude} |
| 状态 | ${statusName} |
""");模板渲染
java
private Map<String, Object> buildUserParams(XiuxianUserEntity user) {
Map<String, Object> params = new HashMap<>();
params.put("name", user.getName());
params.put("realm", formatRealm(user.getRealm(), user.getRealmLevel()));
params.put("spiritRootType", nv(user.getSpiritRootType()));
params.put("spiritRootAttr", nv(user.getSpiritRootAttr()));
params.put("aptitude", nv(user.getAptitude()));
params.put("statusName", getUserStatusName(user.getStatus()));
return params;
}
return render(USER_INFO, buildUserParams(user));条件渲染
velocity
#if($showAssets)
| 灵石余额 | ${spiritStones} |
| 贡献点 | ${contributionPoints} |
#end循环渲染
velocity
#foreach($row in $rows)
| ${row.name} | ${row.realm} | ${row.statusName} |
#end进度通知
发送进度
java
notifyProgress("正在查阅【%s】的基本信息...", userName);
// 执行查询...
notifyProgress("成功查阅到【%s】的基本信息", user.getName());前端展示
AI: 正在思考...
正在查阅【张三】的基本信息...
成功查阅到【张三】的基本信息
### 【张三】修士信息
...分页查询
使用模板方法
java
@Tool(description = "分页查询修士列表")
public String pageUsers(@ToolParam(description = "查询条件") PageUserDto query) {
// 1. 构建查询条件
LambdaQueryWrapper<XiuxianUserEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(XiuxianUserEntity::getStatus, CommonStatus.NORMAL.getCode());
if (StringUtils.hasText(query.getName())) {
wrapper.like(XiuxianUserEntity::getName, query.getName());
}
// 2. 执行分页查询
PageResult<Map<String, Object>> result = executePageQuery(
userService,
wrapper,
query.getPageNum(),
query.getPageSize(),
this::buildUserRowParams // 行转换函数
);
// 3. 渲染结果
return renderPageResult(result, USER_LIST_TEMPLATE);
}分页模板
java
private static final String USER_LIST = preprocess("""
## 查询结果
**共找到 ${total} 条记录**(第${pageNum}页,每页${pageSize}条)
| 道号 | 境界 | 灵根类型 | 状态 |
|------|------|------|------|
#foreach($row in $rows)
| ${row.name} | ${row.realm} | ${row.spiritRootType} | ${row.statusName} |
#end
""");批量查询
使用模板方法
java
@Tool(description = "批量查询修士信息")
public String batchQueryUsers(
@ToolParam(description = "道号列表,逗号分隔") String names
) {
return batchQuery(
names,
name -> userService.lambdaQuery()
.like(XiuxianUserEntity::getName, name)
.list(),
this::buildUserRowParams,
"修士",
USER_LIST_TEMPLATE
);
}权限控制
注解方式
java
@Tool(description = "查询修士详细信息(含敏感信息)")
@RequiresRole(value = XiuxianRole.PEAK_MASTER, message = "查看他人敏感信息需要峰主及以上权限")
public String queryUserDetail(String userName) {
// ...
}代码方式
java
@Tool(description = "调整修士灵石")
@RequiresRole(XiuxianRole.PEAK_MASTER)
public String adjustSpiritStones(String userName, Long amount) {
XiuxianUserEntity user = resolveUserByName(userName);
// 峰主只能操作本峰弟子
String permissionError = checkPeakPermission(user, "调整灵石");
if (permissionError != null) {
return error(permissionError);
}
// 执行调整...
}权限检查方法
BaseToolSupport 提供的权限检查方法:
java
protected String checkPeakPermission(XiuxianUserEntity user, String actionDescription) {
AgentContext ctx = AgentContextHolder.getContext();
if (ctx != null && ctx.getRole() == XiuxianRole.PEAK_MASTER) {
String userPeakId = user.getPeakId();
String ctxPeakId = ctx.getPeakId();
if (userPeakId != null && !userPeakId.equals(ctxPeakId)) {
return "权限不足:您只能" + actionDescription + "本峰弟子";
}
}
return null; // null 表示通过
}事务管理
使用 @Transactional
java
@Tool(description = "新增或修改修士信息")
@RequiresRole(XiuxianRole.PEAK_MASTER)
@Transactional(rollbackFor = Exception.class)
public String manageUser(@ToolParam(description = "操作参数") ManageUserDto dto) {
return dto.isCreate() ? doCreateUser(dto) : doUpdateUser(dto);
}完整示例
完整的查询工具实现:
java
@Slf4j
@Component
@RequiredArgsConstructor
public class XiuxianUserTools extends BaseToolSupport {
private static final String USER_INFO = preprocess("""
### 【${name}】修士信息
| 属性 | 值 |
|------|------|
| 境界 | ${realm} |
| 灵根类型 | ${spiritRootType} |
| 灵根属性 | ${spiritRootAttr} |
| 资质 | ${aptitude} |
| 状态 | ${statusName} |
""");
private final XiuxianUserService userService;
@Tool(name = "query_user_basic", description = "查询修士的基本信息")
public String queryUserBasic(@ToolParam(description = "修士的道号或姓名") String userName) {
if (!StringUtils.hasText(userName)) {
return error("请提供修士的道号或姓名");
}
notifyProgress("正在查阅【%s】的基本信息...", userName);
XiuxianUserEntity user = userService.fuzzyGetByName(userName);
if (user == null) {
return error("未找到修士【" + bold(userName) + "】");
}
notifyProgress("成功查阅到【%s】的基本信息", user.getName());
return render(USER_INFO, buildUserBasicParams(user));
}
private Map<String, Object> buildUserBasicParams(XiuxianUserEntity user) {
Map<String, Object> params = new HashMap<>();
params.put("name", user.getName());
params.put("realm", formatRealm(user.getRealm(), user.getRealmLevel()));
params.put("spiritRootType", nv(user.getSpiritRootType()));
params.put("spiritRootAttr", nv(user.getSpiritRootAttr()));
params.put("aptitude", nv(user.getAptitude()));
params.put("statusName", getUserStatusName(user.getStatus()));
return params;
}
}最佳实践
1. 参数校验
始终校验必填参数:
java
if (!StringUtils.hasText(userName)) {
return error("请提供修士的道号或姓名");
}2. 进度通知
关键步骤发送进度通知:
java
notifyProgress("正在处理...");
// 执行操作
notifyProgress("处理完成");3. 错误处理
返回友好的错误信息:
java
if (user == null) {
return error("未找到修士【" + bold(userName) + "】");
}4. 日志记录
记录关键操作日志:
java
log.info("Created user: id={}, name={}", user.getId(), user.getName());5. 权限检查
敏感操作添加权限注解:
java
@RequiresRole(value = XiuxianRole.PEAK_MASTER, message = "需要峰主权限")