2026 年了,Java 程序员还不会 AI Agent?从原理到实战,用 LangChain4j 构建企业级智能体

前言

2025 年被称为 “AI Agent 元年”,2026 年 Agent 已经从概念验证走向了生产落地。然而笔者在技术社区观察到,大量 Java 开发者对 Agent 的理解仍停留在 “调用大模型 API 聊天” 的阶段。本文将从 底层原理 出发,层层递进,带你用 LangChain4j + Spring Boot 构建一个具备 工具调用、RAG 知识检索、记忆管理 能力的完整 AI Agent。


一、从 ChatBot 到 Agent:一次认知升级

1.1 为什么我们需要 Agent?

回顾大模型应用的演进路径,我们可以清晰地看到三个阶段:

第一阶段:Prompt → Response(纯问答)

这是最简单的形态。用户输入一段文字,大模型返回一段回答。整个过程是 无状态的——模型不记得你上一句说了什么,也无法与外部世界交互。这本质上是一个"文本接龙"游戏。

第二阶段:ChatBot(带上下文的多轮对话)

通过将历史对话拼接进 Prompt,模型获得了"记忆"。但它的本质能力没有变——仍然只是文本生成。它无法查询数据库、无法调用 API、无法帮你执行任何实际操作。

第三阶段:AI Agent(自主决策的智能体)

Agent 的核心突破在于:大模型不再只是"回答问题",而是成为"大脑"——它理解你的意图,规划执行步骤,调用外部工具,并根据结果动态调整策略。

一个直观的类比:ChatBot 像一个只会纸上谈兵的顾问,而 Agent 像一个有手有脚、能干活的员工。

1.2 Agent 的核心架构

一个完整的 Agent 由四个核心组件构成:

┌─────────────────────────────────────────────┐
│                  AI Agent                   │
│                                             │
│  ┌─────────┐  ┌───────┐  ┌──────────────┐  │
│  │  LLM    │  │ Tools │  │   Memory     │  │
│  │ (大脑)  │  │ (手脚) │  │  (记忆系统)  │  │
│  └────┬────┘  └───┬───┘  └──────┬───────┘  │
│       │           │             │           │
│       └───────────┼─────────────┘           │
│                   │                         │
│           ┌───────┴───────┐                 │
│           │  Planner      │                 │
│           │  (规划引擎)   │                 │
│           └───────────────┘                 │
└─────────────────────────────────────────────┘
  • LLM(大脑):负责理解用户意图、推理决策。它是 Agent 的"思考中枢"。
  • Tools(工具集):Agent 与外部世界交互的手段。可以是数据库查询、API 调用、文件操作等。
  • Memory(记忆):让 Agent 在多轮对话中保持连贯性,包括短期记忆(当前对话)和长期记忆(跨会话的知识积累)。
  • Planner(规划器):决定 Agent 的执行策略——先做什么、后做什么、是否需要重新规划。

1.3 Agent 的推理模式:ReAct 循环

目前最主流的 Agent 推理模式叫做 ReAct(Reasoning + Acting),其核心思想是:想一步,做一步,观察结果,再决定下一步。

一个 ReAct 循环的完整过程如下:

用户输入: "A栋办公楼今天能耗异常吗?如果异常就通知张工"

[Thought 1] 用户想了解A栋的能耗情况,我需要先查询今天的实时数据
[Action 1]  调用工具 → queryRealtimeEnergy("A栋办公楼")
[Observe 1] 返回结果: 3856.2 kWh

[Thought 2] 3856 kWh 看起来比较高,我需要对比历史趋势来判断是否异常
[Action 2]  调用工具 → queryEnergyTrend("A栋办公楼", 7)
[Observe 2] 返回结果: "过去7天平均能耗 2100 kWh,今日明显偏高"

[Thought 3] 确认异常,用户要求通知张工
[Action 3]  调用工具 → sendAlert("张工", "A栋今日能耗3856kWh,超出均值84%")
[Observe 3] 返回结果: "通知发送成功"

