AI Agent 是什么?详细解释附 Java 示例

本文有一个短视频:YouTubeTikTok

1. 总体概览

“AI Agent” 是到处出现的关键词,但定义常常模糊不清:有人把所有聊天机器人都叫做 agent,有人只把复杂的多步骤系统才认为是真正的 agent。这篇文章解释了 AI Agent 是什么、必须具有的各个组件,以及其背后的循环机制。读完这篇文章后,你将能够用纯 Java 构建一个最小化的 agent 来理解它的“运作原理”,然后升级到使用真正的 LLM 配合 Spring AI 的版本。

2. AI Agent 是什么?

简洁定义:AI Agent 是一个使用 LLM 作为大脑来自主决定行动以实现目标的系统,而不仅仅是回答一个问题然后停止。

与普通聊天机器人的区别:

特点 普通聊天机器人 AI Agent
处理轮数 1 轮:问 → 回答 多个步骤:观察 → 推理 → 行动 → 重复
是否调用 tool/API 通常不调用 调用,自主决定调用哪个工具
是否有长期目标 没有 有,agent 自己分解任务以达成目标
示例 “河内天气怎么样?” → 返回文本描述 “预订明天河内到胡志明市最便宜的票” → 自动搜索票价、比较、订购、确认

换句话说,LLM 只是一个“语言大脑”,它能生成文本。Agent 将这个大脑变成一个能在现实世界中采取行动(调用 API、读取文件、查询数据库)并能自主重复推理过程直到完成任务的实体。

3. AI Agent 的核心组件

一个最小化的 agent 需要 4 个组件。少了其中任何一个,系统就只是穿着“agent 外衣”的聊天机器人。

3.1. LLM – 推理大脑

LLM 接收提示(包括目标、历史记录、可用工具列表),并生成下一步决策:直接回答还是调用某个工具,用什么参数。

3.2. Tool(函数调用)– Agent 的手臂和腿

Tool 是 agent 可以调用的一个具体函数:查询订单、调用天气 API、查询数据库。这部分把 agent 从“会说话”变成了“会做事”。

public interface Tool {
    String name();
    boolean canHandle(String input);
    String execute(String input);
}

canHandle 检查该工具是否能处理当前输入;execute 执行操作并返回结果(观察)。

3.3. Memory – 记忆

Agent 需要记住之前做过什么(对话历史、已调用工具的结果)以避免重复无用的行动。Memory 可以简单到一个 List<String> 保存日志,也可以复杂到向量存储来检索相关信息。

3.4. Control loop – 控制循环

这是“引擎”部分,驱动整个过程:循环执行思考 → 行动 → 观察的步骤,直到达到目标或达到最大步数限制。下一部分将深入这个机制。

4. ReAct 循环:Agent “思考然后行动”的方式

ReAct(推理 + 行动)是最常见的 agent 循环模型:

  1. Thought:LLM 推理“我接下来需要做什么?”
  2. Action:选择一个工具并用合适的参数调用它。
  3. Observation:获得工具的结果,放回上下文中。
  4. 从第 1 步重复,直到 LLM 决定有足够信息给出最终答案,或达到 maxSteps。

为了理解这个机制而不需要调用真实 LLM(节省 API key),我们用纯 Java 构建一个迷你 agent,用 if-else 代替真实 LLM。

public class SimpleAgent {

    private final List<Tool> tools;
    private final int maxSteps;

    public SimpleAgent(List<Tool> tools, int maxSteps) {
        this.tools = tools;
        this.maxSteps = maxSteps;
    }

    public String run(String goal) {
        String observation = goal;
        for (int step = 1; step <= maxSteps; step++) {
            Tool chosen = tools.stream()
                    .filter(t -> t.canHandle(observation))
                    .findFirst()
                    .orElse(null);

            if (chosen == null) {
                return "没有工具能处理: " + observation;
            }

            System.out.printf("步骤 %d - Thought: 需要用工具 '%s'%n", step, chosen.name());
            String result = chosen.execute(observation);
            System.out.printf("步骤 %d - Observation: %s%n", step, result);

            return result; // 简单示例:一步后完成
        }
        return "达到最大步数限制 (" + maxSteps + ") 但还未完成任务。";
    }
}

maxSteps 正是循环的“预算”——必须设置以防止 agent 在永远无法达成目标时无限运行。for 循环模拟思考(选择工具)、行动(execute)、观察(结果打印到控制台)。

三个示例工具:

public class WeatherTool implements Tool {
    @Override
    public String name() {
        return "WeatherTool";
    }

    @Override
    public boolean canHandle(String input) {
        return input.toLowerCase().contains("天气");
    }

    @Override
    public String execute(String input) {
        return "河内今天 28°C,多云。";
    }
}
public class OrderStatusTool implements Tool {
    @Override
    public String name() {
        return "OrderStatusTool";
    }

    @Override
    public boolean canHandle(String input) {
        return input.toLowerCase().contains("订单");
    }

    @Override
    public String execute(String input) {
        return "订单 #1023 正在运送中,预计 2 天内送达。";
    }
}
public class CalculatorTool implements Tool {
    @Override
    public String name() {
        return "CalculatorTool";
    }

    @Override
    public boolean canHandle(String input) {
        return input.matches(".*\\d+\\s*\\+\\s*\\d+.*");
    }

    @Override
    public String execute(String input) {
        String digits = input.replaceAll("[^0-9+]", "");
        String[] parts = digits.split("\\+");
        int sum = Integer.parseInt(parts[0]) + Integer.parseInt(parts[1]);
        return "结果: " + sum;
    }
}

试试看:

SimpleAgent agent = new SimpleAgent(
        List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
        3
);

