13 - 工具集

一、工具总览

tool/ 目录包含 Dodo-Agent 的所有内置工具:

工具用途Agent
loadContentFileContentService文件加载 + RAGFile / Skills
SkillSkillsTool加载技能Skills (原生)
read_skillReadSkillTool读取技能内容Skills (手动)
read_file / write_file / edit_fileFileSystemTools文件系统操作Skills
list_files / glob_filesFileSystemTools文件查找Skills
grepGrepTool内容搜索Skills
bashBashToolShell 命令执行Skills
WeatherServiceWeatherService天气查询Demo / 自定义

二、FileContentService

tool/FileContentService.java 是文件加载工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Tool(description = "根据文件ID加载文件内容或进行RAG语义检索...")
public String loadContent(
@ToolParam(description = "文件ID") String fileId,
@ToolParam(description = "用户的问题,用于语义检索(可选)") String question) {

var fileInfo = fileManageService.getFileInfo(fileId);
if (fileInfo.getStatus() != FileInfo.FileStatus.SUCCESS) {
return "文件处理中或处理失败...";
}

Integer embed = fileInfo.getEmbed();
if (embed != null && embed == 1) {
return retrieveWithRAG(fileId, fileInfo, question); // RAG
} else {
return loadDirectly(fileId, fileInfo); // 全量
}
}

详见 05 File 智能体09 RAG 与向量检索

三、SkillsTool(Spring AI 原生模式)

tool/SkillsTool.javaSKILL.md 技能文件注册为工具:

3.1 工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
skills.directory: C:\Users\Lenovo\.claude\skills

扫描所有子目录的 SKILL.md

解析 frontmatter(YAML) + content(Markdown)

包装为名为 "Skill" 的工具

工具描述:完整的使用流程 + 可用技能列表(XML)

LLM 看到工具描述,决定何时调用 "Skill" 工具

调用时传入技能名称

返回:技能工作目录 + 完整提示词

3.2 工具描述模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
在当前会话中加载一个技能(Skill)。本工具的唯一作用是:传入技能名称,获取该技能的完整提示词和工作目录。

<什么是技能>
技能是一段专业的提示词,包含特定领域的知识、工作流程和操作指令。
...
</什么是技能>

<技能的完整使用流程>
第一步 — 判断是否需要技能:
第二步 — 通过本工具加载技能:
第三步 — 阅读并理解技能提示词:
第四步 — 按技能提示词执行任务:
</技能的完整使用流程>

<严格禁止>
- 禁止将技能名称当作独立的工具来调用
- 禁止在未通过本工具加载的情况下,假装已经知道技能的内容
- 禁止编造或猜测 <可用技能列表> 中不存在的技能名称
- 禁止重复加载同一个技能
- 禁止加载技能后忽略其提示词内容,自行发挥
</严格禁止>

<可用技能列表>
<skill>
<name>pptx</name>
<description>生成专业演示文稿</description>
<basePath>C:\Users\Lenovo\.claude\skills\pptx</basePath>
</skill>
</可用技能列表>

3.3 Builder 模式

1
2
3
4
5
6
SkillsTool.builder()
.addSkillsDirectory(skillsDirectory) // 添加单个目录
.addSkillsDirectories(List.of(...)) // 添加多个目录
.addSkillsResource(resource) // 添加 Classpath 资源
.addSkillsResources(resources) // 批量添加
.build(); // 构建 ToolCallback

详见 08 Skills 智能体

四、ReadSkillTool(手动模式)

agent/skills/manual/tool/ReadSkillTool.java 是手动模式下的技能读取工具:

1
2
3
4
5
6
7
8
9
10
@Tool(description = "读取技能的完整内容")
public String readSkill(
@ToolParam(description = "技能名称") String skillName) {

SkillMetadata skill = skillManager.getSkill(skillName);
if (skill == null) {
return "未找到技能: " + skillName;
}
return "工作目录: " + skill.getBasePath() + "\n\n" + skill.getContent();
}

五、FileSystemTools

tool/FileSystemTools.java 提供完整的文件系统操作:

5.1 工具列表

工具名用途关键参数
read_file读取文件(带行号)filePath, offset, limit
write_file创建新文件filePath, content
edit_file字符串替换编辑filePath, oldString, newString, replaceAll
list_files列出目录内容path
glob_filesGlob 模式匹配pattern

5.2 关键特性

  • 路径安全:阻止目录穿越(.. 校验)
  • 编码回退:UTF-8 → GBK → ISO-8859-1
  • 行号格式化:类似 cat -n 输出
  • 大文件分页:默认 500 行,offset/limit 分页
  • 大小限制:默认 10MB
  • 长行截断:超过 10000 字符的行列出多个块
  • 虚拟模式:将路径视为 cwd 下的虚拟绝对路径

5.3 read_file 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Tool(name = "read_file", description = "读取文件系统中的文件内容...")
public String readFile(
@ToolParam(description = "要读取的文件路径") String filePath,
@ToolParam(description = "起始行偏移量(默认: 0)", required = false) Integer offset,
@ToolParam(description = "最大读取行数(默认: 500)", required = false) Integer limit,
@ToolParam(description = "图片编码格式(可选)", required = false) String imageFormat) {
try {
Path resolvedPath = resolvePath(filePath);
return readFileContent(resolvedPath, offset, limit, true);
} catch (IllegalArgumentException e) {
return "Error: " + e.getMessage();
} catch (Exception e) {
return "Error reading file '" + filePath + "': " + e.getMessage();
}
}