[Thought 4] 所有步骤已完成,可以总结回复用户
[Final]     A栋办公楼今日能耗 3856.2 kWh,超出近7天均值84%,已通知张工处理。

这就是 Agent 和普通 ChatBot 的根本区别:Agent 具备自主推理和链式行动的能力。理解了这个原理,后面的代码实现就不再神秘了。


二、技术选型深度对比

2.1 Java 生态 AI 框架横评

在动手之前,我们有必要理清 Java 生态中几个主流 AI 框架的定位和差异:

维度Spring AILangChain4jSemantic Kernel
背景Spring 官方社区驱动(LangChain Java 移植)微软
Agent 能力基础 Function Calling完整 ReAct Agent + AiServices有但更新慢
RAG 支持基础完善(多种 Splitter/Retriever)基础
模型适配主流模型20+ 模型供应商主要 Azure
学习曲线低(Spring 用户友好)
社区活跃度非常高

选择 LangChain4j 的核心理由

  1. Agent 抽象最优雅:通过 @Tool 注解 + AiServices 代理模式,开发者只需定义接口,框架自动处理 Tool Calling 的序列化/反序列化、ReAct 循环、错误重试等复杂逻辑。
  2. RAG 开箱即用:内置 10+ 文档切分策略、支持多种向量数据库,RAG 集成只需几行代码。
  3. 模型无关:OpenAI、Anthropic、Ollama、国产大模型(通义、文心、智谱)都能无缝切换。

2.2 Maven 依赖配置

<dependencies>
    <!-- LangChain4j 核心:Agent、Tool、Memory 等基础能力 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>

    <!-- 大模型适配层:封装了与 OpenAI 兼容 API 的通信逻辑 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>

    <!-- Spring Boot Starter:自动配置 Bean,简化集成 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>

    <!-- 内嵌向量存储:基于内存实现,适合开发测试阶段 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-embedding-store-inmemory</artifactId>
        <version>1.0.0-beta3</version>
    </dependency>
</dependencies>

三、Tool Calling 深度解析:让大模型长出"手脚"

3.1 Tool Calling 的底层原理

很多人好奇:大模型是一段文本生成模型,它怎么"调用"Java 方法的?

其实原理并不复杂,核心是一个 协议约定 + 循环调度 的机制:

第一步:工具描述注入 System Prompt

当你用 @Tool 标注一个方法后,框架会自动将方法的 名称、描述、参数类型 转换成 JSON Schema 格式,注入到发送给大模型的 Prompt 中。大模型看到的并不是 Java 代码,而是类似这样的描述:

{
  "name": "queryRealtimeEnergyConsumption",
  "description": "查询指定区域的实时能耗数据,返回单位为 kWh",
  "parameters": {
    "type": "object",
    "properties": {
      "areaName": {
        "type": "string",
        "description": "areaName"
      }
    },
    "required": ["areaName"]
  }
}

第二步:大模型返回"调用意图"而非普通文本

当大模型判断需要调用某个工具时,它返回的不是普通文本,而是一个结构化的 Tool Call 请求

{
  "tool_calls": [{
    "function": {
      "name": "queryRealtimeEnergyConsumption",
      "arguments": "{\"areaName\": \"A栋办公楼\"}"
    }
  }]
}

第三步:框架执行实际方法,结果回传大模型

LangChain4j 框架拦截到这个 Tool Call 请求后:

  1. 通过反射找到对应的 Java 方法
  2. 反序列化参数,执行方法
  3. 将执行结果作为新消息追加到对话历史中
  4. 再次调用大模型,让它根据工具返回的结果继续推理

这个"请求-执行-回传"的循环就是 ReAct 的核心。 大模型可能需要多轮 Tool Call 才能完成一个复杂任务,框架会自动管理这个循环,直到大模型认为任务完成、返回最终文本。

3.2 定义工具:用注解描述能力

理解了原理后,代码实现就很直观了。@Tool 注解中的描述文字 至关重要——它是大模型理解工具用途的唯一依据。描述越精确,大模型的调用决策就越准确。

