知识目录 / 实战教程

手撸一个桌面文件整理 Agent:让你的电脑学会自己收拾

告别混乱的桌面!这篇教程带你从零实现一个能整理文件的智能体,包含文件扫描、分类、移动、重命名等实用工具,让你的电脑学会自己收拾。

写在前面

如果你读过前面的通用的大模型客户端封装,你已经掌握了如何让你的程序真正"思考"。

但是,一个只会聊天的程序,就像一个只会纸上谈兵的军师——它能理解你,指点你,却不能帮你做事儿。

今天,我们来给智能体装上"手"和"脚",让它能够真正操作你的电脑。

想象一下:你的桌面乱七八糟,文件散落一地。你只需要对智能体说一句"帮我整理一下桌面",它就自动帮你:

  • 扫描桌面上的所有文件
  • 让大模型根据文件类型(文档、图片、视频、代码……)做分类决策
  • 创建对应的文件夹
  • 把文件一个个搬进去

全程自主决策,不需要你写分类规则。

这不是科幻,这是今天我们要一起实现的东西。

你将学到什么

  1. 工具系统设计:如何让智能体"学会"使用工具
  2. 文件操作工具:扫描、移动、重命名、删除文件
  3. Agent 循环:让大模型自己决定调用什么工具,实现自循环、自验证

前置知识

如果你还没有读过以下文章,建议先阅读:

  1. Agent开发入门——从理解到实践 - 理解 Agent 的基本概念
  2. 接入大模型:从第一个 Hello World 到多模态对话 - 了解如何调用大模型
  3. 给你的智能体换一颗温暖的大脑:封装 LLM Client,让思考真正发生 - 掌握 LLM Client 和记忆管理

说明:本文使用 Gson 做 JSON 序列化,使用 Java 11+ 内置的 HttpClient 发送 HTTP 请求。除此之外没有任何第三方依赖。

第一部分:先看全局

整个系统只有一条核心链路:

text
你说话 → Agent 思考该调哪个工具 → 工具操作真实文件 → 结果返回给 Agent → Agent 再想下一步

这就是 ReAct 模式(Reasoning + Acting):想一步,做一步,看一步,再想。

你丢给它一个乱七八糟的目录路径,它会:

  1. 扫描目录里的所有文件
  2. 让大模型根据文件类型做分类决策
  3. 创建对应的文件夹
  4. 把文件一个个搬进去

如果文件名太丑,它还能帮你重命名。

代码结构

一共就 两个文件

text
desktop-agent/
├── DesktopAgent.java      ← 全部逻辑,约 400 行
└── lib/
    └── gson-2.11.0.jar    ← 唯一的依赖

工具不是接口、不是抽象类,就是一个个普通的 Java 方法。工具描述是写死的 JSON 字符串,直接塞给大模型。能跑通才是硬道理。

第二部分:理解工具系统

什么是 Function Calling/Tools?

简单来说,Function Calling/Tools 就是让大模型能够"调用函数"。 在这里我就不细说 Function Calling,因为这不是本篇文章的重点,但 Function Calling 这个概念非常重要,你可以通过阅读 Function Calling 详解来了解它。

第三部分:完整代码

下面是 DesktopAgent.java 的完整代码。我会在关键位置加上注释,但你也可以先整体看一遍,再回头研究细节。

编译和运行

bash
# 下载 Gson(唯一的依赖)
curl -L -o gson.jar https://repo1.maven.org/maven2/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar

# 编译
javac -cp .:gson.jar DesktopAgent.java

# 运行(默认整理桌面)
java -cp .:gson.jar DesktopAgent

# 或者指定目录
java -cp .:gson.jar DesktopAgent /tmp/test-desktop

Windows 用户请把 : 换成 ;