5.4 edit_file 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Tool(name = "edit_file", description = "通过字符串替换编辑已有文件...")
public String editFile(
@ToolParam String filePath,
@ToolParam String oldString,
@ToolParam String newString,
@ToolParam(required = false) Boolean replaceAll) {

Path resolvedPath = resolvePath(filePath);
boolean replaceAllFlag = Boolean.TRUE.equals(replaceAll);
return editFileContent(resolvedPath, oldString, newString, replaceAllFlag);
}

private String editFileContent(Path filePath, String oldString, String newString, boolean replaceAll) {
String content = Files.readString(filePath);
int occurrences = countOccurrences(content, oldString);

if (occurrences == 0) return "Error: String not found in file";
if (!replaceAll && occurrences > 1) {
return "Error: String appears " + occurrences + " times. Use replaceAll=true or provide more context.";
}

String newContent = replaceAll
? content.replace(oldString, newString)
: content.substring(0, content.indexOf(oldString)) + newString
+ content.substring(content.indexOf(oldString) + oldString.length());

Files.writeString(filePath, newContent, ...);
return "Successfully edited file (replaced " + occurrences + " occurrence(s))";
}

六、BashTool

tool/BashTool.java 提供 Shell 命令执行能力。

6.1 核心特性

  • 持久化 Shell 会话ShellSessionManager 维护 cd / 环境变量
  • 跨平台支持:自动检测 Windows / Unix
  • 动态描述:根据操作系统生成不同的命令注意事项
  • 会话重启restart=true 清空会话状态
  • 超时控制:默认 120s,可自定义

6.2 跨平台描述

1
2
3
4
5
private static String buildDynamicDescription() {
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win");
String osType = isWindows ? "Windows (cmd.exe)" : "Unix/Linux/Mac (bash)";
// ...
}

Windows 注意事项

1
2
3
4
5
6
- 用 'type' 代替 'cat'
- 用 'dir' 代替 'ls'
- 用 'del' 代替 'rm'
- 用 'findstr' 代替 'grep'
- 路径使用反斜杠: C:\\Users\\...
- 绝对不要用 'mkdir -p',这会创建一个名为 '-p' 的文件夹!

6.3 ShellSessionManager

tool/ShellSessionManager.java 管理持久化 Shell 会话:

1
2
3
4
5
6
7
public CommandResult executeCommand(String sessionId, String command, String workingDir) {
// 1. 获取或创建会话
Process process = getOrCreateSession(sessionId, workingDir);
// 2. 发送命令
// 3. 等待结果(带超时)
// 4. 解析 stdout / stderr
}

七、GrepTool

tool/GrepTool.java 提供代码搜索能力(类似 ripgrep)。

典型工具签名

1
2
3
4
5
6
7
@Tool(name = "grep", description = "在文件中搜索正则表达式...")
public String grep(
@ToolParam String pattern,
@ToolParam String path,
@ToolParam(required = false) String filePattern,
@ToolParam(required = false) Boolean caseInsensitive,
@ToolParam(required = false) Integer contextLines);

八、MarkdownParser

tool/MarkdownParser.java 解析 SKILL.md 文件:

1
2
3
4
5
6
7
8
9
public class MarkdownParser {
private Map<String, Object> frontMatter; // YAML frontmatter
private String content; // Markdown 内容

public MarkdownParser(String markdown) {
parseFrontMatter(markdown);
parseContent(markdown);
}
}

解析示例

1
2
3
4
5
6
7
8
9
---
name: pptx
description: 生成专业演示文稿
---

# PPT 生成技能

## 工作流程
...

解析后:

1
2
frontMatter = {"name": "pptx", "description": "..."}
content = "# PPT 生成技能\n\n## 工作流程\n..."

九、ToolMergeUtils

tool/ToolMergeUtils.java 工具合并工具:

1
2
3
4
5
public static ToolCallback[] mergeTools(ToolCallback[]... toolArrays) {
return Arrays.stream(toolArrays)
.flatMap(Arrays::stream)
.toArray(ToolCallback[]::new);
}

使用(AgentController.initSkillsReactAgent):

1
2
3
4
5
6
7
8
ToolCallback[] allTools = ToolMergeUtils.mergeTools(
webSearchToolCallbacks,
ToolCallbacks.from(fileContentService),
new ToolCallback[]{SkillsTool.builder()...build()},
FileSystemTools.create(),
GrepTool.create(),
BashTool.create()
);

十、自定义工具指南

10.1 基于 @Tool 注解(推荐)

1
2
3
4
5
6
7
8
9
10
@Service
public class MyTool {

@Tool(description = "我的自定义工具")
public String myMethod(
@ToolParam(description = "参数1") String arg1) {
// 业务逻辑
return "result";
}
}

注册

1
List<ToolCallback> tools = List.of(ToolCallbacks.from(myTool));

10.2 基于 FunctionToolCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
public record MyInput(String query) {}

public class MyFunction implements Function<MyInput, String> {
@Override
public String apply(MyInput input) {
return "result for " + input.query();
}
}

ToolCallback callback = FunctionToolCallback.builder("my_tool", new MyFunction())
.description("我的工具")
.inputType(MyInput.class)
.build();

10.3 基于 MCP 协议

1
2
3
4
5
6
7
8
9
10
11
12
// 创建 MCP 客户端
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport
.builder(mcpUrl)
.build();
McpSyncClient client = McpClient.sync(transport).build();
client.initialize();

// 获取所有工具回调
SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder()
.mcpClients(List.of(client))
.build();
ToolCallback[] callbacks = provider.getToolCallbacks();

十一、工具安全

  • 路径穿越防护FileSystemTools.resolvePath() 检查 ..
  • 文件大小限制maxFileSizeBytes 默认 10MB
  • Shell 命令超时BashTool 默认 120s
  • 工具白名单:未来可按用户角色限制可用工具