乐于分享
好东西不私藏

从零手搓一个AI Agent:从单工具到多工具注册+任务规划状态管理

从零手搓一个AI Agent:从单工具到多工具注册+任务规划状态管理

多工具注册+任务规划状态管理

本文是Agent系列的第二篇,重点讲解Agent02相比Agent01的进化:多工具注册中心、任务规划状态管理、以及智能提醒机制。如果你还没看过第一篇,建议先看第一篇,了解基础设计。

从零手搓一个AI Agent:Java版对话+工具调用实战解析

大家好!在上一篇文章中,我们实现了一个基础的AI Agent,它只有一个bash工具。今天我们来聊聊Agent02,它带来了三个重大升级:

  • 多工具注册中心:从硬编码分支到统一接口+注册表
  • 任务规划状态管理:引入todo工具管理多步任务
  • 智能提醒机制:当Agent忘记更新计划时主动提醒
设计思路
多步任务中, 模型会丢失进度 — 重复做过的事、跳步、跑偏。对话越长越严重: 工具结果不断填满上下文, 系统提示的影响力逐渐被稀释。
所以设计了TodoManager和TodoTool,让模型可以拆解任务并记录下任务的执行状态, 先列步骤再动手, 完成率翻倍。

TodoTool:记录拆解任务

TodoManager:管理任务状态,引导模型按步骤推进,聚集工作

效果示例
ai调用todo工具将任务进行拆解
拆解以后,调用write_file工具开始依次执行
执行完成,成功制作一个贪吃蛇小游戏

一、Agent01 vs Agent02:核心差异对比

特性
Agent01
Agent02
工具数量
1个(bash)
5个(bash、read_file、write_file、edit_file、todo)
工具扩展方式
修改ToolExecutor的if-else分支
实现Tool接口+注册到ToolRegistry
任务规划
TodoManager+TodoTool完整状态管理
提醒机制
3轮未调用todo自动提醒
日志系统
简单System.out.println
AgentLogger统一封装

二、核心升级点详解

2.1 Tool接口:统一工具契约

Agent01中,ToolExecutor里硬编码了bash工具的判断逻辑。Agent02引入了Tool接口,所有工具都实现这个统一契约。

设计亮点:每个工具同时提供OpenAI Function声明(name/description/parametersSchema)和本地执行逻辑(execute),新增工具只需实现接口并注册,无需修改执行器代码。

