diff --git a/src/main/java/io/mybatis/learn/s03/S03TodoWrite.java b/src/main/java/io/mybatis/learn/s03/S03TodoWrite.java index a25109d..713f272 100644 --- a/src/main/java/io/mybatis/learn/s03/S03TodoWrite.java +++ b/src/main/java/io/mybatis/learn/s03/S03TodoWrite.java @@ -85,11 +85,6 @@ public class S03TodoWrite implements CommandLineRunner { .call() .content(); - // 每次回复后打印当前 todo 状态 - System.out.println("\n--- Todo Status ---"); - System.out.println(todoManager.render()); - System.out.println("-------------------"); - return response; }); } diff --git a/src/main/java/io/mybatis/learn/s03/TodoManager.java b/src/main/java/io/mybatis/learn/s03/TodoManager.java index 5e82370..c1af0c0 100644 --- a/src/main/java/io/mybatis/learn/s03/TodoManager.java +++ b/src/main/java/io/mybatis/learn/s03/TodoManager.java @@ -1,5 +1,7 @@ package io.mybatis.learn.s03; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; @@ -23,6 +25,8 @@ import java.util.List; */ public class TodoManager { + private static final Logger log = LoggerFactory.getLogger(TodoManager.class); + /** * Todo 项数据结构。 * TIP: 对应 Python 中 todo item 字典 {@code {"id": ..., "text": ..., "status": ...}}。 @@ -73,7 +77,11 @@ public class TodoManager { } this.items = validated; - return render(); + String rendered = render(); + if (log.isDebugEnabled()) { + System.out.printf("📋 调用工具 [Todo] 更新 %d 项任务%n%s%n", items.size(), rendered); + } + return rendered; } /** diff --git a/src/main/java/io/mybatis/learn/s08/BackgroundManager.java b/src/main/java/io/mybatis/learn/s08/BackgroundManager.java index 26345af..7234a36 100644 --- a/src/main/java/io/mybatis/learn/s08/BackgroundManager.java +++ b/src/main/java/io/mybatis/learn/s08/BackgroundManager.java @@ -24,7 +24,13 @@ import java.util.stream.Collectors; *
* TIP: 对应 Python {@code agents/s08_background_tasks.py} 中的 {@code BackgroundManager} 类。 * Python 使用 {@code threading.Thread(daemon=True)}, - * Java 使用 {@link ExecutorService} + 虚拟线程(Java 21)。 + * Java 同样使用 daemon 平台线程(而非虚拟线程)。 + *
+ * ⚠️ 为何不用虚拟线程:{@code execute()} 内通过 {@code BufferedReader.lines()} 读取进程输出, + * 底层调用 {@code FileInputStream.read0()}(native 方法),会将虚拟线程 + * 钉住(pin)在载体线程(carrier thread) 上。 + * 载体线程池大小 = {@code availableProcessors()},若池耗尽则后续任务只能串行等待。 + * daemon 平台线程没有此约束,可真正并行执行多个阻塞 I/O 任务,符合 Python 版行为。 *
* Main thread Background thread
* +-----------------+ +-----------------+
@@ -42,7 +48,14 @@ public class BackgroundManager {
private final Map tasks = new ConcurrentHashMap<>();
private final List notificationQueue = new CopyOnWriteArrayList<>();
- private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
+ // TIP: 使用 daemon 平台线程而非虚拟线程。
+ // 读取进程输出时底层会调用 native read0(),虚拟线程遇到 native 方法会被 pin 在载体线程上,
+ // 导致多个后台任务串行排队。平台线程无此限制,对应 Python 的 threading.Thread(daemon=True)。
+ private final ExecutorService executor = Executors.newCachedThreadPool(r -> {
+ Thread t = new Thread(r, "bg-worker");
+ t.setDaemon(true);
+ return t;
+ });
private final String workDir;
record TaskInfo(String status, String result, String command) {
@@ -56,7 +69,9 @@ public class BackgroundManager {
log.info("BackgroundManager 初始化,workDir={}", workDir);
}
- @Tool(description = "Run a command in a background thread. Returns task_id immediately without waiting.")
+ @Tool(description = "Run a command in a background thread. Returns task_id immediately without waiting. "
+ + "When starting multiple independent background tasks, call this tool for ALL of them "
+ + "in a single response (parallel function calls) so they start at the same time.")
public String backgroundRun(
@ToolParam(description = "The shell command to run in background") String command) {
String taskId = UUID.randomUUID().toString().substring(0, 8);
diff --git a/src/main/java/io/mybatis/learn/s08/S08BackgroundTasks.java b/src/main/java/io/mybatis/learn/s08/S08BackgroundTasks.java
index ac9e4dd..c0efb6f 100644
--- a/src/main/java/io/mybatis/learn/s08/S08BackgroundTasks.java
+++ b/src/main/java/io/mybatis/learn/s08/S08BackgroundTasks.java
@@ -50,8 +50,15 @@ public class S08BackgroundTasks implements CommandLineRunner {
System.out.println("[Background tasks completed: " + notifs.size() + "]");
}
+ // TIP: 明确要求 LLM 并行调用 backgroundRun,避免逐个调用导致任务串行提交。
+ // 同时注入当前 OS 信息,让 LLM 选择平台适配的命令(如 Windows 用 timeout /t N /nobreak 替代 sleep N)。
+ String os = System.getProperty("os.name");
String system = "You are a coding agent at " + workDir
- + ". Use backgroundRun for long-running commands." + bgContext;
+ + ". Current OS: " + os + "."
+ + " Use backgroundRun for long-running commands."
+ + " When starting multiple independent background tasks, issue ALL backgroundRun"
+ + " calls in a single parallel batch so they begin simultaneously."
+ + bgContext;
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem(system)