@Component
public class EnergyQueryTools {

    @Tool("查询指定区域的实时能耗数据,返回数值单位为 kWh。" +
          "当用户询问某个区域'当前能耗'、'今天用了多少电'等问题时调用此工具。")
    public double queryRealtimeEnergyConsumption(String areaName) {
        // 实际项目中调用数据库或第三方能耗采集 API
        Map<String, Double> mockData = Map.of(
            "A栋办公楼", 1523.5,
            "B栋研发中心", 2891.2,
            "C栋数据中心", 8734.6
        );
        return mockData.getOrDefault(areaName, 0.0);
    }

    @Tool("查询指定区域过去 N 天的能耗趋势分析,返回趋势描述文本。" +
          "当用户想了解'能耗趋势'、'最近用电变化'等问题时调用。")
    public String queryEnergyTrend(String areaName, int days) {
        return String.format("%s 过去 %d 天平均能耗呈%s趋势",
            areaName, days, days > 7 ? "上升" : "平稳");
    }

    @Tool("向指定负责人发送能耗预警通知,返回发送结果。" +
          "仅在能耗确实异常且用户明确要求通知时调用。")
    public String sendAlertNotification(String recipient, String message) {
        System.out.printf("[ALERT] To: %s, Message: %s%n", recipient, message);
        return "通知发送成功";
    }
}

关于 @Tool 描述的三条经验法则

  1. 说清楚"什么时候该用":不要只写"查询能耗",要写"当用户询问当前能耗、用电量等问题时调用"。
  2. 说清楚"返回什么":明确告诉模型返回值是数值、文本还是布尔值,以及单位是什么。
  3. 说清楚"什么时候不该用":对于高危操作(如删除数据),明确标注使用条件。

3.3 构建 Agent:AiServices 代理模式

LangChain4j 最精妙的设计之一是 AiServices——它采用类似 MyBatis Mapper 的代理模式:你定义一个 Java 接口,框架在运行时动态生成实现类,自动处理 Tool Calling 循环。

/**
 * 能源管理 Agent 接口
 * 
 * 工作原理:
 * 1. AiServices 在运行时为此接口生成动态代理
 * 2. 当调用 chat() 时,代理将用户消息 + 系统提示词 + 工具描述一起发送给大模型
 * 3. 如果大模型返回 Tool Call,代理自动执行对应方法并循环调用大模型
 * 4. 直到大模型返回最终文本,代理将其作为方法返回值
 */
public interface EnergyAgent {

    @SystemMessage("""
        你是一个智慧能源管理助手,负责帮助用户分析和管理能源消耗。
        
        你的能力:
        1. 查询任意区域的实时能耗数据
        2. 分析指定时间段的能耗趋势
        3. 在能耗异常时向负责人发送预警通知
        
        行为准则:
        - 先查数据,再下结论。不要凭猜测回答。
        - 数据回答必须带单位(kWh)。
        - 发送通知前,必须确认用户有此意图。
        """)
    String chat(@UserMessage String userMessage);
}

为什么用接口而非具体类? 这是典型的"控制反转"思想。你只声明"Agent 应该做什么"(通过注解),而"怎么做的"(ReAct 循环、工具调度、错误重试)全部交给框架。这与 Spring 的 DI 理念一脉相承。

3.4 组装与暴露

@Configuration
public class AgentConfig {

    @Bean
    public EnergyAgent energyAgent(EnergyQueryTools tools) {
        OpenAiChatModel model = OpenAiChatModel.builder()
            .apiKey(System.getenv("LLM_API_KEY"))
            .baseUrl(System.getenv("LLM_BASE_URL"))  // 支持国产模型代理
            .modelName("gpt-4o")
            .temperature(0.3)   // Agent 场景用低 temperature,减少"创意",增强"准确"
            .build();

        return AiServices.builder(EnergyAgent.class)
            .chatLanguageModel(model)
            .tools(tools)        // 注册工具集
            .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
            .build();
    }
}

