【从0到1构建一个ClaudeAgent】规划与协调-TodoWrite

这段代码引入了一个非常关键的概念:“自我反思与状态管理”

之前的 Agent 只是单纯的“听指令 -> 干活”,容易干着干着就忘了初衷,或者在复杂的任务中迷失方向。TodoManager 就像是给 Agent 装了一个“记事本”和“监工”。

Java 实现代码

public class AgentWithTodo {
    private static final Path WORKDIR = Paths.get(System.getProperty("user.dir"));
    
    // --- 1. 状态管理:TodoManager ---
    
    // 任务状态枚举
    public enum TaskStatus {
        PENDING("pending"), IN_PROGRESS("in_progress"), COMPLETED("completed");
        public final String label;
        TaskStatus(String label) { this.label = label; }
        public static TaskStatus fromLabel(String s) {
            for (TaskStatus ts : values()) if (ts.label.equals(s)) return ts;
            return PENDING;
        }
    }

    // 任务实体
    public static class TodoItem {
        public String id;
        public String text;
        public TaskStatus status;
        public TodoItem(String id, String text, String status) {
            this.id = id; this.text = text; this.status = TaskStatus.fromLabel(status);
        }
    }

    // 管理器类
    public static class TodoManager {
        private List<TodoItem> items = new ArrayList<>();

        public String update(List<Map<String, Object>> newItems) throws Exception {
            if (newItems.size() > 20) throw new Exception("Max 20 todos allowed");
            List<TodoItem> validated = new ArrayList<>();
            int inProgressCount = 0;

            for (int i = 0; i < newItems.size(); i++) {
                Map<String, Object> item = newItems.get(i);
                String text = (String) item.getOrDefault("text", "");
                String statusStr = (String) item.getOrDefault("status", "pending");
                String id = String.valueOf(item.getOrDefault("id", String.valueOf(i + 1)));

                if (text.trim().isEmpty()) throw new Exception("Item " + id + ": text required");
                
                TaskStatus status = TaskStatus.fromLabel(statusStr.toLowerCase());
                if (status == TaskStatus.IN_PROGRESS) inProgressCount++;

                validated.add(new TodoItem(id, text.trim(), status.label));
            }

            if (inProgressCount > 1) throw new Exception("Only one task can be in_progress at a time");
            this.items = validated;
            return render();
        }

        public String render() {
            if (items.isEmpty()) return "No todos.";
            StringBuilder sb = new StringBuilder();
            for (TodoItem item : items) {
                String marker = item.status == TaskStatus.PENDING ? "[ ]" :
                                item.status == TaskStatus.IN_PROGRESS ? "[>]" : "[x]";
                sb.append(String.format("%s #%s: %s%n", marker, item.id, item.text));
            }
            long done = items.stream().filter(i -> i.status == TaskStatus.COMPLETED).count();
            sb.append(String.format("%n(%d/%d completed)", done, items.size()));
            return sb.toString();
        }
    }

    private static final TodoManager TODO_MANAGER = new TodoManager();

    // --- 2. 工具定义与分发 ---
    public enum ToolType {
        BASH("bash"), READ_FILE("read_file"), WRITE_FILE("write_file"), 
        EDIT_FILE("edit_file"), TODO("todo");  // 新增 todo 工具
        public final String name;
        ToolType(String name) { this.name = name; }
    }

    private static final Map<String, ToolExecutor> TOOL_HANDLERS = new HashMap<>();

    static {
        // ... 省略已有的工具注册
        
        // 注册 Todo 工具
        TOOL_HANDLERS.put(ToolType.TODO.name, args -> {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> items = (List<Map<String, Object>>) args.get("items");
            return TODO_MANAGER.update(items);
        });
    }

    // --- 3. 核心循环 ---
    public static void agentLoop(List<Map<String, Object>> messages) {
        int roundsSinceTodo = 0;  // 新增:跟踪轮数

        while (true) {
            // ... 省略相同的 LLM 调用、消息追加、停止检查逻辑
            
            // 3. 执行工具
            List<Map<String, Object>> toolResults = new ArrayList<>();
            List<Map<String, Object>> content = (List<Map<String, Object>>) response.get("content");
            boolean usedTodo = false;  // 新增:标记是否使用了 todo 工具

            for (Map<String, Object> block : content) {
                if ("tool_use".equals(block.get("type"))) {
                    // ... 省略相同的工具调用逻辑
                    String toolName = (String) block.get("name");
                    // ... 执行工具
                    
                    if (toolName.equals("todo")) usedTodo = true;  // 标记 todo 使用
                }
            }

            // 4. 监工逻辑 (Nag Reminder)
            roundsSinceTodo = usedTodo ? 0 : roundsSinceTodo + 1;
            
            if (roundsSinceTodo >= 3) {  // 关键:超过3轮没更新就提醒
                Map<String, Object> nag = new HashMap<>();
                nag.put("type", "text");
                nag.put("text", "<reminder>Update your todos.</reminder>");
                toolResults.add(0, nag); // 插入到结果列表最前面
                System.out.println(">>> 监工提醒:更新待办列表!");
            }

            // 5. 回传结果
            // ... 省略相同的回传逻辑
        }
    }

    // --- 4. 工具实现 (简化版) ---
    // ... 省略已有的工具实现
}

状态管理:TodoManager 类

