首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java转AI高薪领域必备:从0到1打通生产级AI Agent开发

Java转AI高薪领域必备:从0到1打通生产级AI Agent开发

原创
作者头像
用户12678265
发布2026-08-23 13:53:33
发布2026-08-23 13:53:33
1730
举报

Java转AI高薪领域必备:从0到1打通生产级AI Agent开发

当“所有软件都值得用 AI 重做一遍”成为共识,Java 开发者如何跨越语言栈与技术思维的双重鸿沟,构建真正可落地的生产级智能体?本文摒弃 Demo 式教学,基于 Spring Boot 3 + LangChain4j + Qdrant + Ollama,完整实现一个具备工具调用(Tool Calling)RAG 检索增强多轮对话记忆异步流式输出的 AI Agent,并深入剖析其工程化关键——从提示词工程到向量索引更新策略,从超时熔断到可观测性埋点。全文代码均可直接运行,助你完成从“CRUD 工程师”到“AI 应用架构师”的认知升级。


一、为什么 Java 开发者是 AI Agent 落地的最佳主力?

AI 圈的热点从“大模型参数竞赛”转向“Agent 应用竞赛”,但大量 Python 实现的 Agent 框架(如 LangChain、AutoGen)在生产环境中暴露出稳定性、可观测性和工程治理方面的短板。而 Java 生态在并发控制、事务管理、分布式追踪、性能调优等方面积累了二十余年的工业级实践——这正是 AI Agent 从“原型玩具”走向“业务系统”时最稀缺的能力。

Java 转 AI 的核心不是重新学 Python,而是将 Java 的工程思维投射到 AI 范式上

  • 把 LLM 调用看作一种 远程 RPC(需要重试、熔断、降级);
  • 把 Prompt 模板看作一种 配置化的业务规则(需要版本管理和 A/B 测试);
  • 把向量检索看作一种 特殊的索引访问(需要一致性更新与缓存策略);
  • 把 Agent 的多轮推理看作一种 有状态的工作流(需要状态机与超时控制)。

本文以 “企业智能客服助手” 为业务场景,构建一个能自主查询订单、检索产品文档、并动态生成回复的生产级 Agent。我们将全程使用 Java 技术栈,不依赖 Python 桥接,确保部署运维链路与现有企业体系无缝融合。


二、技术选型与架构总览

2.1 核心组件清单

组件

选型

理由

基础框架

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)

支持前端流式渲染,提升用户体验

2.2 整体架构分层

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────────┐
│                    接入层(REST / SSE)                      │
└───────────────────────────┬─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│                  Agent 编排层(核心)                         │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────────────┐    │
│  │ 意图识别器   │  │ 工具选择器  │  │ 计划执行器       │    │
│  └─────────────┘  └─────────────┘  └──────────────────┘    │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────────────┐    │
│  │ 记忆管理器   │  │ RAG 检索器  │  │ 响应合成器       │    │
│  └─────────────┘  └─────────────┘  └──────────────────┘    │
└───────────────────────────┬─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│                   能力层(工具 & 数据)                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌───────────┐  │
│  │订单查询   │  │文档检索   │  │计算器    │  │外部API    │  │
│  └──────────┘  └──────────┘  └──────────┘  └───────────┘  │
└───────────────────────────┬─────────────────────────────────┘
                            │
┌───────────────────────────▼─────────────────────────────────┐
│                 基础设施(LLM + 向量库 + Redis)             │
└─────────────────────────────────────────────────────────────┘

三、环境准备:5 分钟拉起依赖服务

为保障专注代码逻辑,我们全部采用容器化依赖:

代码语言:javascript
复制
# 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 集群,此处本地容器用于开发测试。


四、工程脚手架与核心配置

4.1 Maven 依赖核心片段

代码语言:javascript
复制
<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>

4.2 application.yml 配置

代码语言:javascript
复制
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数触发摘要压缩