@RestController
@RequestMapping("/api/agent")
public class AgentController {

    @Autowired
    private EnergyAgent energyAgent;

    @PostMapping("/chat")
    public ResponseEntity<String> chat(@RequestBody ChatRequest request) {
        return ResponseEntity.ok(energyAgent.chat(request.getMessage()));
    }
}

四、RAG 原理与实现:让 Agent 拥有"私域知识"

4.1 为什么需要 RAG?

大模型的知识有两个致命缺陷:

  1. 知识截止日期:模型训练数据有时效性,无法知道最新的信息。
  2. 缺乏私域知识:你公司的《能源管理手册》、内部规章制度、设备参数——这些模型从未见过。

RAG(Retrieval-Augmented Generation,检索增强生成) 的核心思想非常朴素:在提问之前,先从知识库中检索出相关文档片段,把它和用户问题一起交给大模型,让模型"开卷考试"。

4.2 RAG 的完整链路

           【离线索引阶段】
           
企业文档 ──→ 文档切分 ──→ Embedding向量化 ──→ 存入向量数据库
 (PDF/Word)   (Chunking)    (文本→数值向量)      (Milvus等)


           【在线检索阶段】
           
用户提问 ──→ Query向量化 ──→ 向量相似度检索 ──→ Top-K相关片段
                                                     │
                                                     ▼
                              LLM ←── 相关片段 + 用户问题
                               │
                               ▼
                          带上下文的专业回答

4.3 关键技术点解析

文档切分(Chunking):为什么不能直接存整篇文档?

这是一个常见的初学者疑问。原因有三:

  1. Token 限制:大模型的上下文窗口有限(通常 4K~128K tokens),塞不下整篇文档。
  2. 检索精度:文档越长,包含的主题越杂。短片段能让检索更精准地命中相关内容。
  3. 噪声控制:只给模型最相关的片段,避免无关信息干扰模型的判断。

LangChain4j 提供了多种切分策略,最常用的是 递归切分:先尝试按段落分,段落太大再按句子分,句子太大再按字符分,直到每个片段控制在目标大小内。

// 目标: 每个片段约 500 token,相邻片段重叠 50 token
// 重叠的目的:防止一句话被从中间切断,导致上下文丢失
DocumentSplitter splitter = DocumentSplitters
    .recursive(500, 50);
Embedding 向量化:文本如何变成"可比较的数字"?

Embedding 是 RAG 最核心的技术。 它的本质是:将一段文本映射到高维向量空间中的一个点。语义相似的文本,在向量空间中的距离更近。

举例来说,“今天用电量很高” 和 “今日能耗偏大” 这两句话虽然用词不同,但语义相近,它们的向量余弦相似度可能达到 0.92(满分 1.0)。而 “今天用电量很高” 和 “明天天气不错” 的相似度可能只有 0.15。

这就是向量检索的数学基础:将"语义匹配"问题转化为"向量距离计算"问题。

EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
    .apiKey(System.getenv("LLM_API_KEY"))
    .modelName("text-embedding-3-small")  // 1536维向量,性价比最优
    .build();

// 将文本转换为向量
Embedding vector = embeddingModel.embed("今天A栋能耗偏高").content();
// vector → [0.023, -0.156, 0.892, ..., 0.041]  (1536个浮点数)
向量检索:在百万文档中毫秒级找到相关内容

向量数据库的作用类似于传统数据库的索引,但它是为高维向量相似度搜索而优化的。常用的向量数据库有 Milvus、Pinecone、Elasticsearch(8.x+ 支持向量检索)等。

开发阶段可以用内存实现,生产环境必须换成持久化方案:

// 开发阶段:内存向量存储(重启丢失数据)
EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();

// 生产阶段:持久化向量存储
// EmbeddingStore<TextSegment> store = MilvusEmbeddingStore.builder()
//     .uri("http://milvus:19530")
//     .collectionName("energy_docs")
//     .build();

4.4 完整 RAG 服务实现

@Service
public class RagService {