java
import com.google.gson.*;
import java.io.*;
import java.net.http.*;
import java.net.URI;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class DesktopAgent {

    // ============================================
    //  配置项:改成你自己的 API Key 和地址
    // ============================================
    private static final String API_KEY   = "sk-xxxxxxxxxxxxxxxx";
    private static final String API_URL   = "https://api.deepseek.com/v1/chat/completions";
    private static final String MODEL     = "deepseek-chat";
    private static final int    MAX_STEPS = 15;

    private static final Gson gson = new Gson();

    // ============================================
    //  工具定义:告诉大模型你有哪些"手"
    //  格式是 OpenAI Function Calling 标准
    // ============================================
    private static final List<String> TOOL_DEFINITIONS = List.of(
        """
        {
            "type": "function",
            "function": {
                "name": "list_files",
                "description": "列出指定目录下的所有文件和子目录,返回文件名、大小和修改时间",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "目录路径"
                        }
                    },
                    "required": ["path"]
                }
            }
        }
        """,
        """
        {
            "type": "function",
            "function": {
                "name": "create_folder",
                "description": "创建文件夹(如果不存在)。支持多级目录",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "要创建的文件夹路径"
                        }
                    },
                    "required": ["path"]
                }
            }
        }
        """,
        """
        {
            "type": "function",
            "function": {
                "name": "move_file",
                "description": "移动文件到目标路径。如果目标目录不存在会自动创建",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "source": {
                            "type": "string",
                            "description": "源文件路径"
                        },
                        "destination": {
                            "type": "string",
                            "description": "目标文件路径(含文件名)"
                        }
                    },
                    "required": ["source", "destination"]
                }
            }
        }
        """,
        """
        {
            "type": "function",
            "function": {
                "name": "rename_file",
                "description": "重命名文件或文件夹",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "文件当前路径"
                        },
                        "new_name": {
                            "type": "string",
                            "description": "新的文件名(不含目录路径)"
                        }
                    },
                    "required": ["path", "new_name"]
                }
            }
        }
        """,
        """
        {
            "type": "function",
            "function": {
                "name": "delete_file",
                "description": "删除文件或文件夹。注意:此操作不可撤销!",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "要删除的文件或文件夹路径"
                        },
                        "confirm": {
                            "type": "boolean",
                            "description": "必须为 true 才执行删除"
                        }
                    },
                    "required": ["path", "confirm"]
                }
            }
        }
        """,
        """
        {
            "type": "function",
            "function": {
                "name": "get_file_type",
                "description": "根据文件扩展名判断文件类型分类,返回:文档、图片、视频、音频、代码、压缩包、安装包、其他",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "filename": {
                            "type": "string",
                            "description": "文件名(含扩展名)"
                        }
                    },
                    "required": ["filename"]
                }
            }
        }
        """
    );

    // ============================================
    //  主函数:程序入口
    // ============================================
    public static void main(String[] args) {
        String dir = args.length > 0
            ? args[0]
            : System.getProperty("user.home") + "/Desktop";

        System.out.println("桌面整理 Agent 已启动");
        System.out.println("目标目录:" + dir);
        System.out.println();

        Scanner scanner = new Scanner(System.in);

        // 对话历史:第一条是系统提示,告诉大模型它的角色
        List<Map<String, Object>> messages = new ArrayList<>();
        messages.add(Map.of(
            "role", "system",
            "content", """
                你是一个桌面文件整理助手。
                你的能力:扫描文件、创建文件夹、移动文件、重命名文件、删除文件、识别文件类型。

                工作流程:
                1. 先用 list_files 扫描目录
                2. 用 get_file_type 判断每个文件的类型
                3. 制定分类方案,告诉用户你要怎么做,等用户确认
                4. 创建文件夹、移动文件
                5. 给出整理报告

                注意:
                - 不要删除重要文件,删除前一定要用户确认
                - 整理方案先告诉用户,等用户说"继续"或"可以"后再执行
                """
        ));

        System.out.println("你:(输入你的需求,输入 quit 退出)");

        while (true) {
            System.out.print("你:");
            String input = scanner.nextLine().trim();
            if ("quit".equalsIgnoreCase(input)) break;
            if (input.isEmpty()) continue;

            messages.add(Map.of("role", "user", "content", input));

            String reply = agentLoop(messages);
            System.out.println("助手:" + reply);
            System.out.println();
        }

        scanner.close();
    }

    // ============================================
    //  Agent 循环:这就是智能体的核心
    //
    //  不断地:调大模型 → 有工具调用就执行 → 把结果送回去 → 再调
    //  直到大模型返回纯文本(不再需要调工具)
    // ============================================
    private static String agentLoop(List<Map<String, Object>> messages) {
        for (int step = 0; step < MAX_STEPS; step++) {

            // 1) 把对话历史 + 工具定义一起发给大模型
            JsonObject body = buildRequestBody(messages);
            JsonObject resp = callLlm(body);

            JsonObject msg = resp.getAsJsonArray("choices")
                              .get(0).getAsJsonObject()
                              .getAsJsonObject("message");

            // 2) 如果大模型没有调用工具,任务完成,返回文本
            if (!msg.has("tool_calls")
                || msg.get("tool_calls").isJsonNull()) {
                String content = msg.has("content")
                    && !msg.get("content").isJsonNull()
                    ? msg.get("content").getAsString()
                    : "";
                // 把助手的最终回复加入对话历史
                messages.add(Map.of("role", "assistant", "content", content));
                return content;
            }

            // 3) 大模型要求调用工具
            //    先把助手的消息(含 tool_calls)加入对话历史
            messages.add(Map.of(
                "role", "assistant",
                "tool_calls", msg.getAsJsonArray("tool_calls"),
                "content", msg.get("content") // 可能是 null
            ));

            // 4) 逐个执行工具,把结果加入对话历史
            for (JsonElement tcElem : msg.getAsJsonArray("tool_calls")) {
                JsonObject tc = tcElem.getAsJsonObject();
                String callId = tc.get("id").getAsString();
                JsonObject func = tc.getAsJsonObject("function");
                String funcName = func.get("name").getAsString();
                JsonObject args = JsonParser.parseString(
                    func.get("arguments").getAsString()
                ).getAsJsonObject();

                // 执行
                String result = executeTool(funcName, args);

                // 在终端打印执行情况(方便你看清楚发生了什么)
                System.out.println("  🔧 " + funcName + " → "
                    + (result.length() > 100
                        ? result.substring(0, 100) + "..."
                        : result));

                // 把工具执行结果加入对话历史
                messages.add(Map.of(
                    "role", "tool",
                    "tool_call_id", callId,
                    "content", result
                ));
            }
            // 循环继续 → 大模型会看到工具结果,继续思考
        }
        return "(达到最大思考轮次,任务可能未完成)";
    }

    // ============================================
    //  工具执行:根据名字分发到对应的 Java 方法
    // ============================================
    private static String executeTool(String name, JsonObject args) {
        try {
            return switch (name) {
                case "list_files"
                    -> toolListFiles(args.get("path").getAsString());
                case "create_folder"
                    -> toolCreateFolder(args.get("path").getAsString());
                case "move_file"
                    -> toolMoveFile(
                        args.get("source").getAsString(),
                        args.get("destination").getAsString());
                case "rename_file"
                    -> toolRenameFile(
                        args.get("path").getAsString(),
                        args.get("new_name").getAsString());
                case "delete_file"
                    -> toolDeleteFile(
                        args.get("path").getAsString(),
                        args.get("confirm").getAsBoolean());
                case "get_file_type"
                    -> toolGetFileType(args.get("filename").getAsString());
                default -> "未知工具:" + name;
            };
        } catch (Exception e) {
            return "工具执行出错:" + e.getMessage();
        }
    }

    // ============================================
    //  6 个工具方法:每个就是一个普通的 Java 方法
    // ============================================

    /** 工具 1:列出目录里的所有文件 */
    private static String toolListFiles(String path) {
        File dir = new File(path);
        if (!dir.exists() || !dir.isDirectory())
            return "目录不存在:" + path;

        File[] files = dir.listFiles();
        if (files == null || files.length == 0)
            return "目录为空:" + path;

        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        StringBuilder sb = new StringBuilder();
        sb.append("共 ").append(files.length).append(" 个项目:\n");
        for (File f : files) {
            sb.append(f.isDirectory() ? "[目录]" : "[文件]")
              .append(" ").append(f.getName())
              .append(" | ").append(formatSize(f.length()))
              .append(" | ").append(fmt.format(new Date(f.lastModified())))
              .append("\n");
        }
        return sb.toString();
    }

    /** 工具 2:创建文件夹 */
    private static String toolCreateFolder(String path) {
        File folder = new File(path);
        if (folder.exists()) return "文件夹已存在:" + path;
        return folder.mkdirs()
            ? "已创建:" + path
            : "创建失败,请检查权限:" + path;
    }

    /** 工具 3:移动文件 */
    private static String toolMoveFile(String source, String destination) {
        File src = new File(source);
        if (!src.exists()) return "源文件不存在:" + source;

        File dest = new File(destination);
        File destDir = dest.getParentFile();
        if (destDir != null && !destDir.exists()) destDir.mkdirs();

        try {
            Files.move(src.toPath(), dest.toPath(),
                      StandardCopyOption.REPLACE_EXISTING);
            return "移动成功:" + src.getName() + " → " + destination;
        } catch (Exception e) {
            return "移动失败:" + e.getMessage();
        }
    }

    /** 工具 4:重命名 */
    private static String toolRenameFile(String path, String newName) {
        File file = new File(path);
        if (!file.exists()) return "文件不存在:" + path;

        File renamed = new File(file.getParent(), newName);
        if (renamed.exists()) return "目标名已存在:" + newName;

        return file.renameTo(renamed)
            ? "重命名成功:" + file.getName() + " → " + newName
            : "重命名失败,请检查权限";
    }

    /** 工具 5:删除(需要 confirm=true) */
    private static String toolDeleteFile(String path, boolean confirm) {
        if (!confirm) return "未确认,取消删除";
        File file = new File(path);
        if (!file.exists()) return "文件不存在:" + path;
        if (file.isDirectory()) {
            deleteDir(file);
            return "已删除目录:" + path;
        }
        return file.delete()
            ? "已删除:" + path
            : "删除失败";
    }
    private static void deleteDir(File dir) {
        File[] items = dir.listFiles();
        if (items != null)
            for (File f : items)
                if (f.isDirectory()) deleteDir(f); else f.delete();
        dir.delete();
    }

    /** 工具 6:根据扩展名判断文件类型 */
    private static String toolGetFileType(String filename) {
        String ext = "";
        int dot = filename.lastIndexOf('.');
        if (dot > 0 && dot < filename.length() - 1)
            ext = filename.substring(dot + 1).toLowerCase();

        return switch (ext) {
            case "doc","docx","pdf","txt","rtf",
                 "xls","xlsx","ppt","pptx","csv"
                -> "文档";
            case "jpg","jpeg","png","gif","bmp",
                 "svg","webp","ico","tiff"
                -> "图片";
            case "mp4","avi","mov","mkv","wmv","flv"
                -> "视频";
            case "mp3","wav","flac","aac","ogg","m4a"
                -> "音频";
            case "java","py","js","ts","html","css",
                 "cpp","c","go","rs","rb","php","sh","sql",
                 "json","xml","yaml","yml"
                -> "代码";
            case "zip","rar","7z","tar","gz","bz2"
                -> "压缩包";
            case "exe","msi","app","dmg","deb","rpm"
                -> "安装包";
            default -> "其他";
        };
    }

    // ============================================
    //  构建发送给大模型的请求体
    // ============================================
    private static JsonObject buildRequestBody(
            List<Map<String, Object>> messages) {
        JsonObject body = new JsonObject();
        body.addProperty("model", MODEL);
        body.add("messages", gson.toJsonTree(messages));

        // 工具定义:把 JSON 字符串解析后组成 JSON 数组
        JsonArray tools = new JsonArray();
        for (String def : TOOL_DEFINITIONS)
            tools.add(JsonParser.parseString(def));
        body.add("tools", tools);

        return body;
    }

    // ============================================
    //  调用大模型 API
    // ============================================
    private static JsonObject callLlm(JsonObject body) {
        try {
            HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(API_URL))
                .header("Content-Type", "application/json")
                .header("Authorization", "Bearer " + API_KEY)
                .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
                .build();

            HttpResponse<String> resp = HttpClient.newHttpClient()
                .send(req, HttpResponse.BodyHandlers.ofString());

            return JsonParser.parseString(resp.body()).getAsJsonObject();
        } catch (Exception e) {
            throw new RuntimeException("调用大模型失败:" + e.getMessage(), e);
        }
    }

    // ============================================
    //  工具方法:格式化文件大小
    // ============================================
    private static String formatSize(long bytes) {
        if (bytes < 1024) return bytes + " B";
        if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
        if (bytes < 1024L * 1024 * 1024)
            return String.format("%.1f MB", bytes / (1024.0 * 1024));
        return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024));
    }
}

