对应代码:
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 · 踩坑记录与优化清单