    private final EmbeddingStore<TextSegment> embeddingStore;
    private final EmbeddingModel embeddingModel;

    public RagService() {
        this.embeddingStore = new InMemoryEmbeddingStore<>();
        this.embeddingModel = OpenAiEmbeddingModel.builder()
            .apiKey(System.getenv("LLM_API_KEY"))
            .modelName("text-embedding-3-small")
            .build();
    }

    /**
     * 离线索引:将企业文档加载到向量库
     * 
     * 处理流程:
     * 1. 解析 PDF/Word 为纯文本
     * 2. 按语义边界切分成片段(避免断句)
     * 3. 每个片段生成 Embedding 向量
     * 4. 向量 + 原文一起存入向量数据库
     */
    @PostConstruct
    public void loadDocuments() {
        // 从 PDF 加载文档
        Document document = FileSystemDocumentLoader.loadDocument(
            Path.of("docs/energy-management-handbook.pdf"),
            new ApachePdfBoxDocumentParser()
        );

        // 递归切分:500 token / 片段,50 token 重叠
        DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
        List<TextSegment> segments = splitter.split(document);

        // 批量向量化(比逐条调用快 10~50 倍)
        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

        // 存入向量库
        embeddingStore.addAll(embeddings, segments);
        
        log.info("文档索引完成,共 {} 个片段", segments.size());
    }

    /**
     * 在线检索:根据用户问题检索相关文档片段
     * 
     * @param query      用户问题
     * @param maxResults 返回的最大片段数(通常 3~5 个)
     * @return 相关文档片段列表
     */
    public List<TextSegment> search(String query, int maxResults) {
        Embedding queryEmbedding = embeddingModel.embed(query).content();

        // 相似度阈值 0.7:低于此值的片段被视为不相关
        // 阈值设置过低 → 引入噪声;过高 → 可能漏掉有用信息
        List<EmbeddingSearchResult<TextSegment>> results = 
            embeddingStore.findRelevant(queryEmbedding, maxResults, 0.7);

        return results.stream()
            .map(EmbeddingSearchResult::embeddedObject)
            .toList();
    }

    // getter 供 Agent 配置使用
    public EmbeddingStore<TextSegment> getEmbeddingStore() { return embeddingStore; }
    public EmbeddingModel getEmbeddingModel() { return embeddingModel; }
}

4.5 将 RAG 挂载到 Agent

这一步非常简洁。LangChain4j 的 ContentRetriever 机制会在每次对话前 自动执行 RAG 检索,将相关文档片段注入到 Prompt 中:

// 构建内容检索器
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
    .embeddingStore(ragService.getEmbeddingStore())
    .embeddingModel(ragService.getEmbeddingModel())
    .maxResults(3)       // 每次最多检索 3 个片段
    .minScore(0.7)       // 最低相似度阈值
    .build();

// 挂载到 Agent
EnergyAgent agent = AiServices.builder(EnergyAgent.class)
    .chatLanguageModel(model)
    .tools(tools)
    .contentRetriever(retriever)   // ← 这一行就是 RAG 集成的全部
    .chatMemory(MessageWindowChatMemory.withMaxMessages(20))
    .build();

背后的执行流程:每次用户提问时,Agent 会先调用 retriever 检索相关文档,将检索结果和用户问题一起拼接成增强 Prompt 发送给大模型。大模型就能基于你的私域知识来回答了。


五、记忆系统:让 Agent 真正"认识"用户

5.1 为什么记忆如此重要?

没有记忆的 Agent,每次对话都像第一次见面。用户说"它太高了",Agent 不知道"它"指的是什么。用户上午说了"我在关注 B 栋",下午再问"B 栋怎么样了",Agent 已经忘了这回事。

记忆系统从层次上分为两类:

  • 短期记忆(Working Memory):当前对话会话内的历史消息。类似于人的"工作记忆",容量有限但存取极快。
  • 长期记忆(Long-term Memory):跨会话持久化的用户偏好、历史总结。类似于人的"长期记忆",容量大但需要显式检索。

