Skip to content

Commit 253a450

Browse files
committed
fix: make PythonTool base path configurable for different SkillRegistry types
1 parent 1ef6694 commit 253a450

File tree

1 file changed

+40
-10
lines changed
  • examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials

1 file changed

+40
-10
lines changed

examples/documentation/src/main/java/com/alibaba/cloud/ai/examples/documentation/framework/tutorials/SkillExample.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import java.io.FileNotFoundException;
4545
import java.io.IOException;
4646
import java.nio.charset.StandardCharsets;
47+
import java.nio.file.Paths;
4748
import java.util.List;
4849
import java.util.Map;
4950
import java.util.function.BiFunction;
@@ -55,6 +56,11 @@
5556
* @author zlt
5657
*/
5758
public class SkillExample {
59+
private static final String PROJECT_SKILLS_PATH =
60+
Paths.get(System.getProperty("user.dir"),
61+
"examples", "documentation", "src", "main", "resources", "skills")
62+
.toString();
63+
5864
// ==================== 基础注册表使用 ====================
5965

6066
/**
@@ -65,7 +71,7 @@ public static SkillRegistry fileSystemSkillRegistry() {
6571
// 配置从当前项目的 skills 目录加载
6672
// 目录结构示例: ./skills/my-skill/SKILL.md (每个技能是一个文件夹,且必须包含 SKILL.md)
6773
SkillRegistry registry = FileSystemSkillRegistry.builder()
68-
.projectSkillsDirectory(System.getProperty("user.dir") + "/src/main/resources/skills")
74+
.projectSkillsDirectory(PROJECT_SKILLS_PATH)
6975
.build();
7076

7177
System.out.println("Registry created with file system");
@@ -97,7 +103,7 @@ public static SkillRegistry multiLevelDirectory() {
97103
// 配置用户级和项目级目录
98104
SkillRegistry registry = FileSystemSkillRegistry.builder()
99105
.userSkillsDirectory(System.getProperty("user.home") + "/saa/skills")
100-
.projectSkillsDirectory(System.getProperty("user.dir") + "/src/main/resources/skills")
106+
.projectSkillsDirectory(PROJECT_SKILLS_PATH)
101107
.build();
102108

103109
System.out.println("Registry created with multi-level directories");
@@ -149,8 +155,23 @@ public static SkillsAgentHook progressiveToolDisclosure(SkillRegistry registry)
149155
// 需与 SKILL.md 元数据里的 name 一致
150156
String skillName = "weekly-report-assistant";
151157

158+
String basePath;
159+
if (registry instanceof ClasspathSkillRegistry) {
160+
// ClasspathSkillRegistry 将技能复制到 /tmp/skills
161+
basePath = "/tmp/skills";
162+
} else if (registry instanceof FileSystemSkillRegistry fsRegistry) {
163+
basePath = fsRegistry.getProjectSkillsDirectory() != null
164+
? fsRegistry.getProjectSkillsDirectory()
165+
: fsRegistry.getUserSkillsDirectory();
166+
} else {
167+
throw new IllegalArgumentException("Unsupported registry type: " + registry.getClass());
168+
}
169+
152170
// 定义一个特定技能才需要的工具,例如 weekly-report-assistant 使用 pythonTool 来运行 skill 里面的 python 脚本
153-
ToolCallback pythonTool = PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION);
171+
ToolCallback pythonTool = PythonTool.createPythonToolCallback(
172+
PythonTool.DESCRIPTION,
173+
basePath
174+
);
154175

155176
// 将工具与技能名称绑定 (key 必须与 .md 文件中的 name 字段一致)
156177
Map<String, List<ToolCallback>> groupedTools = Map.of(
@@ -164,7 +185,7 @@ public static SkillsAgentHook progressiveToolDisclosure(SkillRegistry registry)
164185
.groupedTools(groupedTools) // 注入工具绑定关系
165186
.build();
166187

167-
System.out.println("Hook configured with progressive tool disclosure");
188+
System.out.println("Hook configured with progressive tool disclosure (base path: " + basePath + ")");
168189
return hook;
169190
}
170191

@@ -281,20 +302,27 @@ The tool will execute the logic and return the result (printed output or return
281302

282303
private static final Logger log = LoggerFactory.getLogger(PythonTool.class);
283304
private final Engine engine;
305+
private final File allowedBasePath;
284306

285-
public PythonTool() {
307+
public PythonTool(String allowedBasePath) {
286308
// Create a shared engine for better performance
287309
this.engine = Engine.newBuilder()
288310
.option("engine.WarnInterpreterOnly", "false")
289311
.build();
312+
try {
313+
this.allowedBasePath = new File(allowedBasePath).getCanonicalFile();
314+
log.info("PythonTool initialized with allowed base path: {}", this.allowedBasePath);
315+
} catch (IOException e) {
316+
throw new IllegalArgumentException("Invalid base path: " + allowedBasePath, e);
317+
}
290318
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
291319
}
292320

293321
/**
294322
* Create a ToolCallback for the Python tool.
295323
*/
296-
public static ToolCallback createPythonToolCallback(String description) {
297-
return FunctionToolCallback.builder("python_tool", new PythonTool())
324+
public static ToolCallback createPythonToolCallback(String description, String allowedBasePath) {
325+
return FunctionToolCallback.builder("python_tool", new PythonTool(allowedBasePath))
298326
.description(description)
299327
.inputType(PythonRequest.class)
300328
.build();
@@ -406,10 +434,12 @@ public PythonRequest() {}
406434
}
407435

408436
private File getSafeScriptFile(String scriptPath) throws IOException {
409-
File baseDir = new File("/tmp/skills").getCanonicalFile();
410437
File targetFile = new File(scriptPath).getCanonicalFile();
411-
if (!targetFile.getPath().startsWith(baseDir.getPath())) {
412-
throw new SecurityException("Access denied: Script path is outside the allowed directory.");
438+
if (!targetFile.getPath().startsWith(allowedBasePath.getPath())) {
439+
throw new SecurityException(
440+
String.format("Access denied: Script path '%s' is outside the allowed directory '%s'.",
441+
targetFile.getPath(), allowedBasePath.getPath())
442+
);
413443
}
414444
if (!targetFile.exists() || !targetFile.isFile()) {
415445
throw new FileNotFoundException("Script not found: " + scriptPath);

0 commit comments

Comments
 (0)