就是这一个文件,大约 400 行。你可以复制粘贴,改一下 API_KEY,编译运行。

第四部分:它是怎么运转的

整个程序的核心只有 一个循环——agentLoop() 方法。让我用一个具体场景解释它的工作过程。

假设你对它说:"帮我整理桌面,路径是 /tmp/test-desktop"

第一轮:扫描

  1. Agent 把你的消息和工具定义发给大模型
  2. 大模型回复:"我需要先扫描目录",并请求调用 list_files(path="/tmp/test-desktop")
  3. Agent 执行工具,得到文件列表
  4. 工具结果被送回对话历史
text
🔧 list_files → 共 9 个项目:
  [文件] report.pdf | 156 KB | 2025-05-10
  [文件] notes.txt | 3.2 KB | 2025-05-15
  [文件] photo1.jpg | 4.8 MB | 2025-04-01
  [文件] demo.py | 2.1 KB | 2025-05-16
  ...

第二轮:制定方案

大模型看到了文件列表,思考后回复一个整理方案,并等你确认(这是系统提示要求的)。

第三轮起:执行

你说"可以",大模型开始连续调用工具:create_folder 创建分类目录,然后 move_file 逐个移动文件。每一轮它都能看到上一步的结果,所以如果有文件移动失败,它会自动处理。