String result = agent.run("告诉我河内今天的天气");
System.out.println(result);

输出:

步骤 1 - Thought: 需要用工具 'WeatherTool'
步骤 1 - Observation: 河内今天 28°C,多云。
河内今天 28°C,多云。

写测试来确保正确选择工具并处理无工具匹配的情况:

import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;

class SimpleAgentTest {

    @Test
    void shouldPickWeatherToolWhenGoalMentionsWeather() {
        SimpleAgent agent = new SimpleAgent(
                List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
                3
        );

        String result = agent.run("告诉我河内今天的天气");

        assertThat(result).contains("河内");
    }

    @Test
    void shouldReturnErrorWhenNoToolMatches() {
        SimpleAgent agent = new SimpleAgent(List.of(new CalculatorTool()), 3);

        String result = agent.run("给我唱首歌");

        assertThat(result).startsWith("没有工具能处理");
    }
}

这个例子用 if-else 代替 LLM,所以“推理”非常死板。真实的 agent 不同之处在于:不是硬编码的 canHandle,而是 LLM 读取工具描述后自主决定调用哪个工具,并从自然语言问题中自动提取参数。

5. 用 Spring AI 和工具调用构建真实的 agent

为了看到真实 LLM 的决策过程,我们使用 Spring AI——一个将 AI 模型集成到 Spring Boot 的框架,并内置支持工具调用。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.0.0</version>
</dependency>

配置 API key 和模型:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini

用 @Tool 注解声明工具,这与之前例子中的 OrderStatusTool 角色相同,但这次真实 LLM 会调用它:

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class OrderTools {

    @Tool(description = "根据订单号查询订单状态")
    public String getOrderStatus(String orderId) {
        // 实际上会调用 OrderService / 数据库
        return "订单 " + orderId + " 正在运送中,预计 2 天内送达。";
    }
}

description 至关重要:LLM 基于这个描述来决定是否应该调用该工具以及传递什么参数。

Service 使用 ChatClient 将工具绑定到 agent:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
public class OrderAssistantService {

    private final ChatClient chatClient;

    public OrderAssistantService(ChatClient.Builder builder, OrderTools orderTools) {
        this.chatClient = builder
                .defaultTools(orderTools)
                .build();
    }

    public String ask(String question) {
        return chatClient.prompt(question).call().content();
    }
}

调用 ask("我的订单 ORD-1023 在哪里?"),Spring AI 会在背后:

  1. 将问题连同 getOrderStatus 工具的描述发送给模型。
  2. 模型返回决策“调用 getOrderStatus,参数 orderId="ORD-1023"“而不是直接回答。
  3. Spring AI 自动调用相应的 Java 方法,获取结果。
  4. 将结果再次发送给模型,让模型生成最终的自然语言答案。
你的订单 ORD-1023 目前正在运送中,预计再过 2 天就会送达。

上面的 4 个步骤正是一个完整的 ReAct 循环,唯一的区别是 Spring AI 隐藏了 for 循环,你不需要像第 4 部分的纯 Java 示例那样自己写控制循环。

注意:spring-ai-starter-model-openai 的版本和 artifact 名称可能会随时间变化;在添加依赖之前,请始终检查与你使用的 Spring Boot 版本相对应的 BOM。

6. 常见的 Agent 类型

  • 单 Agent ReAct:一个 agent,循环执行思考-行动-观察。适合中等复杂度的任务,就像上面的两个例子。
  • 计划并执行:agent 先制定详细计划(步骤列表),然后逐步执行,不像 ReAct 那样不断重新推理。适合多步骤且结构明确的任务(例如:“生成一份包含 5 个部分的报告”)。
  • 多 Agent:多个专业化的 agent 协作,通常有一个“监督者”agent 协调“工作者”agent(例如:搜索 agent、写作 agent、事实检查 agent),最后汇总结果。

选择哪种类型取决于任务的复杂度:简单任务用单 agent,涉及许多不同领域的任务(搜索+写作+检查)应该拆分成多 agent,以便更容易控制和调试每个部分。

7. 常见错误 & 何时不应该使用 Agent

  • 无限循环、燃烧 token:如果没有设置 maxSteps 或 token 预算,agent 可能会无限循环,因为 LLM“认为”任务还没完成。像第 4 部分的 SimpleAgent 一样,总是设置明确的步数限制。
  • 工具描述模糊:如果工具的 description 不清楚,LLM 容易调用错工具或传递错误的参数类型。写清楚的描述,明确说明期望的参数格式。
  • 危险操作缺少防护措施:agent 有权调用删除数据、转账等工具,没有确认步骤是个大风险。总是在执行不可逆操作前添加一层审查(人工审核或基于规则的检查)。
  • 为简单任务过度设计:如果问题只需要一次 LLM 调用就能得到答案(例如翻译一句话),就别费力搭建整个 agent 循环加工具调用。既增加延迟,又浪费钱多次调用模型。

**注意:**如果你的系统不需要调用外部操作(API、数据库),只需生成一次文本,那这是个普通 LLM 的任务——不是 agent 任务。别无谓地增加复杂度。

8. 总结

AI Agent 与普通聊天机器人的区别在于它能自主决定行动并重复推理过程直到达成目标,这基于四个组件:LLM、工具、记忆和控制循环。ReAct 循环(思考-行动-观察)是当今大多数 agent 背后的常见机制,你可以用纯 Java 模拟它来理解本质,然后在生产环境中升级到使用 Spring AI 这样的框架。最关键的是:总是设置步数限制、写清楚工具描述、为不可逆操作添加防护措施——agent 很强大,但如果没有适当的控制也容易“过度执行”。

AI AgentLLMSpring AIJavaFunction Calling