一、引言
提到 AI Agent,大家首先想到的是 Python 生态的 LangChain。但 Java 开发者也有自己的选择——LangChain4j,一个专门为 Java 设计的 AI 框架。
今天我要对比 Python LangChain 和 Java LangChain4j 实现同一个 Agent(自动查询数据库并生成报表),展示 Java 版的优势:类型安全、编译期检查、IDE 友好。
更重要的是,我们将实战一个完整的 Agent 工作流:意图识别 → 工具选择 → SQL 生成 → 执行 → 结果解读 → Markdown 报表,全部在 Java 中闭环。
二、技术栈对比
2.1 依赖配置
Python LangChain:
pip install langchain langchain-openai langchain-sqlalchemy
Java LangChain4j:
<!-- pom.xml -->
<dependencies>
<!-- LangChain4j 核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>0.29.0</version>
</dependency>
<!-- OpenAI 集成 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.29.0</version>
</dependency>
<!-- SQL 支持 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-sql</artifactId>
<version>0.29.0</version>
</dependency>
<!-- Spring Boot 集成(可选) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>0.29.0</version>
</dependency>
<!-- SQLite 驱动(演示用) -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.45.1.0</version>
</dependency>
</dependencies>
2.2 核心概念对比
| 概念 | Python LangChain | Java LangChain4j |
|---|---|---|
| LLM 调用 | ChatOpenAI() | OpenAiChatModel.builder() |
| 工具定义 | @tool 装饰器 | @Tool 注解 |
| 工具调用 | AgentExecutor | AiServices.create() |
| 流式输出 | stream() 方法 | TokenStream 链式调用 |
| 类型安全 | 运行时检查 | 编译期检查 |
| IDE 支持 | 基本支持 | 完整支持(重构、提示) |
三、Python 版实现
3.1 代码示例
from langchain_openai import ChatOpenAI
from langchain.agents import create_sql_agent
from langchain_community.utilities.sql_database import SQLDatabase
from langchain.agents.agent_toolkits import SQLDatabaseToolkit
# 1. 连接数据库
db = SQLDatabase.from_uri("sqlite:///sales.db")
# 2. 创建 LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 3. 创建工具包
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
# 4. 创建 Agent
agent = create_sql_agent(
llm=llm,
toolkit=toolkit,
verbose=True
)
# 5. 执行查询
response = agent.run("查询 2024 年每个月的销售额")
# 6. 生成报表(需要额外处理)
print(response)
3.2 Python 版的痛点
痛点分析:
1. 类型不安全:工具参数错误只能在运行时发现
2. 代码提示差:IDE 无法准确提示工具方法签名
3. 流式输出复杂:需要处理 Generator 对象
4. 与 Spring 集成困难:需要额外的桥接代码
5. 异常处理繁琐:try-except 代码冗长
四、Java LangChain4j 版实现
4.1 定义 AI 服务接口
package com.example.agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
public interface SalesReportAgent {
@SystemMessage("""
你是一个销售数据分析助手。你可以:
1. 查询数据库获取销售数据
2. 根据数据生成分析报告
3. 输出 Markdown 格式的报表
请确保生成的 SQL 只包含 SELECT 语句,不允许执行 INSERT/UPDATE/DELETE。
""")
String analyzeSalesData(@UserMessage String userQuery);
}
4.2 定义工具类
package com.example.agent;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
public class SalesDatabaseTools {
private final SalesDatabaseService databaseService;
public SalesDatabaseTools(SalesDatabaseService databaseService) {
this.databaseService = databaseService;
}
@Tool("查询销售数据库")
public String querySalesData(@P("SQL 查询语句") String sql) {
return databaseService.executeQuery(sql);
}
}
### 4.3 创建 Agent 工厂
```java
package com.example.agent;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
@Component
public class SalesReportAgentFactory {
private final DataSource dataSource;
public SalesReportAgentFactory(DataSource dataSource) {
this.dataSource = dataSource;
}
public SalesReportAgent createAgent() {
var chatModel = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.1)
.build();
var databaseService = new SalesDatabaseService(dataSource);
var tools = new SalesDatabaseTools(databaseService);
return AiServices.builder(SalesReportAgent.class)
.chatLanguageModel(chatModel)
.tools(tools)
.build();
}
}
class SalesDatabaseService {
private final DataSource dataSource;
SalesDatabaseService(DataSource dataSource) {
this.dataSource = dataSource;
}
public String executeQuery(String sql) {
if (!sql.trim().toUpperCase().startsWith("SELECT")) {
return "错误:只允许执行 SELECT 查询";
}
try (var conn = dataSource.getConnection();
var stmt = conn.prepareStatement(sql)) {
stmt.setQueryTimeout(30);
try (var rs = stmt.executeQuery()) {
return resultSetToJson(rs);
}
} catch (SQLException e) {
return "查询失败:" + e.getMessage();
}
}
private String resultSetToJson(java.sql.ResultSet rs) throws SQLException {
java.util.List<java.util.Map<String, Object>> rows = new java.util.ArrayList<>();
var metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while (rs.next()) {
java.util.Map<String, Object> row = new java.util.LinkedHashMap<>();
for (int i = 1; i <= columnCount; i++) {
row.put(metaData.getColumnName(i), rs.getObject(i));
}
rows.add(row);
}
return rows.toString();
}
}
4.4 使用 Agent
package com.example.controller;
import com.example.agent.SalesReportAgent;
import com.example.agent.SalesReportAgentFactory;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/report")
public class ReportController {
private final SalesReportAgent agent;
public ReportController(SalesReportAgentFactory factory) {
this.agent = factory.createAgent();
}
@PostMapping("/analyze")
public Map<String, Object> analyze(@RequestBody Map<String, String> request) {
String query = request.get("query");
String result = agent.analyzeSalesData(query);
return Map.of(
"query", query,
"report", result,
"timestamp", System.currentTimeMillis()
);
}
}
五、流式输出实现
5.1 LangChain4j 流式 API
package com.example.agent;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.model.output.token.TokenStream;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
public class StreamingSalesReportAgent {
public static void main(String[] args) {
var streamingModel = OpenAiStreamingChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o")
.temperature(0.1)
.build();
var streamingAgent = AiServices.builder(StreamingSalesReportAgentApi.class)
.chatLanguageModel(streamingModel)
.build();
TokenStream tokenStream = streamingAgent.analyzeSalesData(
"查询 2024 年每个月的销售额并生成报表"
);
tokenStream
.onNext(token -> System.out.print(token))
.onComplete(response -> System.out.println("\n\n报表生成完成"))
.onError(error -> System.err.println("生成失败:" + error.getMessage()))
.start();
}
public interface StreamingSalesReportAgentApi {
@SystemMessage("你是一个销售数据分析助手,输出 Markdown 格式报表")
TokenStream analyzeSalesData(@UserMessage String query);
}
}
5.2 封装为 Java Stream(可选)
package com.example.util;
import dev.langchain4j.model.output.token.TokenStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Stream;
public class StreamingUtils {
public static Stream<String> toStream(TokenStream tokenStream) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
tokenStream
.onNext(token -> {
try {
queue.put(token);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
})
.onComplete(response -> {
try {
queue.put(null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
})
.onError(error -> {
try {
queue.put(null);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
})
.start();
return Stream.generate(() -> {
try {
String token = queue.take();
if (token == null) {
throw new RuntimeException("Stream ended");
}
return token;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}).onClose(() -> {});
}
}
六、SQL 安全防护
6.1 安全配置
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
import java.util.regex.Pattern;
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.sqlite.JDBC");
dataSource.setUrl("jdbc:sqlite:sales.db");
// SQLite 只读模式(如果支持)
// dataSource.setUrl("jdbc:sqlite:file:sales.db?mode=ro");
return dataSource;
}
@Bean
public SqlQueryValidator sqlQueryValidator() {
return new SqlQueryValidator();
}
}
class SqlQueryValidator {
private static final String[] ALLOWED_COMMANDS = {"SELECT", "WITH", "EXPLAIN"};
public boolean isValid(String sql) {
if (sql == null || sql.trim().isEmpty()) {
return false;
}
String upperSql = sql.trim().toUpperCase();
boolean startsWithAllowed = false;
for (String command : ALLOWED_COMMANDS) {
if (upperSql.startsWith(command)) {
startsWithAllowed = true;
break;
}
}
if (!startsWithAllowed) {
return false;
}
String[] dangerousPatterns = {
"INSERT", "UPDATE", "DELETE", "DROP", "ALTER",
"TRUNCATE", "CREATE", "EXEC", "EXECUTE"
};
for (String pattern : dangerousPatterns) {
if (containsCommand(upperSql, pattern)) {
return false;
}
}
return true;
}
private boolean containsCommand(String sql, String command) {
String regex = "\\b" + command + "\\b";
return Pattern.compile(regex).matcher(sql).find();
}
}
6.2 使用安全验证
private String executeSafeQuery(String sql, SqlQueryValidator validator) {
// 1. 语法验证
if (!validator.isValid(sql)) {
return "错误:不允许执行此类型的 SQL 语句";
}
// 2. 执行查询(带超时)
try (Connection conn = dataSource.getConnection()) {
// 3. 设置只读模式
conn.setReadOnly(true);
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setQueryTimeout(30);
try (ResultSet rs = stmt.executeQuery()) {
return resultSetToJson(rs);
}
}
} catch (SQLException e) {
return "查询失败:" + e.getMessage();
}
}
七、完整工作流演示
7.1 Agent 工作流程
flowchart TD
A[用户请求] --> B{意图识别}
B --> C[生成 SQL]
C --> D{SQL 验证}
D -->|无效| E[返回错误]
D -->|有效| F[执行查询]
F --> G[获取结果]
G --> H[结果解读]
H --> I[生成 Markdown 报表]
I --> J[返回响应]
style A fill:#f9f,stroke:#333,stroke-width:2px
style J fill:#bfb,stroke:#333,stroke-width:2px
7.2 请求示例
curl -X POST http://localhost:8080/api/report/analyze \
-H "Content-Type: application/json" \
-d '{
"query": "查询 2024 年每个月的销售额,按金额从高到低排序,并生成 Markdown 报表"
}'
7.3 响应示例
{
"query": "查询 2024 年每个月的销售额,按金额从高到低排序,并生成 Markdown 报表",
"report": """
# 2024 年月度销售报表
## 概述
根据数据库查询结果,2024 年各月份销售数据如下:
## 详细数据
| 月份 | 销售额(元) | 订单数 | 平均客单价(元) |
|------|-------------|--------|-----------------|
| 12月 | 1,258,300 | 2,340 | 537.74 |
| 11月 | 1,189,500 | 2,156 | 551.72 |
| 10月 | 1,056,800 | 1,987 | 531.85 |
| 9月 | 987,600 | 1,876 | 526.44 |
| 8月 | 923,400 | 1,765 | 523.17 |
| 7月 | 876,500 | 1,654 | 529.93 |
| 6月 | 812,300 | 1,543 | 526.44 |
| 5月 | 765,400 | 1,432 | 534.49 |
| 4月 | 712,300 | 1,321 | 539.21 |
| 3月 | 678,900 | 1,210 | 561.07 |
| 2月 | 523,400 | 987 | 530.29 |
| 1月 | 567,800 | 1,056 | 537.70 |
## 分析
1. **年度趋势**:销售额呈现明显的季节性,第四季度(10-12月)销售额最高
2. **峰值月份**:12月销售额最高,达到 125.83 万元
3. **低谷月份**:2月销售额最低,为 52.34 万元(受春节假期影响)
4. **平均客单价**:全年平均客单价约为 535 元,波动不大
## 建议
针对第四季度销售高峰,建议提前备货并增加营销活动。
""",
"timestamp": 1704067200000
}
八、Java 版优势总结
8.1 类型安全
// Java:编译期检查
public interface SalesReportAgent {
@Tool
String querySalesData(@V("SQL 查询语句") String sql); // 参数类型明确
}
// Python:运行时检查
def query_sales_data(sql): # 参数类型不明确
pass
8.2 IDE 支持
| 功能 | Python | Java |
|---|---|---|
| 代码提示 | 基本支持 | 完整支持 |
| 方法签名 | 不明确 | 精确显示 |
| 重构 | 容易出错 | 安全可靠 |
| 文档提示 | 依赖类型注解 | 内置支持 |
8.3 流式输出
// Java:TokenStream 链式调用,优雅简洁
tokenStream
.onNext(token -> System.out.print(token))
.onComplete(response -> System.out.println("完成"))
.onError(error -> System.err.println("失败"))
.start();
// Python:Generator 模式,需要手动迭代
for token in agent.stream():
print(token)
8.4 Spring Boot 集成
// Java:无缝集成
@Component
public class SalesReportAgentFactory {
@Autowired
private DataSource dataSource;
@Bean
public SalesReportAgent agent() {
return AiServices.create(...);
}
}
// Python:需要额外配置
app = Flask(__name__)
agent = create_agent() # 需要手动管理生命周期
8.5 异常处理
// Java:结构化异常处理
try (Connection conn = dataSource.getConnection()) {
// 自动资源管理
} catch (SQLException e) {
// 精确的异常类型
}
// Python:异常类型模糊
try:
conn = connect()
except Exception as e:
# 不确定具体异常类型
九、完整项目结构
spring-boot-langchain4j-agent/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── agent/
│ │ │ ├── SalesReportAgent.java # Agent 接口
│ │ │ ├── SalesReportAgentFactory.java # Agent 工厂
│ │ │ └── StreamingSalesReportAgent.java # 流式 Agent
│ │ ├── config/
│ │ │ ├── DataSourceConfig.java # 数据源配置
│ │ │ └── SqlQueryValidator.java # SQL 验证器
│ │ ├── controller/
│ │ │ └── ReportController.java # REST 控制器
│ │ └── Application.java # 启动类
│ └── resources/
│ ├── application.yml # 配置文件
│ └── sales.db # SQLite 数据库
└── pom.xml # Maven 依赖
十、总结
10.1 Python vs Java 对比
| 维度 | Python LangChain | Java LangChain4j |
|---|---|---|
| 类型安全 | 弱 | 强 |
| 编译检查 | 无 | 有 |
| IDE 支持 | 一般 | 优秀 |
| 流式输出 | Generator 模式 | 回调模式 |
| Spring 集成 | 困难 | 无缝 |
| 代码规范 | 依赖约定 | 强制规范 |
| 学习曲线 | 低 | 中等 |
10.2 选型建议
选型决策树:
┌─────────────────────────────────────────────────────┐
│ │
│ 你是哪种开发者? │
│ │
│ Python 开发者 → Python LangChain(生态成熟) │
│ │
│ Java/Spring Boot 开发者 → LangChain4j(类型安全) │
│ │
│ 需要企业级稳定性 → LangChain4j(编译检查) │
│ │
│ 需要快速原型开发 → Python LangChain(开发快) │
│ │
│ 需要与现有 Java 系统集成 → LangChain4j │
│ │
└─────────────────────────────────────────────────────┘
10.3 关键注意事项
使用注意事项:
1. SQL 注入防护:使用只读连接、SQL 验证、参数化查询
2. 超时控制:设置合理的查询超时时间
3. 流式输出:使用 TokenStream 处理实时响应
4. 错误处理:完善的异常捕获和降级机制
5. 日志记录:记录所有 LLM 调用和工具执行
6. 成本控制:监控 Token 使用量
💡 互动话题:你在项目中使用过 LangChain4j 吗?体验如何?欢迎在评论区分享你的使用心得!