5.2 短期记忆的窗口策略

最直接的做法是维护一个"消息窗口"。常见策略有两种:

策略一:消息数窗口 — 简单粗暴,保留最近 N 条消息。优点是实现简单,缺点是不同消息的 Token 长度差异很大,可能导致实际上下文忽大忽小。

ChatMemory memory = MessageWindowChatMemory.withMaxMessages(20);

策略二:Token 窗口 — 按 Token 总数限制,更精确地控制发送给模型的数据量。适合对成本敏感的生产环境。

ChatMemory memory = TokenWindowChatMemory.builder()
    .maxTokens(4000)
    .tokenizer(new OpenAiTokenizer())
    .build();

5.3 持久化记忆:重启不丢失

在生产环境中,用户的对话历史必须持久化。否则服务一重启,所有上下文全部丢失。LangChain4j 通过 ChatMemoryStore 接口提供了持久化的扩展点:

/**
 * 数据库持久化记忆存储
 * 
 * 设计思路:
 * - 每个会话(sessionId)对应一组消息
 * - 每次 updateMessages 时,全量替换该会话的消息列表
 * - 消息序列化后存储为 JSON 文本
 */
public class DatabaseChatMemoryStore implements ChatMemoryStore {

    @Autowired
    private ChatMemoryRepository repository;

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        return repository.findBySessionId(memoryId.toString())
            .stream()
            .map(this::deserialize)
            .toList();
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        // 先删后插:简单可靠的全量替换策略
        repository.deleteBySessionId(memoryId.toString());
        messages.forEach(msg -> repository.save(
            new ChatMemoryEntity(memoryId.toString(), serialize(msg))
        ));
    }

    @Override
    public void deleteMessages(Object memoryId) {
        repository.deleteBySessionId(memoryId.toString());
    }
    
    private String serialize(ChatMessage message) {
        return JsonUtils.toJson(message);
    }
    
    private ChatMessage deserialize(ChatMemoryEntity entity) {
        return JsonUtils.fromJson(entity.getContent(), ChatMessage.class);
    }
}

使用持久化记忆:

ChatMemory persistentMemory = MessageWindowChatMemory.builder()
    .id("user-12345-session")   // 会话唯一标识
    .maxMessages(50)
    .chatMemoryStore(new DatabaseChatMemoryStore())
    .build();

六、生产环境的工程化考量

6.1 超时与容错

大模型 API 的响应时间波动很大(从 1 秒到 30 秒不等),生产环境必须做好容错:

OpenAiChatModel model = OpenAiChatModel.builder()
    .apiKey(System.getenv("LLM_API_KEY"))
    .modelName("gpt-4o")
    .timeout(Duration.ofSeconds(60))   // 单次请求超时
    .maxRetries(3)                      // 自动重试(含指数退避)
    .logRequests(true)                  // 开发调试利器
    .logResponses(true)
    .build();

6.2 成本控制:Token 用量监控

大模型 API 按 Token 计费。在 Agent 场景下,一次用户对话可能触发多轮 Tool Calling,实际消耗的 Token 远超表面看到的问答。因此必须做好监控:

Response<AiMessage> response = model.generate(messages);
TokenUsage usage = response.tokenUsage();

log.info("[Token统计] Prompt: {}, Completion: {}, Total: {}",
    usage.inputTokenCount(),
    usage.outputTokenCount(),
    usage.totalTokenCount());

// 生产环境建议:将 Token 用量上报到监控系统(如 Prometheus)
// metricsService.recordTokenUsage("energy-agent", usage.totalTokenCount());

6.3 安全红线:工具权限分级

Tool Calling 赋予了大模型调用系统接口的能力,这也带来了安全风险。建议对工具进行权限分级:

// 只读工具:所有用户可用
@Tool("查询指定区域的实时能耗数据")
public double queryRealtimeEnergy(String areaName) { ... }

// 写入工具:需要操作权限
@Tool("调整指定区域的能耗告警阈值(需要管理员权限)")
public String updateAlertThreshold(String areaName, double threshold) {
    SecurityUtils.checkRole("ADMIN");  // 权限校验
    ...
}

