13 - 工具集 一、工具总览 tool/ 目录包含 Dodo-Agent 的所有内置工具:
工具 类 用途 Agent loadContentFileContentService文件加载 + RAG File / 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); } else { return loadDirectly(fileId, fileInfo); } }
详见 05 File 智能体 和 09 RAG 与向量检索
tool/SkillsTool.java 将 SKILL.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) .addSkillsResources(resources) .build();
详见 08 Skills 智能体
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(); }
tool/FileSystemTools.java 提供完整的文件系统操作:
5.1 工具列表 工具名 用途 关键参数 read_file读取文件(带行号) filePath, offset, limitwrite_file创建新文件 filePath, contentedit_file字符串替换编辑 filePath, oldString, newString, replaceAlllist_files列出目录内容 pathglob_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))" ; }
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) { Process process = getOrCreateSession(sessionId, workingDir); }
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; private String content; 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..."
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() );
十、自定义工具指南 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));
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 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 默认 10MBShell 命令超时 :BashTool 默认 120s工具白名单 :未来可按用户角色限制可用工具