五、生产级 Agent 核心实现

5.1 自定义工具(Tool)——订单查询与计算器

LangChain4j 通过注解 @Tool 声明可调用方法,框架自动生成 OpenAI 兼容的 Function Schema。

代码语言:javascript
复制
@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 进行数值推理。

代码语言:javascript
复制
@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);
        }
    }
}

5.2 RAG 检索增强:文档向量化与检索

我们准备一份产品手册的文本片段(实际可从数据库或文件加载),使用 Ollama Embedding 模型生成向量并存入 Qdrant。

代码语言:javascript
复制
@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());
    }
}

5.3 对话记忆:滑动窗口 + 摘要压缩

我们实现一个 MemoryStore 接口,基于 Redis 存储对话历史,并提供智能压缩策略。

代码语言:javascript
复制
@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); }
    }
}

5.4 Agent 核心编排器:决策与执行

LangChain4j 提供了 AiServices 快速构建 Agent,但为了更精细控制(重试、监控、流式),我们手动实现一个 AgentOrchestrator。采用 ReAct 模式(推理-行动-观察)的变体,结合工具调用和 RAG 上下文注入。

代码语言:javascript
复制
@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;
    }
}

5.5 流式 Controller(SSE)

使用 Spring WebFlux 提供 Server-Sent Events 接口,前端可逐字接收。

代码语言:javascript
复制
@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));
    }
}

六、生产级增强:可观测性、熔断与降级

6.1 埋点与 Metrics

通过 Micrometer 记录关键指标:

代码语言:javascript
复制
@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());
    }
}

6.2 熔断与重试

使用 Spring Retry 或 Resilience4j 包裹 LLM 调用:

代码语言:javascript
复制
@Bean
public ChatLanguageModel chatModelWithRetry(ChatLanguageModel delegate) {
    return new RetryableChatModel(delegate, 
            RetryConfig.custom()
                .maxAttempts(3)
                .retryOnException(e -> e instanceof IOException)
                .build());
}

6.3 向量索引更新策略

生产环境文档变更时,通过消息队列(如 RabbitMQ)触发增量更新:

代码语言:javascript
复制
@Component
public class DocumentUpdateConsumer {

    @EventListener
    public void onDocumentUpdated(DocumentUpdatedEvent event) {
        // 删除旧的向量,重新embed并插入
        docStore.update(event.getDocumentId(), event.getNewContent());
    }
}

七、运行与测试

启动 Spring Boot 应用后,使用 curl 或 Postman 测试:

代码语言:javascript
复制
curl -X POST "http://localhost:8080/api/agent/chat?sessionId=user123" \
  -H "Content-Type: application/plain" \
  -d "我的订单C1001发货了吗?"

预期 SSE 流式返回类似:

代码语言:javascript
复制
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 删除。

目录
  • Java转AI高薪领域必备:从0到1打通生产级AI Agent开发
    • 一、为什么 Java 开发者是 AI Agent 落地的最佳主力?
    • 二、技术选型与架构总览
      • 2.1 核心组件清单
      • 2.2 整体架构分层
    • 三、环境准备:5 分钟拉起依赖服务
    • 四、工程脚手架与核心配置
      • 4.1 Maven 依赖核心片段
      • 4.2 application.yml 配置
    • 五、生产级 Agent 核心实现
      • 5.1 自定义工具(Tool)——订单查询与计算器
      • 5.2 RAG 检索增强:文档向量化与检索
      • 5.3 对话记忆:滑动窗口 + 摘要压缩
      • 5.4 Agent 核心编排器:决策与执行
      • 5.5 流式 Controller(SSE)
    • 六、生产级增强:可观测性、熔断与降级
      • 6.1 埋点与 Metrics
      • 6.2 熔断与重试
      • 6.3 向量索引更新策略
    • 七、运行与测试
    • 八、关键生产落地要点
    • 九、总结与展望
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档