在开发基于大模型的 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:向大模型解释入参的含义,帮助模型生成准确的终端指令(比如模型会根据用户的提问,自动生成ipconfig或dir等传入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 执行系统命令的权限具有一定风险,在实际生产环境中,务必做好命令的正则过滤或权限隔离(比如限制只能执行特定的白名单命令),避免恶意指令的注入。