public interface Tool {    // OpenAI Function名称    String name();    // 供模型阅读的描述    String description();    // 参数Schema(OpenAI JSON Schema格式)    JsonObject parametersSchema();    // 执行工具逻辑    String execute(String argumentsJson);    // 默认方法:组装为OpenAI兼容的tools声明    default JsonObject toOpenAiToolDeclaration() {        JsonObject fn = new JsonObject();        fn.addProperty("name", name());        fn.addProperty("description", description());        fn.add("parameters", parametersSchema());        JsonObject outer = new JsonObject();        outer.addProperty("type", "function");        outer.add("function", fn);        return outer;    }    // 工具类通用辅助方法    static JsonObject stringProperty(String description) { ... }    default Path resolveUnderCwd(String userPath) { ... } // 路径安全校验}

2.2 ToolRegistry:工具注册中心

ToolRegistry是工具的统一管理中心,负责维护工具列表、生成OpenAI声明、按名分发执行。

关键设计:声明与执行同源。AgentConfig.tools()直接调用ToolRegistry.openAiTools(),保证”模型看到的工具”和”服务端能执行的工具”完全一致。

public final class ToolRegistry {    // 标准工具声明列表(类加载时初始化)    private static final ListSTANDARD_DECLARATIONS =         Collections.unmodifiableList(Arrays.asList(            new BashTool(),            new ReadFileTool(),            new WriteFileTool(),            new EditFileTool(),            new TodoTool(new TodoManager()) // 仅用于Schema生成        ));    // 缓存的OpenAI tools声明    private static final JsonArray OPENAI_TOOLS =         buildOpenAiTools(STANDARD_DECLARATIONS);    private final MaptoolsByName;    // 按名分发执行    public String dispatch(String name, String argumentsJson) {        Tool tool = toolsByName.get(name);        if (tool == null) {            return "Error: unsupported tool " + name;        }        try {            return tool.execute(argumentsJson);        } catch (Exception e) {            return "Error: " + e.getMessage();        }    }    // 创建带会话级TodoManager的注册表    public static ToolRegistry withStandardTools(TodoManager todoManager) {        ToolRegistry r = new ToolRegistry();        Liststandard = Arrays.asList(            new BashTool(),            new ReadFileTool(),            new WriteFileTool(),            new EditFileTool(),            new TodoTool(todoManager) // 注入会话级状态管理器        );        for (Tool t : standard) {            r.register(t);        }        return r;    }    // 供AgentConfig使用的静态声明    public static JsonArray openAiTools() {        return OPENAI_TOOLS;    }}

2.3 TodoManager:任务规划状态管理

这是Agent02最重磅的新功能!TodoManager实现了会话级的任务计划管理,强制同一时间只能有一个in_progress任务,引导模型按步骤推进。

状态流转:PENDING(待办)→ IN_PROGRESS(进行中)→ COMPLETED(完成)。start()方法会检查是否已有进行中任务,complete()要求任务必须是进行中状态,防止跳步。

public final class TodoManager {    public enum Status { PENDING, IN_PROGRESS, COMPLETED, CANCELLED }    public static final class TodoItem {        private final String id;        private String title;        private Status status;        // ...    }    private final Listitems = new ArrayList<>();    private int nextId = 1;    // 新增任务    public synchronized TodoItem add(String title) {        TodoItem item = new TodoItem("task-" + nextId++, title);        items.add(item);        return item;    }    // 启动任务:强制同一时间只有一个in_progress    public synchronized TodoItem start(String id) {        TodoItem target = findById(id);        TodoItem inProgress = findInProgress();        if (inProgress != null && !inProgress.id.equals(target.id)) {            throw new IllegalStateException(                "another task already in_progress: " + inProgress.id);        }        target.status = Status.IN_PROGRESS;        return target;    }    // 完成任务:必须先in_progress才能complete    public synchronized TodoItem complete(String id) {        TodoItem target = findById(id);        if (target.status != Status.IN_PROGRESS) {            throw new IllegalStateException(                "task must be in_progress before complete: " + target.id);        }        target.status = Status.COMPLETED;        return target;    }    // 生成快照供模型查看    public synchronized JsonObject snapshot() {        JsonObject out = new JsonObject();        JsonArray arr = new JsonArray();        // 统计各状态数量...        out.add("items", arr);        out.addProperty("summary", summaryText());        return out;    }}

2.4 TodoTool:任务规划工具

TodoTool是模型用来操作任务计划的工具接口,支持add、update、start、complete、set_status、list等操作。

public final class TodoTool implements Tool {    private final TodoManager todoManager;    public TodoTool(TodoManager todoManager) {        this.todoManager = todoManager;    }    @Override    public String name() { return "todo"; }    @Override    public String description() {        return "Manage a step-by-step plan with stateful todo items.";    }    @Override    public JsonObject parametersSchema() {        JsonObject props = new JsonObject();        props.add("action", Tool.stringProperty(            "One of: add, update, start, complete, set_status, list"));        props.add("id", Tool.stringProperty(            "Todo item id, required for update/start/complete/set_status"));        props.add("title", Tool.stringProperty(            "Todo title, required for add or update"));        props.add("status", Tool.stringProperty(            "Status for set_status: pending/in_progress/completed/cancelled"));        return objectSchema(props, "action");    }    @Override    public String execute(String argumentsJson) {        JsonObject o = parseObject(argumentsJson);        String action = str(o, "action").trim().toLowerCase();        try {            switch (action) {                case "add":                    return ok("added", todoManager.add(str(o, "title")).getId());                case "start":                    return ok("started", todoManager.start(str(o, "id")).getId());                case "complete":                    return ok("completed", todoManager.complete(str(o, "id")).getId());                case "list":                    return listOnly();                // ...            }        } catch (Exception e) {            return error(e.getMessage());        }    }}

2.5 智能提醒机制(Nag)

Agent02引入了一个非常实用的功能:当模型连续多轮没有调用todo工具时,系统会自动注入提醒文案,督促模型更新计划。

提醒阈值:TODO_NAG_THRESHOLD = 3,即连续3轮未调用todo工具时触发提醒。提醒内容会附加到第一条tool消息的content中。

public final class AgentLoop {    private static final int TODO_NAG_THRESHOLD = 3;    private int roundsSinceTodo;  // 计数器    public void run(JsonArray messages, LLMClient llm) throws IOException {        // ...        boolean calledTodo = containsTodoCall(toolCalls);        if (calledTodo) {            roundsSinceTodo = 0;  // 重置计数器        } else {            roundsSinceTodo++;        }        // 检查是否需要提醒        String nagReminder = shouldInjectNag(calledTodo) ? buildNagReminder() : "";        // 执行工具调用        boolean nagInjected = false;        for (JsonElement tc : toolCalls) {            JsonObject toolMessage = tools.executeToolCall(tc.getAsJsonObject());            // 将提醒附加到第一条tool消息            if (!nagInjected && !nagReminder.isEmpty()) {                String original = str(toolMessage, "content");                toolMessage.addProperty("content", original + "\n\n" + nagReminder);                nagInjected = true;            }            messages.add(toolMessage);        }    }    private boolean shouldInjectNag(boolean calledTodoInRound) {        return !calledTodoInRound && roundsSinceTodo >= TODO_NAG_THRESHOLD;    }    private String buildNagReminder() {        return "[REMINDER] You have gone " + roundsSinceTodo            + " rounds without calling todo. Update the plan before proceeding. "            + "Current todo snapshot: " + todoManager.summaryText();    }}

2.6 文件操作工具集

Agent02新增了三个文件操作工具,都通过resolveUnderCwd限制在工作目录内,防止越权访问。

工具
功能
安全特性
read_file
读取UTF-8文本文件
5万字符截断、路径限制在工作区
write_file
创建或覆盖文件
自动创建父目录、路径限制
edit_file
精确替换文本片段
要求old_string唯一匹配、路径限制

三、类结构依赖图

Agent02采用了更清晰的分层架构:

