当“所有软件都值得用 AI 重做一遍”成为共识,Java 开发者如何跨越语言栈与技术思维的双重鸿沟,构建真正可落地的生产级智能体?本文摒弃 Demo 式教学,基于 Spring Boot 3 + LangChain4j + Qdrant + Ollama,完整实现一个具备工具调用(Tool Calling)、RAG 检索增强、多轮对话记忆和异步流式输出的 AI Agent,并深入剖析其工程化关键——从提示词工程到向量索引更新策略,从超时熔断到可观测性埋点。全文代码均可直接运行,助你完成从“CRUD 工程师”到“AI 应用架构师”的认知升级。
AI 圈的热点从“大模型参数竞赛”转向“Agent 应用竞赛”,但大量 Python 实现的 Agent 框架(如 LangChain、AutoGen)在生产环境中暴露出稳定性、可观测性和工程治理方面的短板。而 Java 生态在并发控制、事务管理、分布式追踪、性能调优等方面积累了二十余年的工业级实践——这正是 AI Agent 从“原型玩具”走向“业务系统”时最稀缺的能力。
Java 转 AI 的核心不是重新学 Python,而是将 Java 的工程思维投射到 AI 范式上:
本文以 “企业智能客服助手” 为业务场景,构建一个能自主查询订单、检索产品文档、并动态生成回复的生产级 Agent。我们将全程使用 Java 技术栈,不依赖 Python 桥接,确保部署运维链路与现有企业体系无缝融合。
组件 | 选型 | 理由 |
|---|---|---|
基础框架 | Spring Boot 3.2.x + JDK 17 | 企业标配,虚拟线程支持高并发 I/O |
LLM 调用层 | LangChain4j 0.33.0 | 统一的 Java API,支持 OpenAI、Ollama、Azure,内置 Tool 与 RAG 抽象 |
本地模型服务 | Ollama + llama3.1:8b | 可离线部署,兼顾效果与成本,避免 API 敏感数据外流 |
向量数据库 | Qdrant (Docker) | 纯 Rust 实现,性能优异,支持 gRPC 与过滤条件,生产就绪 |
Embedding 模型 | BAAI/bge-m3 (通过 Ollama) | 中英文语义表征能力强,维度 1024,检索精度高 |
对话记忆 | 基于 Redis 的滑动窗口 + 摘要压缩 | 控制 Token 消耗,同时保持长上下文关键信息 |
可观测性 | Micrometer + Prometheus + Grafana | 记录 LLM 调用延迟、Token 用量、向量检索耗时 |
异步流式 | Spring WebFlux (Server-Sent Events) | 支持前端流式渲染,提升用户体验 |
┌─────────────────────────────────────────────────────────────┐
│ 接入层(REST / SSE) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ Agent 编排层(核心) │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ 意图识别器 │ │ 工具选择器 │ │ 计划执行器 │ │
│ └─────────────┘ └─────────────┘ └──────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ 记忆管理器 │ │ RAG 检索器 │ │ 响应合成器 │ │
│ └─────────────┘ └─────────────┘ └──────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 能力层(工具 & 数据) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │订单查询 │ │文档检索 │ │计算器 │ │外部API │ │
│ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 基础设施(LLM + 向量库 + Redis) │
└─────────────────────────────────────────────────────────────┘为保障专注代码逻辑,我们全部采用容器化依赖:
# 1. 启动 Qdrant(向量数据库)
docker run -d -p 6333:6333 -p 6334:6334 \
-v qdrant_storage:/qdrant/storage \
qdrant/qdrant
# 2. 启动 Ollama 并下载模型(需 GPU 或 CPU 运行)
docker run -d -v ollama:/root/.ollama -p 11434:11434 ollama/ollama
docker exec -it <ollama-container> ollama pull llama3.1:8b
docker exec -it <ollama-container> ollama pull bge-m3
# 3. 启动 Redis(记忆存储)
docker run -d -p 6379:6379 redis:7-alpine生产环境建议使用云厂商的托管向量库(如腾讯云向量数据库)和 Redis 集群,此处本地容器用于开发测试。
<properties>
<spring-boot.version>3.2.4</spring-boot.version>
<langchain4j.version>0.33.0</langchain4j.version>
</properties>
<dependencies>
<!-- Spring Boot WebFlux + Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- LangChain4j 核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Ollama 集成 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Qdrant 向量存储 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qdrant</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Redis 用于记忆 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 可观测性 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
</dependencies>spring:
codec:
max-in-memory-size: 5MB
redis:
host: localhost
port: 6379
timeout: 2s
langchain4j:
ollama:
chat:
base-url: http://localhost:11434
model: llama3.1:8b
temperature: 0.3
top-p: 0.9
timeout: 60s
max-retries: 2
embedding:
base-url: http://localhost:11434
model: bge-m3
timeout: 30s
qdrant:
host: localhost
grpc-port: 6334
collection-name: product_docs
vector-size: 1024
distance: COSINE
agent:
max-tool-iterations: 3
memory-window-size: 5 # 保留最近5轮对话
memory-compression-threshold: 2000 # 超过此token数触发摘要压缩LangChain4j 通过注解 @Tool 声明可调用方法,框架自动生成 OpenAI 兼容的 Function Schema。
@Component
@Slf4j
public class OrderServiceTool {
// 模拟订单数据(实际应从数据库或外部API获取)
private final Map<String, List<Order>> fakeDb = new HashMap<>();
@PostConstruct
public void init() {
fakeDb.put("C1001", List.of(
new Order("ORD-2026-001", "iPhone 15 Pro", 8999.00, "已发货"),
new Order("ORD-2026-002", "AirPods Pro 2", 1899.00, "已签收")
));
fakeDb.put("C1002", List.of(
new Order("ORD-2026-003", "MacBook Pro 14", 16999.00, "待支付")
));
}
@Tool("根据用户ID和可选订单状态查询订单列表,返回订单号、商品名、金额、状态")
public List<Order> queryOrders(
@P("用户ID,例如 C1001") String userId,
@P("状态筛选,可选:已发货、已签收、待支付、全部,默认全部") String status) {
log.info("Tool调用: queryOrders userId={}, status={}", userId, status);
// 模拟延迟,体现工具调用耗时
sleepQuietly(200);
List<Order> all = fakeDb.getOrDefault(userId, Collections.emptyList());
if (all.isEmpty()) return Collections.emptyList();
if ("全部".equals(status) || status == null) return all;
return all.stream()
.filter(o -> o.status().equals(status))
.collect(Collectors.toList());
}
private void sleepQuietly(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
}
public record Order(String orderId, String product, Double amount, String status) {}
}另一个工具:计算器,用于演示 Agent 进行数值推理。
@Component
@Slf4j
public class CalculatorTool {
@Tool("执行基础的数学运算,支持加减乘除,表达式格式如 '2 + 3' 或 '10 * 5'")
public double calculate(@P("数学表达式,例如 '12.5 / 2.5'") String expression) {
log.info("Tool调用: calculate expression={}", expression);
// 使用 ScriptEngine 简单计算(生产建议用 exp4j 或 GraalVM)
try {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
Object result = engine.eval(expression);
return ((Number) result).doubleValue();
} catch (Exception e) {
throw new IllegalArgumentException("计算表达式错误: " + expression, e);
}
}
}我们准备一份产品手册的文本片段(实际可从数据库或文件加载),使用 Ollama Embedding 模型生成向量并存入 Qdrant。
@Component
@Slf4j
public class DocumentVectorStore {
private final QdrantVectorStore vectorStore;
private final EmbeddingModel embeddingModel;
public DocumentVectorStore(QdrantVectorStore vectorStore, EmbeddingModel embeddingModel) {
this.vectorStore = vectorStore;
this.embeddingModel = embeddingModel;
}
// 初始化时建立索引(实际生产应通过定时任务或增量更新)
@PostConstruct
public void initIndex() {
// 检查是否已有数据,避免重复插入
if (vectorStore.count() > 0) {
log.info("向量库已存在数据,跳过初始化");
return;
}
List<TextSegment> segments = loadDocuments();
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
List<VectorEntry> entries = new ArrayList<>();
for (int i = 0; i < segments.size(); i++) {
entries.add(new VectorEntry(UUID.randomUUID().toString(), embeddings.get(i), segments.get(i)));
}
vectorStore.addAll(entries);
log.info("成功索引 {} 个文档片段", segments.size());
}
private List<TextSegment> loadDocuments() {
// 模拟产品文档,实际可从 Markdown/PDF 解析
return List.of(
TextSegment.from("iPhone 15 Pro 支持灵动岛交互,配备 A17 Pro 芯片,支持 USB-C 3.0 高速传输。"),
TextSegment.from("AirPods Pro 2 采用 H2 芯片,降噪效果是上一代的 2 倍,支持自适应通透模式。"),
TextSegment.from("MacBook Pro 14 搭载 M3 Pro 芯片,续航最长 18 小时,支持 Wi-Fi 6E。"),
TextSegment.from("公司退货政策:自签收之日起 14 天内无理由退货,需保持商品完好。"),
TextSegment.from("订单支付支持微信、支付宝、信用卡分期,分期最高 24 期。")
);
}
// RAG 检索方法,返回相关文本片段(含相似度)
public List<TextSegment> retrieveRelevant(String query, int maxResults) {
Embedding queryEmbedding = embeddingModel.embed(query).content();
List<VectorEntry> results = vectorStore.findRelevant(queryEmbedding, maxResults);
log.info("RAG检索,查询='{}', 命中{}条", query, results.size());
return results.stream()
.map(VectorEntry::embedded)
.collect(Collectors.toList());
}
}我们实现一个 MemoryStore 接口,基于 Redis 存储对话历史,并提供智能压缩策略。
@Component
@Slf4j
public class RedisChatMemoryStore implements ChatMemoryStore {
private final ReactiveRedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper;
private final int windowSize;
private final int compressionThreshold;
// 使用 Jackson 序列化消息
public RedisChatMemoryStore(ReactiveRedisTemplate<String, String> redisTemplate,
@Value("${agent.memory-window-size:5}") int windowSize,
@Value("${agent.memory-compression-threshold:2000}") int threshold) {
this.redisTemplate = redisTemplate;
this.objectMapper = new ObjectMapper();
this.windowSize = windowSize;
this.compressionThreshold = threshold;
}
// 保存新消息(用户或AI)
public Mono<Void> append(String sessionId, ChatMessage message) {
String key = "chat:mem:" + sessionId;
return redisTemplate.opsForList()
.rightPush(key, serialize(message))
.flatMap(len -> {
// 超过窗口大小则移除最旧的消息
if (len > windowSize * 2 + 2) { // 每轮包含user+assistant
return redisTemplate.opsForList().leftPop(key)
.then(redisTemplate.opsForList().leftPop(key)); // 移除一对
}
return Mono.empty();
})
.then(compressIfNeeded(sessionId, key))
.then();
}
private Mono<Void> compressIfNeeded(String sessionId, String key) {
return redisTemplate.opsForList().size(key)
.flatMap(size -> {
if (size < 2) return Mono.empty();
// 估算token数(简化:按字符数/3)
return redisTemplate.opsForList().range(key, 0, -1)
.collectList()
.flatMap(msgs -> {
int totalChars = msgs.stream().mapToInt(String::length).sum();
if (totalChars > compressionThreshold) {
log.info("触发记忆压缩,session={}, 字符数={}", sessionId, totalChars);
return compressHistory(sessionId, msgs);
}
return Mono.empty();
});
});
}
// 压缩:将较旧的消息(除最近2轮外)交给LLM生成摘要,替换为一条摘要消息
private Mono<Void> compressHistory(String sessionId, List<String> msgs) {
// 保留最近2轮(4条消息),其余生成摘要
int keep = Math.min(4, msgs.size());
List<String> toSummarize = msgs.subList(0, msgs.size() - keep);
List<String> recent = msgs.subList(msgs.size() - keep, msgs.size());
// 调用LLM生成摘要(这里简化,实际通过ChatLanguageModel)
String summary = callLLMForSummary(toSummarize);
// 删除原列表中的旧消息,然后左推摘要消息
return redisTemplate.opsForList().trim(key, 0, 0) // 清空(简化处理)
.thenMany(redisTemplate.opsForList().leftPush(key, serialize(new SystemMessage("历史摘要: " + summary))))
.thenMany(Flux.fromIterable(recent).flatMap(m -> redisTemplate.opsForList().rightPush(key, m)))
.then();
}
private String callLLMForSummary(List<String> messages) {
// 实际使用 ChatLanguageModel.generate(...) 实现,此处为占位
return "用户咨询了订单和退货政策,AI提供了相关说明。";
}
// 获取最近N轮对话(用于构建上下文)
public Mono<List<ChatMessage>> getRecent(String sessionId, int n) {
String key = "chat:mem:" + sessionId;
return redisTemplate.opsForList()
.range(key, -n * 2, -1) // 取最后n轮
.map(this::deserialize)
.collectList();
}
private String serialize(ChatMessage msg) {
try { return objectMapper.writeValueAsString(msg); }
catch (JsonProcessingException e) { throw new RuntimeException(e); }
}
private ChatMessage deserialize(String json) {
try { return objectMapper.readValue(json, ChatMessage.class); }
catch (JsonProcessingException e) { throw new RuntimeException(e); }
}
}LangChain4j 提供了 AiServices 快速构建 Agent,但为了更精细控制(重试、监控、流式),我们手动实现一个 AgentOrchestrator。采用 ReAct 模式(推理-行动-观察)的变体,结合工具调用和 RAG 上下文注入。
@Service
@Slf4j
public class AgentOrchestrator {
private final ChatLanguageModel chatModel;
private final EmbeddingModel embeddingModel; // 用于RAG
private final RedisChatMemoryStore memoryStore;
private final DocumentVectorStore docStore;
private final OrderServiceTool orderTool;
private final CalculatorTool calcTool;
private final ObjectMapper objectMapper;
private final int maxIterations;
// 注入所有工具,用于生成 Function 定义
private final List<Object> tools;
public AgentOrchestrator(ChatLanguageModel chatModel,
EmbeddingModel embeddingModel,
RedisChatMemoryStore memoryStore,
DocumentVectorStore docStore,
OrderServiceTool orderTool,
CalculatorTool calcTool,
@Value("${agent.max-tool-iterations:3}") int maxIter) {
this.chatModel = chatModel;
this.embeddingModel = embeddingModel;
this.memoryStore = memoryStore;
this.docStore = docStore;
this.orderTool = orderTool;
this.calcTool = calcTool;
this.objectMapper = new ObjectMapper();
this.maxIterations = maxIter;
this.tools = List.of(orderTool, calcTool);
}
// 流式处理入口(SSE)
public Flux<AgentResponseChunk> processStream(String sessionId, String userMessage) {
return Flux.defer(() -> {
log.info("Agent开始处理 session={}, message={}", sessionId, userMessage);
// 1. 获取历史记忆
return memoryStore.getRecent(sessionId, 5)
.defaultIfEmpty(Collections.emptyList())
.flatMapMany(history -> {
// 2. 执行多轮迭代
return executeReActLoop(sessionId, userMessage, history, 0);
})
.doFinally(sig -> {
// 3. 保存本轮用户消息和最终AI回复(需要额外处理,见后续)
// 实际应在最终响应时保存,这里简化
});
});
}
private Flux<AgentResponseChunk> executeReActLoop(String sessionId,
String userMessage,
List<ChatMessage> history,
int iteration) {
if (iteration >= maxIterations) {
return Flux.just(new AgentResponseChunk("抱歉,处理超时,请简化您的问题。", true));
}
// 构建消息列表:系统提示 + RAG注入 + 历史 + 当前用户消息
List<ChatMessage> messages = new ArrayList<>();
// 系统提示词(含工具描述)
messages.add(SystemMessage.from(buildSystemPrompt()));
// RAG检索:根据用户消息检索相关文档,注入到上下文
List<TextSegment> docs = docStore.retrieveRelevant(userMessage, 3);
if (!docs.isEmpty()) {
String context = docs.stream()
.map(TextSegment::text)
.collect(Collectors.joining("\n"));
messages.add(UserMessage.from("【参考文档】\n" + context));
}
// 历史消息
messages.addAll(history);
// 当前用户消息
messages.add(UserMessage.from(userMessage));
// 调用LLM(带工具定义)
ToolSpecification toolSpec = ToolSpecifications.toolSpecificationsFrom(tools);
ChatRequest request = ChatRequest.builder()
.messages(messages)
.toolSpecifications(toolSpec)
.build();
return chatModel.generate(request)
.flatMapMany(response -> {
AiMessage aiMsg = response.content();
if (aiMsg.hasToolExecutionRequests()) {
// 模型要求执行工具
List<ToolExecutionRequest> toolRequests = aiMsg.toolExecutionRequests();
// 依次执行工具
List<ToolExecution> executions = toolRequests.stream()
.map(req -> executeTool(req))
.collect(Collectors.toList());
// 构造工具响应消息
List<ChatMessage> toolResults = executions.stream()
.map(exec -> ToolExecutionResultMessage.from(exec.request(), exec.result()))
.collect(Collectors.toList());
// 追加到对话并继续迭代
List<ChatMessage> newHistory = new ArrayList<>(history);
newHistory.add(UserMessage.from(userMessage));
newHistory.add(aiMsg);
newHistory.addAll(toolResults);
// 递归调用,iteration+1
return executeReActLoop(sessionId, userMessage, newHistory, iteration + 1);
} else {
// 最终文本响应
String text = aiMsg.text();
// 流式输出(为了演示,直接一次性返回;实际可结合 StreamingChatLanguageModel)
return Flux.just(new AgentResponseChunk(text, true))
.doOnNext(chunk -> {
// 异步保存完整对话到记忆
memoryStore.append(sessionId, UserMessage.from(userMessage)).subscribe();
memoryStore.append(sessionId, AiMessage.from(text)).subscribe();
});
}
})
.onErrorResume(e -> {
log.error("Agent执行异常", e);
return Flux.just(new AgentResponseChunk("系统繁忙,请稍后重试", true));
});
}
private String buildSystemPrompt() {
return """
你是一个智能客服助手,可以查询订单、计算数值,并依据产品文档回答用户问题。
当需要计算或查询订单时,请调用相应工具。
若用户询问产品功能或政策,优先使用【参考文档】中的信息。
如果工具返回结果为空,请告知用户未找到相关信息。
请用中文简洁回复。
""";
}
private ToolExecution executeTool(ToolExecutionRequest request) {
String toolName = request.name();
Map<String, Object> args = request.arguments();
log.info("执行工具: {}, 参数: {}", toolName, args);
try {
Object result = null;
if ("queryOrders".equals(toolName)) {
String userId = (String) args.get("userId");
String status = (String) args.getOrDefault("status", "全部");
result = orderTool.queryOrders(userId, status);
} else if ("calculate".equals(toolName)) {
String expr = (String) args.get("expression");
result = calcTool.calculate(expr);
} else {
throw new IllegalArgumentException("未知工具: " + toolName);
}
// 将结果序列化为JSON字符串
String jsonResult = objectMapper.writeValueAsString(result);
return ToolExecution.builder()
.request(request)
.result(jsonResult)
.build();
} catch (Exception e) {
log.error("工具执行失败", e);
return ToolExecution.builder()
.request(request)
.result("错误: " + e.getMessage())
.build();
}
}
// 响应Chunk DTO
@Data
@AllArgsConstructor
public static class AgentResponseChunk {
private String content;
private boolean finished;
}
}使用 Spring WebFlux 提供 Server-Sent Events 接口,前端可逐字接收。
@RestController
@RequestMapping("/api/agent")
@Slf4j
public class AgentController {
private final AgentOrchestrator orchestrator;
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> chat(@RequestParam String sessionId,
@RequestBody String userMessage) {
return orchestrator.processStream(sessionId, userMessage)
.map(chunk -> ServerSentEvent.<String>builder()
.data(chunk.getContent())
.event(chunk.isFinished() ? "finish" : "message")
.build())
.doOnComplete(() -> log.info("SSE完成, session={}", sessionId))
.doOnError(e -> log.error("SSE错误", e));
}
}通过 Micrometer 记录关键指标:
@Component
@Aspect
@Slf4j
public class AgentMetricsAspect {
private final MeterRegistry meterRegistry;
@Around("execution(* com.demo.agent.AgentOrchestrator.processStream(..))")
public Mono<Object> measureAgent(ProceedingJoinPoint pjp) throws Throwable {
Timer.Sample sample = Timer.start(meterRegistry);
return ((Mono<?>) pjp.proceed())
.doOnSuccess(v -> {
sample.stop(Timer.builder("agent.processing.time")
.description("Agent处理耗时")
.register(meterRegistry));
meterRegistry.counter("agent.requests.total").increment();
})
.doOnError(e -> meterRegistry.counter("agent.errors.total").increment());
}
}使用 Spring Retry 或 Resilience4j 包裹 LLM 调用:
@Bean
public ChatLanguageModel chatModelWithRetry(ChatLanguageModel delegate) {
return new RetryableChatModel(delegate,
RetryConfig.custom()
.maxAttempts(3)
.retryOnException(e -> e instanceof IOException)
.build());
}生产环境文档变更时,通过消息队列(如 RabbitMQ)触发增量更新:
@Component
public class DocumentUpdateConsumer {
@EventListener
public void onDocumentUpdated(DocumentUpdatedEvent event) {
// 删除旧的向量,重新embed并插入
docStore.update(event.getDocumentId(), event.getNewContent());
}
}启动 Spring Boot 应用后,使用 curl 或 Postman 测试:
curl -X POST "http://localhost:8080/api/agent/chat?sessionId=user123" \
-H "Content-Type: application/plain" \
-d "我的订单C1001发货了吗?"预期 SSE 流式返回类似:
data: 根据查询,订单ORD-2026-001(iPhone 15 Pro)状态为“已发货”,订单ORD-2026-002(AirPods Pro 2)已签收。
event: finish再测试工具调用:"计算 8999 打八折是多少?" Agent 会调用计算器工具返回 7199.2。
挑战 | 解决方案 |
|---|---|
LLM 输出不稳定 | 设置 temperature=0.2,结合结构化输出(JSON Mode) |
Token 成本过高 | 使用本地 Ollama 或腾讯云混元大模型,启用 prompt caching |
工具调用循环 | 设置最大迭代次数 3,并加入时间超时控制 |
并发安全性 | 每个 session 独立,记忆使用 Redis 原子操作 |
向量检索延迟 | 引入二级缓存(Caffeine),预热高频查询 |
上下文切换 | 使用虚拟线程(Java 21+)提升并发吞吐 |
本文从 Java 开发者的视角,完整构建了一个生产级 AI Agent 系统,覆盖了工具调用、RAG、记忆管理、流式交互和可观测性等核心能力。更重要的是,我们全程使用 Java 原生生态,证明了 Java 在 AI 应用层同样具备强大的竞争力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。