文章 601
评论 5
浏览 236340
LangChain4j + Java:用流式 API 构建 AI Agent,代码比 Python 版更清爽

LangChain4j + Java:用流式 API 构建 AI Agent,代码比 Python 版更清爽

一、引言

提到 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 LangChainJava LangChain4j
LLM 调用ChatOpenAI()OpenAiChatModel.builder()
工具定义@tool 装饰器@Tool 注解
工具调用AgentExecutorAiServices.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 支持

功能PythonJava
代码提示基本支持完整支持
方法签名不明确精确显示
重构容易出错安全可靠
文档提示依赖类型注解内置支持

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 LangChainJava 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 吗?体验如何?欢迎在评论区分享你的使用心得!


标题:LangChain4j + Java:用流式 API 构建 AI Agent,代码比 Python 版更清爽
作者:jiangyi
地址:http://jiangyi.space/articles/2026/07/20/1784440802221.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消