最后一轮:汇报

大模型判断活干完了,返回一段整理报告——不再是工具调用,而是纯文本。循环结束。

关键设计:为什么不让程序自动执行,而要等用户确认?

因为移动和删除文件是不可逆操作。通过在系统提示里要求"先告知方案,等用户确认",我们在大模型层面加了一道安全阀。这比在代码里写 if 判断更灵活——大模型能理解什么情况需要确认,什么情况可以直接做。

第五部分:实际运行效果

准备测试目录

bash
mkdir -p /tmp/test-desktop
touch /tmp/test-desktop/{report.pdf,notes.txt,data.xlsx}
touch /tmp/test-desktop/{photo.jpg,screenshot.png}
touch /tmp/test-desktop/demo.mp4
touch /tmp/test-desktop/{Main.java,index.html}
touch /tmp/test-desktop/mystery.xyz

运行效果

text
桌面整理 Agent 已启动
目标目录:/tmp/test-desktop

你:帮我整理一下这个目录
  🔧 list_files → 共 9 个项目:[文件] report.pdf | 156 KB ...
助手:我扫描了目录,发现 9 个文件。建议按以下方式分类:
  📁 文档:report.pdf, notes.txt, data.xlsx
  📁 图片:photo.jpg, screenshot.png
  📁 视频:demo.mp4
  📁 代码:Main.java, index.html
  📁 其他:mystery.xyz
