09 · Java 双栈:Spring Boot + LangChain4j

对应代码:server-java/** 为什么做双栈:JD 里明确出现 "基于 Java + langchain4j 框架,负责 AI Agent 核心架构设计与落地"

一、为什么同一个业务做两遍

统计 778 条岗位时发现,Java 50 条、Python 98 条,而 Java + AI 的岗位竞争反而更小。 只做 Python 会丢掉一批机会。

更重要的是------做两遍之后,我才能真正回答面试官那个问题: "这两套你为什么选它?"

二、环境准备(本机实录)

powershell 复制代码
# JDK 17(清华镜像 Adoptium)
https://mirrors.tuna.tsinghua.edu.cn/Adoptium/17/jdk/x64/windows/
# 解压到 D:\zhonglin\agent_project\tools\jdk-17.0.20.1+1
setx JAVA_HOME "D:\zhonglin\agent_project\tools\jdk-17.0.20.1+1"

# Maven 3.9.9(华为云镜像)
setx MAVEN_HOME "D:\zhonglin\agent_project\tools\apache-maven-3.9.9"

mvn -B compile
# BUILD SUCCESS

踩坑:BOM 模式下 java.version 不生效

xml 复制代码
<!-- ❌ 只在用 spring-boot-starter-parent 时才生效 -->
<java.version>17</java.version>

<!-- ✅ 用 spring-boot-dependencies BOM 时必须显式声明 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>

否则 maven-compiler-plugin 会退回默认的 -source 8, 然后报:-source 8 中不支持 文本块(Java 15+ 特性)。

三、核心依赖

xml 复制代码
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j</artifactId>
  <version>0.35.0</version>
</dependency>
<dependency>
  <groupId>dev.langchain4j</groupId>
  <artifactId>langchain4j-open-ai</artifactId>
  <version>0.35.0</version>
</dependency>
<dependency>
  <groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId>
</dependency>

四、AiServices:Java 版的"声明式 Agent"

这是 LangChain4j 最妙的设计------你只写接口,实现由框架动态生成

java 复制代码
public interface OpsAssistant {
    @SystemMessage({
        "你是企业智能运营助手(EAP)...",
        "规则:1) 只依据知识库和工具返回的真实数据作答,禁止编造数字;",
        "      2) 引用资料时在句末标注来源,如 [1];",
        "      4) 涉及推送告警、写库等高风险动作,必须先向用户确认..."
    })
    TokenStream chat(@UserMessage String message);
}

装配:

java 复制代码
OpsAssistant assistant = AiServices.builder(OpsAssistant.class)
        .chatLanguageModel(model)                       // 模型
        .contentRetriever(retrievalService.contentRetriever())  // RAG
        .tools(biQueryTool, alertTool)                  // Function Calling
        .chatMemory(memories.computeIfAbsent(threadId,  // 会话级记忆
                k -> MessageWindowChatMemory.withMaxMessages(10)))
        .build();

运行时自动完成:提示词注入 → RAG 检索 → 工具调用 → 记忆管理 → 流式输出。

小细节:我没有@AiService 注解,而是手写 AiServices.builder(), 因为要做会话级记忆隔离(注解方式在按 session 隔离上不够灵活)。

五、RAG:ContentRetriever

java 复制代码
@PostConstruct
public void buildIndex() {
    // 扫描 .md → 按标题切块 → embedding → 存入向量库
    List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
    store.addAll(embeddings, segments);
}

public ContentRetriever contentRetriever() {
    return EmbeddingStoreContentRetriever.builder()
            .embeddingStore(store)
            .embeddingModel(embeddingModel)
            .maxResults(5)
            .minScore(0.15)
            .build();
}

向量库用 InMemoryEmbeddingStore,换成 PGVector / Milvus 只改 Bean,上层零改动

六、工具:@Tool 注解

java 复制代码
@Component
public class BiQueryTool {
    @Tool("查询 BI 数据:项目成本、缺陷趋势、交付效率等指标统计。输入中文业务问题。")
    public String query(@P("业务问题,例如:本月各项目成本排名前 5") String question) {
        String lastError = "";
        for (int attempt = 0; attempt < 2; attempt++) {
            String rawSql = model.generate(prompt + lastError);
            try {
                return execute(validate(rawSql));   // 校验 + 执行
            } catch (Exception e) { lastError = e.getMessage(); }
        }
        return "SQL 生成或执行失败:" + lastError;
    }
}

和 Python 侧一模一样:生成 → 只读校验 → 执行 → 报错回炉。 校验逻辑同样是白名单表 + 危险关键字 + 单语句 + 自动补 LIMIT。

七、流式:TokenStream + WebFlux SSE

java 复制代码
@PostMapping(value = "/api/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> chat(@RequestBody ChatRequest request) {
    Sinks.Many<ServerSentEvent<String>> sink = Sinks.many().unicast().onBackpressureBuffer();
    agentService.chat(request.getThreadId(), request.getQuery())
        .onNext(token -> sink.tryEmitNext(ServerSentEvent.builder(token).event("token").build()))
        .onError(t -> sink.tryEmitNext(...event("error")...))
        .onComplete(r -> {
            sink.tryEmitNext(ServerSentEvent.builder("[DONE]").event("done").build());
            sink.tryEmitComplete();
        })
        .start();
    return sink.asFlux();
}

八、Python vs Java:到底怎么选

维度 Python(LangGraph) Java(LangChain4j)
编排 显式状态图 + 条件边,强管控 AiServices 动态代理,模型自主决策
RAG 自建混合检索(向量 + BM25 + RRF + 重排) EmbeddingStoreContentRetriever
工具 ToolRegistry + JSON Schema @Tool / @P 注解
记忆 窗口 + 摘要 MessageWindowChatMemory
流式 生成器 + SSE TokenStream + WebFlux
调试 图可视化、状态可回放 依赖日志
接入速度 慢(要设计图) (一个接口搞定)

我的选型结论(面试直接这么说)

"需要严格控制每一步的场景(金融、审计、有合规要求),用 LangGraph 显式建图------ 每一步都在图里,可审计、可回放。 业务边界清晰、追求接入速度的场景,用 LangChain4j 的 AiServices------ 一个接口 + 几个注解就跑起来了。 两套我都实现过。"

九、统一接口契约

同一个前端可以连两个后端(改 vite proxy 的 target):

接口 Python Java
流式对话 POST /api/chat (SSE) POST /api/chat (SSE)
检索 POST /api/knowledge/search GET /api/search
健康检查 GET /api/health GET /api/health

下一篇:10 · 踩坑记录与优化清单

相关推荐
再吃一根胡萝卜1 小时前
10 · 踩坑记录与优化清单
面试
再吃一根胡萝卜1 小时前
07 · 记忆、可观测与成本控制
面试
再吃一根胡萝卜1 小时前
04 · RAG 全流程:从切分到引用溯源
面试
再吃一根胡萝卜1 小时前
11 · 如何把项目写进简历与面试
面试
再吃一根胡萝卜7 小时前
08 · 前端:Vue3 + SSE 流式与执行链路可视化
面试
再吃一根胡萝卜7 小时前
03 · 后端:FastAPI 与分层架构
面试
Interview Aid11212 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
ocean210316 小时前
2025-2026年AI提效与实践大厂面试高频问题
人工智能·面试·职场和发展·提示词工程·ai提效
程序员梅雨17 小时前
Linux & Shell 实用干货
linux·运维·服务器·后端·面试·php