为Agent引入长期记忆和工作进度追踪能力,让Agent能"记住"自己的任务列表和工作状态。

// 任务状态枚举
public enum TaskStatus {
    PENDING("pending"),
    IN_PROGRESS("in_progress"),
    COMPLETED("completed");
    // 状态枚举:明确定义三种状态
    // 状态驱动:Agent根据状态决定下一步操作
}
// 任务实体 - 数据结构
public static class TodoItem {
    public String id;          // 唯一标识
    public String text;        // 任务描述
    public TaskStatus status;  // 状态
    // 结构化的任务表示
    // 为LLM提供清晰的上下文
}
// TodoManager - 核心状态管理
public class TodoManager {
    private List<TodoItem> items = new ArrayList<>();  // 状态存储

    public String update(List<Map<String, Object>> newItems) throws Exception {
        if (newItems.size() > 20) throw new Exception("Max 20 todos allowed");
        // 业务规则1:限制任务数量,防止滥用
        
        int inProgressCount = 0;
        List<TodoItem> validated = new ArrayList<>();

        for (int i = 0; i < newItems.size(); i++) {
            Map<String, Object> item = newItems.get(i);
            String text = (String) item.getOrDefault("text", "");
            String statusStr = (String) item.getOrDefault("status", "pending");
            String id = String.valueOf(item.getOrDefault("id", String.valueOf(i + 1)));
            
            if (text.trim().isEmpty()) throw new Exception("Item " + id + ": text required");
            // 业务规则2:任务文本必填
            
            TaskStatus status = TaskStatus.fromLabel(statusStr.toLowerCase());
            if (status == TaskStatus.IN_PROGRESS) inProgressCount++;
            // 业务规则3:跟踪进行中任务数量
        }

        if (inProgressCount > 1) throw new Exception("Only one task can be in_progress at a time");
        // 业务规则4:一次只能进行一个任务,聚焦执行
        
        this.items = validated;  // 原子性更新
        return render();  // 返回可视化表示
    }

    public String render() {
        if (items.isEmpty()) return "No todos.";
        StringBuilder sb = new StringBuilder();
        for (TodoItem item : items) {
            String marker = item.status == TaskStatus.PENDING ? "[ ]" :
                           item.status == TaskStatus.IN_PROGRESS ? "[>]" : "[x]";
            sb.append(String.format("%s #%s: %s%n", marker, item.id, item.text));
            // 可视化格式:[ ] 待办, [>] 进行中, [x] 已完成
        }
        
        long done = items.stream().filter(i -> i.status == TaskStatus.COMPLETED).count();
        sb.append(String.format("%n(%d/%d completed)", done, items.size()));
        // 进度统计:为LLM提供进度反馈
        return sb.toString();
    }
}
  • 状态持久化:Agent有了"记忆",不再是完全无状态的
  • 结构化表示:用面向对象的方式管理任务状态
  • 业务约束:通过校验规则确保状态一致性
  • 可视化输出:为LLM提供人类可读的进度展示

Todo工具集成

// 在工具枚举中新增
TODO("todo");  // 扩展工具集,添加状态管理工具

// 注册Todo工具实现
TOOL_HANDLERS.put(ToolType.TODO.name, args -> {
    @SuppressWarnings("unchecked")
    List<Map<String, Object>> items = (List<Map<String, Object>>) args.get("items");
    return TODO_MANAGER.update(items);
    // 状态更新工具:让LLM能操作任务状态
    // 接受LLM传入的任务列表,更新内部状态
});
  • 状态操作作为工具:将状态管理抽象为工具调用
  • 双向通信:LLM可以通过工具更新状态,也能获取状态
  • 统一接口:与其他工具使用相同的调用模式

监工逻辑(Nag Reminder)

// 在agentLoop中新增
int roundsSinceTodo = 0;  // 计数器:记录多少轮没使用todo工具
boolean usedTodo = false; // 标记当前轮是否使用了todo

// 执行工具时记录
if (toolName.equals("todo")) usedTodo = true;

// 每轮结束后的监工检查
roundsSinceTodo = usedTodo ? 0 : roundsSinceTodo + 1;  // 重置或递增

if (roundsSinceTodo >= 3) {  // 如果超过3轮没更新待办
    Map<String, Object> nag = new HashMap<>();
    nag.put("type", "text");
    nag.put("text", "<reminder>Update your todos.</reminder>");
    toolResults.add(0, nag); // 插入到结果列表最前面
    
    System.out.println(">>> 监工提醒:更新待办列表!");
    // 强制提醒:防止LLM忘记更新状态
}
  • 防遗忘机制:LLM可能会忘记更新状态,需要外部提醒
  • 渐进式提醒:容忍短期遗忘,超过阈值再干预
  • 结构化提示:使用特殊标签<reminder>,让LLM识别这是系统提示
  • 优先级:插入到结果列表最前面,确保LLM先看到

架构演进与价值

从 AgentWithTools 到 AgentWithTodo 的升级

维度 AgentWithTools AgentWithTodo
状态管理 无状态 有状态(TodoManager)
进度追踪 不支持 支持任务进度管理
长期记忆 不支持 支持任务列表记忆
监督机制 有监工提醒
任务管理 工具级 项目级
posted @ 2026-04-11 09:00  程序员Seven  阅读(70)  评论(0)    收藏  举报