是否继续?

你:可以
  🔧 create_folder → 已创建:/tmp/test-desktop/文档
  🔧 create_folder → 已创建:/tmp/test-desktop/图片
  🔧 create_folder → 已创建:/tmp/test-desktop/视频
  🔧 create_folder → 已创建:/tmp/test-desktop/代码
  🔧 create_folder → 已创建:/tmp/test-desktop/其他
  🔧 move_file → 移动成功:report.pdf → /tmp/test-desktop/文档/report.pdf
  🔧 move_file → 移动成功:photo.jpg → /tmp/test-desktop/图片/photo.jpg
  ...
助手:整理完成!创建了 5 个文件夹,移动了 9 个文件。

整理后的目录结构

text
/tmp/test-desktop/
├── 文档/
│   ├── report.pdf
│   ├── notes.txt
│   └── data.xlsx
├── 图片/
│   ├── photo.jpg
│   └── screenshot.png
├── 视频/
│   └── demo.mp4
├── 代码/
│   ├── Main.java
│   └── index.html
└── 其他/
    └── mystery.xyz

第六部分:代码拆解——三个核心

整个程序 400 行,但真正需要你理解的核心只有三个部分。

核心 1:工具定义(写死的 JSON)

TOOL_DEFINITIONS 是一个 JSON 字符串列表,每个元素描述一个工具的名字、用途和参数。这些 JSON 会原样发给大模型,告诉它"你可以用这些工具"。