  • runtime层:Main、AgentLoop、TodoManager(编排与状态)
  • client层:LLMClient(网关通信)
  • config层:AgentConfig(配置管理)
  • tools层:Tool接口、ToolRegistry、ToolExecutor(契约与分发)
  • tools.impl层:各工具具体实现
  • utils层:AgentLogger(日志封装)

四、如何扩展新工具?

相比Agent01需要修改ToolExecutor的if-else分支,Agent02的扩展方式优雅多了:

  1. 创建新类实现Tool接口
  2. ToolRegistry.withStandardTools()中注册实例
  3. 如需在API声明中展示,同步添加到STANDARD_DECLARATIONS列表

完全不需要修改AgentLoop、LLMClient、ToolExecutor的核心逻辑!这就是接口+注册表架构的威力。

五、总结

Agent02在Agent01的基础上完成了三个重要进化:

  • 工具架构升级:从硬编码到接口+注册表,扩展性大幅提升
  • 任务规划能力:TodoManager实现状态管理,强制单任务进行中的约束
  • 智能提醒机制:防止模型”忘记”更新计划,主动纠偏

这些改进让Agent从一个简单的”命令执行器”进化成了具备规划能力自我纠正的智能助手。如果你想构建更强大的AI Agent,Agent02的架构设计非常值得参考!

下一篇文章我们将继续探索引入子agent和技能的实现。敬请期待!

本篇以及历史的代码放在了👇

https://github.com/laixiaoxing2026/learn-java-agent/tree/main/src/main/java/com/learn/javaagent/Agent02


参考项目:learn-java-agent/Agent02核心类:Tool.java、ToolRegistry.java、TodoManager.java、AgentLoop.java

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 从零手搓一个AI Agent:从单工具到多工具注册+任务规划状态管理

猜你喜欢

  • 暂无文章