// 危险工具:需要二次确认
@Tool("删除指定区域的历史能耗数据(不可逆操作)")
public String deleteHistoricalData(String areaName, String dateRange) {
    // 高危操作必须有人工确认环节
    ...
}

6.4 性能优化:流式输出

Agent 推理过程可能耗时较长(5~15 秒),为了不让用户"干等",可以采用流式输出,让回答逐字呈现:

public interface StreamingEnergyAgent {

    @SystemMessage("你是智慧能源管理助手...")
    TokenStream chat(@UserMessage String userMessage);
}

// Controller 中使用 SSE 推送
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(@RequestParam String message) {
    SseEmitter emitter = new SseEmitter();
    
    TokenStream tokenStream = streamingAgent.chat(message);
    tokenStream.onNext(token -> {
        try { emitter.send(token); } catch (IOException e) { /* ... */ }
    });
    tokenStream.onComplete(response -> emitter.complete());
    tokenStream.onError(emitter::completeWithError);
    
    return emitter;
}

七、架构全景图

将以上所有组件组合在一起,最终的系统架构如下:

                        ┌──────────────────────────────────┐
                        │           Spring Boot App         │
                        │                                   │
                        │  ┌─────────────┐                  │
  用户 ──── HTTP ─────→ │  │ REST API    │                  │
                        │  └──────┬──────┘                  │
                        │         │                         │
                        │  ┌──────▼──────┐                  │
                        │  │ Energy Agent│ (AiServices代理) │
                        │  └──┬───┬───┬──┘                  │
                        │     │   │   │                     │
                        │     │   │   └──→ Memory Store     │
                        │     │   │        (数据库持久化)    │
                        │     │   │                         │
                        │     │   └────→ Content Retriever  │
                        │     │         ┌──────────────┐    │
                        │     │         │ 向量数据库    │    │
                        │     │         │ (RAG知识检索) │    │
                        │     │         └──────────────┘    │
                        │     │                             │
                        │     └────→ Tool Execution         │
                        │           ┌──────────────┐        │
                        │           │ 能耗查询工具  │        │
                        │           │ 趋势分析工具  │        │
                        │           │ 通知发送工具  │        │
                        │           └──────┬───────┘        │
                        │                  │                │
                        └──────────────────┼────────────────┘
                                           │
                            ┌──────────────▼──────────────┐
                            │     外部系统                  │
                            │  ┌──────┐ ┌──────┐ ┌─────┐  │
                            │  │ DM DB│ │  MQ  │ │ SMS │  │
                            │  └──────┘ └──────┘ └─────┘  │
                            └─────────────────────────────┘

八、总结与展望

本文我们从原理到代码,完整构建了一个企业级 AI Agent 系统。核心要点回顾:

模块核心原理关键 API
Tool Calling工具描述注入 Prompt + 循环调度@Tool + AiServices
RAG向量相似度检索 + 上下文增强ContentRetriever + EmbeddingStore
记忆管理消息窗口 + 持久化存储ChatMemory + ChatMemoryStore
工程化超时重试、成本监控、权限分级timeout/maxRetries/TokenUsage

2026 年下半年值得关注的 Agent 技术趋势

  1. Multi-Agent 协作:多个专业 Agent 通过"编排器"协同完成复杂任务。LangGraph4j 已经提供了图状态机来实现 Agent 间的协作与分支。
  2. MCP 协议标准化:Model Context Protocol 正在成为工具调用的统一标准,让 Agent 可以即插即用地接入各种工具服务。
  3. 端侧小模型 Agent:Qwen2.5-7B、Llama 3.1-8B 等小模型已具备可靠的 Tool Calling 能力,可在端侧部署,大幅降低延迟和成本。
  4. Agent 可观测性:OpenTelemetry for LLM 规范逐渐成熟,可以完整追踪 Agent 的推理链路、工具调用耗时、Token 消耗等指标。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