格式是 OpenAI Function Calling 标准,几乎所有主流大模型都兼容这个格式。你只需要写一次,换任何模型都能用。

核心 2:工具执行(switch 分发)

executeTool() 就是一个 switch-case:大模型说调 list_files,就调 Java 的 toolListFiles() 方法;说调 move_file,就调 toolMoveFile()

每个工具方法就是一个普通的 Java 方法,读参数、操作文件、返回字符串。没有任何特殊接口,没有任何继承关系。

核心 3:Agent 循环(agentLoop)

这是最精彩的部分。整个逻辑可以浓缩为一句话:

调大模型 → 有工具调用就执行 → 把结果送回去 → 再调。直到大模型不再需要调工具。

这个循环之所以强大,是因为大模型在每一轮都能看到所有历史消息——包括之前的工具调用和工具结果。这意味着它能做连续的、有上下文的决策

  • 看到文件列表 → 决定分类方案
  • 看到文件夹创建成功 → 开始移动文件
  • 看到某个文件移动失败 → 自动跳过或重试
  • 看到所有文件都处理完 → 生成整理报告

这就是 Agent 的"自循环、自验证"——它不需要你写 if-else 来规定流程,它自己看着办。

第七部分:扩展与优化

我们的桌面整理 Agent 已经可以工作了,但还有很多可以改进的地方。我来说几个例子,然后你可以试着实现,当然你也可以举一反三。自由的给他扩展完善。

还记得我最早学编程的时候,老师讲到一半,我脑海里总是会冒出各种稀奇古怪的花式玩法,总是迫不及待的想试一试。现在我把这个机会留给你,你可以试着实现下面的迭代。

支持交互式确认

如果你想让 Agent 在执行每个移动操作前都先问你,可以在系统提示里加上:

text
每移动一个文件前,先告诉用户你要做什么,等用户说"可以"再执行。

一个提示词的改变,行为就完全不同——这就是大模型驱动的灵活性。

添加撤销功能

你可以在工具执行后记录日志,然后添加一个 "undo_move" 工具,让大模型在用户说"撤销"时,把文件移回原来的位置。

支持自定义分类

你可以在系统提示里加上用户偏好:

text
用户的工作文件以"周报"、"会议"开头,请把它们归到"工作"分类,而不是"文档"。

不需要写代码,只需要修改提示词,大模型就会照做。

性能优化

对于大量文件(几百个以上),可以考虑:

  • 并行移动文件(使用 ExecutorService
  • 批量处理(一次请求移动多个文件)
  • 只处理变化的文件(增量扫描)

这个 400 行的程序是一个完整的 Agent。你已经掌握了 Agent 开发的核心套路:

  1. 定义工具 —— 写 JSON 描述 + 实现 Java 方法
  2. 写循环 —— 调大模型 → 执行工具 → 送结果 → 再调
  3. 结束 —— 大模型返回纯文本,循环退出

同样的套路,你可以做很多事:

  • toolListFiles 换成查数据库 → 数据库查询 Agent
  • 加上发送邮件的工具 → 邮件助理 Agent
  • 加上读写 Excel 的工具 → 数据分析 Agent
  • 加上调 GitHub API 的工具 → 代码审查 Agent

变化的只是工具,不变的是那个循环。


最后的话

回到最初的问题:一个大模型能帮你做什么?

在没有工具之前,它只能帮你"想"。有了工具之后,它能帮你"做"。而把这两样连起来的,就是一个不到 400 行的循环。

这不是什么高深的架构,不是什么复杂的框架。它就是一个循环、几个方法、一堆 JSON。但就是这么朴素的东西,让一个 AI 真正走进了你的电脑,帮你做了一件具体的事。

去试试吧。你的桌面也在等你。


动手试试吧! 当你看到你的桌面被自动整理得井井有条时,你会感受到一种奇妙的成就感。那不仅仅是技术的成功,更是你用心创造的价值。