Spring AI 实战:为 AI Agent 封装一个本地终端命令执行 Tool

Spring AI 实战:为 AI Agent 封装一个本地终端命令执行 Tool

_

在开发基于大模型的 AI 应用时,让大模型拥有“行动能力”是迈向 AI Agent(智能体)的关键一步。通过 Spring AI 提供的 Function Calling(函数调用)机制,我们可以非常优雅地将本地 Java 方法暴露给 AI,让大模型根据上下文自主决定何时调用这些工具。

今天在这篇文章中,我将分享一个最近在 Spring AI 项目中封装的实用工具类:终端操作工具(TerminalOperationTool)。它的作用是赋予 AI Agent 在宿主机上执行本地终端命令的能力。

💡 设计思路与核心代码

为了让 AI 能够执行终端命令,我们本质上需要利用 Java 的 ProcessBuilder 来拉起底层的命令行进程,并将执行结果或错误信息捕获后返回给大模型。

以下是完整的工具类实现代码:

package com.okcl.heartaiagent.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * 终端操作工具类
 */
public class TerminalOperationTool {
    
    @Tool(description = "Executes a command on the terminal")
    public String executeCommand(@ToolParam(description = "The command to execute") String command) {
        try {
            // 针对 Windows 环境,使用 cmd.exe 执行命令
            ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", command);
            pb.redirectErrorStream(true); // 合并标准输出流和错误流
            Process process = pb.start();

            // 划重点:使用 GBK 编码读取 Windows 终端的输出,防止中文乱码
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"));

            StringBuilder result = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line).append("\n");
            }

            int exitCode = process.waitFor();
            if (exitCode == 0) {
                // 如果没有输出内容,返回一个默认的成功提示
                return result.toString().isEmpty() ? "Command executed successfully." : result.toString();
            } else {
                return "Command failed (Exit Code: " + exitCode + "). Output:\n" + result.toString();
            }
        } catch (Exception e) {
            return "Error executing command: " + e.getMessage();
        }
    }
}

🔍 代码细节原理解析

这段代码虽然不长,但包含了几个在实际开发中非常容易踩坑的细节:

1. Spring AI 的注解驱动

我们使用了 Spring AI 专属的 @Tool@ToolParam 注解。

  • @Tool(description = "..."):这里的描述至关重要,它是大模型理解这个工具用途的唯一依据。务必用清晰的英文或中文描述该工具的功能。

  • @ToolParam:向大模型解释入参的含义,帮助模型生成准确的终端指令(比如模型会根据用户的提问,自动生成 ipconfigdir 等传入 command 参数中)。

2. 兼容 Windows 系统的命令执行

代码中使用了 new ProcessBuilder("cmd.exe", "/c", command)。 因为很多内置命令(如 dir, echo 等)并不是独立的可执行文件,而是由 cmd.exe 解释执行的。加上 /c 参数表示执行完字符串指定的命令后终止。(注:如果你的应用需要部署在 Linux/Ubuntu 上,这里需要修改为 "/bin/sh", "-c")

3. 中文乱码处理(防坑必备)

这是本地调试时最常遇到的问题。Windows 默认终端的字符集通常是 GBK(代码页 936)。如果直接使用 Java 默认的 UTF-8 读取输入流,遇到中文提示(比如“找不到文件”)就会变成乱码。 因此,代码中特意指定了 InputStreamReader(..., "GBK"),确保大模型能接收到正常的中文反馈,从而做出正确的下一步推理。

4. 友好的异常与状态处理

  • 流合并pb.redirectErrorStream(true) 将错误流合并到了标准输出中,这样无论命令是成功还是报错,大模型都能看到完整的日志反馈。

  • 退出码判断:通过 process.waitFor() 获取退出码(0为成功,非0为失败),并将详细状态封装进返回值中。

🎯 应用场景

将这个 Bean 注册到 Spring 容器并配置给你的 AI ChatClient 后,你就可以体验到如下的魔法:

  • 用户: “帮我看看这台电脑目前的 IP 地址是多少?”

  • AI: (思考后触发 executeCommand,传入 ipconfig,读取返回结果) -> “你的 IPv4 地址是 192.168.xxx.xxx。”

  • 用户: “用 ping 测试一下百度的延迟。”

  • AI: (触发 executeCommand,传入 ping baidu.com) -> 整合输出结果反馈给用户。

结语

通过封装简单的 Java 原生 API 并结合 Spring AI 的 Tool 抽象,我们就能轻易打破大模型与宿主机之间的壁垒。当然,安全提示:赋予 AI 执行系统命令的权限具有一定风险,在实际生产环境中,务必做好命令的正则过滤或权限隔离(比如限制只能执行特定的白名单命令),避免恶意指令的注入。

滑动窗口:优雅解决 API 批量处理限制 2026-05-13
LeetCode 3:无重复字符的最长子串(滑动窗口解法) 2026-05-15

评论区