一:2026 年了,Java 程序员还不会 AI Agent?从原理到实战,用 LangChain4j 构建企业级智能体
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 AI | LangChain4j | Semantic Kernel |
|---|---|---|---|
| 背景 | Spring 官方 | 社区驱动(LangChain Java 移植) | 微软 |
| Agent 能力 | 基础 Function Calling | 完整 ReAct Agent + AiServices | 有但更新慢 |
| RAG 支持 | 基础 | 完善(多种 Splitter/Retriever) | 基础 |
| 模型适配 | 主流模型 | 20+ 模型供应商 | 主要 Azure |
| 学习曲线 | 低(Spring 用户友好) | 中 | 高 |
| 社区活跃度 | 高 | 非常高 | 低 |
选择 LangChain4j 的核心理由:
- Agent 抽象最优雅:通过
@Tool注解 +AiServices代理模式,开发者只需定义接口,框架自动处理 Tool Calling 的序列化/反序列化、ReAct 循环、错误重试等复杂逻辑。 - RAG 开箱即用:内置 10+ 文档切分策略、支持多种向量数据库,RAG 集成只需几行代码。
- 模型无关: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 请求后:
- 通过反射找到对应的 Java 方法
- 反序列化参数,执行方法
- 将执行结果作为新消息追加到对话历史中
- 再次调用大模型,让它根据工具返回的结果继续推理
这个"请求-执行-回传"的循环就是 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 描述的三条经验法则:
- 说清楚"什么时候该用":不要只写"查询能耗",要写"当用户询问当前能耗、用电量等问题时调用"。
- 说清楚"返回什么":明确告诉模型返回值是数值、文本还是布尔值,以及单位是什么。
- 说清楚"什么时候不该用":对于高危操作(如删除数据),明确标注使用条件。
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?
大模型的知识有两个致命缺陷:
- 知识截止日期:模型训练数据有时效性,无法知道最新的信息。
- 缺乏私域知识:你公司的《能源管理手册》、内部规章制度、设备参数——这些模型从未见过。
RAG(Retrieval-Augmented Generation,检索增强生成) 的核心思想非常朴素:在提问之前,先从知识库中检索出相关文档片段,把它和用户问题一起交给大模型,让模型"开卷考试"。
4.2 RAG 的完整链路
【离线索引阶段】
企业文档 ──→ 文档切分 ──→ Embedding向量化 ──→ 存入向量数据库
(PDF/Word) (Chunking) (文本→数值向量) (Milvus等)
【在线检索阶段】
用户提问 ──→ Query向量化 ──→ 向量相似度检索 ──→ Top-K相关片段
│
▼
LLM ←── 相关片段 + 用户问题
│
▼
带上下文的专业回答
4.3 关键技术点解析
文档切分(Chunking):为什么不能直接存整篇文档?
这是一个常见的初学者疑问。原因有三:
- Token 限制:大模型的上下文窗口有限(通常 4K~128K tokens),塞不下整篇文档。
- 检索精度:文档越长,包含的主题越杂。短片段能让检索更精准地命中相关内容。
- 噪声控制:只给模型最相关的片段,避免无关信息干扰模型的判断。
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 技术趋势:
- Multi-Agent 协作:多个专业 Agent 通过"编排器"协同完成复杂任务。LangGraph4j 已经提供了图状态机来实现 Agent 间的协作与分支。
- MCP 协议标准化:Model Context Protocol 正在成为工具调用的统一标准,让 Agent 可以即插即用地接入各种工具服务。
- 端侧小模型 Agent:Qwen2.5-7B、Llama 3.1-8B 等小模型已具备可靠的 Tool Calling 能力,可在端侧部署,大幅降低延迟和成本。
- Agent 可观测性:OpenTelemetry for LLM 规范逐渐成熟,可以完整追踪 Agent 的推理链路、工具调用耗时、Token 消耗等指标。
更多推荐
所有评论(0)