Skip to content

工具开发

本文档介绍如何开发业务工具,让 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 = "需要峰主权限")

下一步