LangChain4j框架使用指南

LangChain4j 框架使用指南

本文面向 Java 后端工程师,系统讲解 LangChain4j —— Java 版 LangChain 的核心能力、Spring Boot 集成方式与生产实践。所有示例基于 Java 17 + Spring Boot 3.x + LangChain4j 0.34.0


一、为什么需要 LangChain4j

如果你直接用 OkHttp 调 OpenAI 接口写一个 ChatGPT,会发现要解决一堆「围绕 LLM 的工程问题」:

  • 多个模型供应商(OpenAI / Azure / Ollama / 通义 / 文心)切换;
  • Prompt 模板拼接、变量替换;
  • 多轮对话的 Chat Memory 维护与裁剪;
  • Function Calling / Tools 调用本地服务;
  • RAG:文档切分 → embedding → 向量库 → 检索 → 拼接到 prompt;
  • 流式输出(SSE);
  • Token 用量统计、限流、重试;
  • 单元测试与可观测性。

LangChain4j 把上述能力做成可组合的 Java 抽象,类似于 Python 生态的 LangChain,但更Java 风格:基于接口、Builder、Spring Boot Starter,无 Python 那一堆动态魔法。

官方文档:https://docs.langchain4j.dev


二、核心概念地图

┌────────────────────────────────────────────────────────────┐
│                       AI Service                           │
│   声明式接口(@SystemMessage、@UserMessage)                │
│       │                                                    │
│       ▼                                                    │
│  ┌─────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐  │
│  │ ChatModel│   │  Memory  │   │  Tools   │   │   RAG    │  │
│  └─────────┘   └──────────┘   └──────────┘   └──────────┘  │
│       │              │              │              │       │
│       ▼              ▼              ▼              ▼       │
│   OpenAI/Ollama   消息裁剪    本地方法/HTTP   Embedding+VectorStore
└────────────────────────────────────────────────────────────┘

记住四个一等公民:

抽象角色
ChatLanguageModel / StreamingChatLanguageModel与 LLM 通信
ChatMemory维护多轮对话上下文
EmbeddingModel + EmbeddingStore向量化 + 向量存储(RAG 基础)
AiServices把上面三者粘合到一个 Java 接口

三、环境准备

3.1 Maven 依赖

<properties>
    <java.version>17</java.version>
    <langchain4j.version>0.34.0</langchain4j.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>${langchain4j.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>

    <!-- OpenAI 兼容(DeepSeek / 通义百炼 / Moonshot 都用这个) -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>

    <!-- Ollama 本地模型 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-ollama</artifactId>
    </dependency>

    <!-- Spring Boot Starter(自动装配 ChatModel) -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    </dependency>

    <!-- RAG 文档解析 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
    </dependency>

    <!-- 向量库:内存版,演示用 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-easy-rag</artifactId>
    </dependency>
</dependencies>

3.2 application.yml

langchain4j:
  open-ai:
    chat-model:
      base-url: https://api.openai.com/v1   # 也可填 DeepSeek / 阿里百炼地址
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4o-mini
      temperature: 0.7
      timeout: PT60S
      log-requests: true
      log-responses: true
    streaming-chat-model:
      base-url: https://api.openai.com/v1
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4o-mini

本地零成本玩:装个 Ollama,把 langchain4j-ollama-spring-boot-starter 换上即可,一行 API Key 都不用。


四、第一个例子:直接调用 ChatModel

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;

public class HelloLangChain4j {
    public static void main(String[] args) {
        ChatLanguageModel model = OpenAiChatModel.builder()
                .apiKey(System.getenv("OPENAI_API_KEY"))
                .modelName("gpt-4o-mini")
                .temperature(0.7)
                .build();

        String answer = model.generate("用一句话解释什么是 BM25 算法");
        System.out.println(answer);
    }
}

带消息历史:

import dev.langchain4j.data.message.*;

List<ChatMessage> history = List.of(
        SystemMessage.from("你是一名严谨的 Java 后端架构师。"),
        UserMessage.from("Spring 的循环依赖是怎么解决的?只讲三级缓存。")
);
String resp = model.generate(history).content().text();

五、Prompt 模板:变量替换 + 结构化输出

5.1 PromptTemplate

import dev.langchain4j.model.input.PromptTemplate;
import java.util.Map;

PromptTemplate tpl = PromptTemplate.from(
        "请把下面这段 {{lang}} 代码翻译成 {{target}}:\n```\n{{code}}\n```");

String prompt = tpl.apply(Map.of(
        "lang", "Java",
        "target", "Kotlin",
        "code", "public int add(int a, int b){return a+b;}"
)).text();

System.out.println(model.generate(prompt));

5.2 结构化输出(直接拿对象)

LangChain4j 自动用 JSON Schema + 反射把 LLM 输出反序列化成 POJO:

record Recipe(String name, List<String> ingredients, String steps) {}

interface ChefAssistant {
    @UserMessage("给我一道{{dish}}的菜谱,输出 JSON。")
    Recipe recipeOf(@V("dish") String dish);
}

ChefAssistant chef = AiServices.create(ChefAssistant.class, model);
Recipe r = chef.recipeOf("番茄炒蛋");
System.out.println(r.name() + " 食材:" + r.ingredients());

底层会自动追加 output format instruction,把响应解析为 Recipe


六、AI Services:声明式 LLM 接口(最常用)

AiServices 是 LangChain4j 的「招牌特性」。把 LLM 当成一个 Spring Bean,开箱即用:

public interface SupportAgent {

    @SystemMessage("你是一个客服机器人,回答必须中文,礼貌且简短。")
    String chat(@UserMessage String userMessage);

    @SystemMessage("把下面的文本翻译成英文,仅输出译文。")
    String translate(String chinese);

    @UserMessage("分析下面这段评论的情感,返回 POSITIVE / NEGATIVE / NEUTRAL:\n{{it}}")
    Sentiment analyze(String comment);

    enum Sentiment { POSITIVE, NEGATIVE, NEUTRAL }
}

注册:

@Configuration
public class AiServiceConfig {
    @Bean
    SupportAgent supportAgent(ChatLanguageModel model) {
        return AiServices.builder(SupportAgent.class)
                .chatLanguageModel(model)
                .build();
    }
}

调用就是普通 Spring Bean:

@RestController
@RequiredArgsConstructor
public class ChatController {
    private final SupportAgent agent;

    @PostMapping("/chat")
    public String chat(@RequestBody String msg) {
        return agent.chat(msg);
    }
}

七、Chat Memory:让对话有"记忆"

7.1 几种内置策略

import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;

ChatMemory memory = MessageWindowChatMemory.withMaxMessages(20);
// 或按 token 裁剪(更精细)
ChatMemory memory2 = TokenWindowChatMemory.withMaxTokens(2000, new OpenAiTokenizer("gpt-4o-mini"));

7.2 多用户隔离(按 conversationId)

public interface ChatBot {
    String chat(@MemoryId String userId, @UserMessage String message);
}

ChatBot bot = AiServices.builder(ChatBot.class)
        .chatLanguageModel(model)
        .chatMemoryProvider(uid -> MessageWindowChatMemory.withMaxMessages(30))
        .build();

bot.chat("user-A", "我叫小王");
bot.chat("user-A", "我刚才说我叫什么?");   // -> 小王
bot.chat("user-B", "你好");                 // 完全独立的会话

7.3 持久化到 Redis

ChatMemoryStore 接口实现一下即可:

@Component
@RequiredArgsConstructor
public class RedisChatMemoryStore implements ChatMemoryStore {

    private final StringRedisTemplate redis;
    private static final String KEY = "chat:memory:";
    private static final Duration TTL = Duration.ofDays(7);

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String json = redis.opsForValue().get(KEY + memoryId);
        return json == null ? new ArrayList<>() : ChatMessageDeserializer.messagesFromJson(json);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        redis.opsForValue().set(KEY + memoryId,
                ChatMessageSerializer.messagesToJson(messages), TTL);
    }

    @Override
    public void deleteMessages(Object memoryId) {
        redis.delete(KEY + memoryId);
    }
}

然后注入:

.chatMemoryProvider(uid -> MessageWindowChatMemory.builder()
        .id(uid)
        .maxMessages(30)
        .chatMemoryStore(redisChatMemoryStore)
        .build())

八、Tools / Function Calling:让 LLM 调用你的 Java 方法

public class WeatherTools {

    @Tool("查询指定城市的天气,返回温度(摄氏度)和天气状况")
    public String getWeather(@P("城市名,如:杭州") String city) {
        // 真实实现:调和风/墨迹 API
        return "{\"city\":\"" + city + "\",\"temp\":24,\"desc\":\"多云\"}";
    }

    @Tool("查询当前北京时间,返回 ISO-8601 字符串")
    public String now() {
        return LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}
interface SmartAssistant {
    String chat(@MemoryId String uid, @UserMessage String msg);
}

SmartAssistant assistant = AiServices.builder(SmartAssistant.class)
        .chatLanguageModel(model)
        .chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(20))
        .tools(new WeatherTools())   // 关键:注入工具
        .build();

System.out.println(assistant.chat("u1", "现在杭州天气怎么样?要带伞吗?"));

执行流程:

User: 现在杭州天气怎么样?
  ↓ LLM 决定调用 getWeather("杭州")
Tool: {"city":"杭州","temp":24,"desc":"多云"}
  ↓ LLM 把工具结果用自然语言总结
Bot: 杭州目前 24°C,多云,不需要带伞。

多个工具时,LLM 会自动选择合适的方法;支持嵌套 / 并行调用。


九、RAG:检索增强生成

RAG 是企业落地最重要的一块,分为两个阶段:离线建库在线检索 + 生成

9.1 离线建库

// 1. 加载文档(支持 .txt / .md / .pdf / .docx ...)
List<Document> docs = FileSystemDocumentLoader.loadDocuments(
        Path.of("./knowledge"),
        new ApachePdfBoxDocumentParser()
);

// 2. 切分(按段落 / 按 token)
DocumentSplitter splitter = DocumentSplitters.recursive(500, 50); // chunkSize=500, overlap=50

// 3. 选择 embedding 模型(OpenAI / Ollama / 本地 BGE)
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("text-embedding-3-small")
        .build();

// 4. 选向量库(演示用内存版,生产换成 Milvus / Elasticsearch / PgVector / Chroma)
EmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();

// 5. 灌库
EmbeddingStoreIngestor.builder()
        .documentSplitter(splitter)
        .embeddingModel(embeddingModel)
        .embeddingStore(store)
        .build()
        .ingest(docs);

9.2 在线检索 + 生成

ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
        .embeddingStore(store)
        .embeddingModel(embeddingModel)
        .maxResults(5)
        .minScore(0.6)
        .build();

interface KnowledgeAssistant {
    @SystemMessage("你是一个内部知识库助手,只能根据提供的资料回答;资料没有就说不知道。")
    String ask(@UserMessage String question);
}

KnowledgeAssistant ka = AiServices.builder(KnowledgeAssistant.class)
        .chatLanguageModel(model)
        .contentRetriever(retriever)            // 自动检索 + 拼 prompt
        .chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(10))
        .build();

System.out.println(ka.ask("请问公司的报销流程是什么?"));

9.3 切换到 Elasticsearch 向量库

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-elasticsearch</artifactId>
</dependency>
RestClient rc = RestClient.builder(HttpHost.create("http://localhost:9200")).build();
EmbeddingStore<TextSegment> esStore = ElasticsearchEmbeddingStore.builder()
        .restClient(rc)
        .indexName("kb_docs")
        .dimension(1536)
        .build();

切换零成本,业务代码不用改。

9.4 高级 RAG:多路召回 + 重排

import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.query.router.DefaultQueryRouter;
import dev.langchain4j.rag.content.aggregator.ReRankingContentAggregator;

ContentRetriever vector  = EmbeddingStoreContentRetriever.builder()...build();
ContentRetriever bm25    = new BM25ContentRetriever(...);   // 自己实现,调 ES BM25

RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder()
        .queryRouter(new DefaultQueryRouter(vector, bm25))   // 双路并发召回
        .contentAggregator(new ReRankingContentAggregator(rerankModel))  // 交叉编码器重排
        .build();

KnowledgeAssistant ka = AiServices.builder(KnowledgeAssistant.class)
        .chatLanguageModel(model)
        .retrievalAugmentor(augmentor)
        .build();

十、流式输出(SSE)

后端:

import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.service.TokenStream;

interface StreamingAssistant {
    TokenStream chat(@UserMessage String msg);
}

StreamingAssistant agent = AiServices.builder(StreamingAssistant.class)
        .streamingChatLanguageModel(streamingModel)
        .build();

@GetMapping(value = "/sse/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chat(@RequestParam String q) {
    SseEmitter emitter = new SseEmitter(0L);
    agent.chat(q)
            .onNext(token -> safeSend(emitter, token))
            .onComplete(resp -> emitter.complete())
            .onError(emitter::completeWithError)
            .start();
    return emitter;
}

private void safeSend(SseEmitter e, String data) {
    try { e.send(SseEmitter.event().data(data)); } catch (IOException ex) { e.completeWithError(ex); }
}

前端就是普通 EventSource,与你写过的 ChatGPT 流式 Demo 完全一样。


十一、可观测性:日志、Token、重试

11.1 请求/响应日志

OpenAiChatModel.builder()
        .apiKey(...)
        .logRequests(true)
        .logResponses(true)
        .build();

或者 application.yml 中:

langchain4j:
  open-ai:
    chat-model:
      log-requests: true
      log-responses: true

11.2 Token 用量

Response<AiMessage> resp = model.generate(messages);
TokenUsage usage = resp.tokenUsage();
log.info("input={}, output={}, total={}", usage.inputTokenCount(),
        usage.outputTokenCount(), usage.totalTokenCount());

11.3 重试

OpenAiChatModel 自带 maxRetries(int) 配置;底层基于指数退避。

.maxRetries(3)
.timeout(Duration.ofSeconds(60))

更复杂的可以包装 Resilience4j:

Retry retry = Retry.of("llm", RetryConfig.custom()
        .maxAttempts(3)
        .waitDuration(Duration.ofSeconds(2))
        .retryExceptions(IOException.class)
        .build());

十二、实战示例:企业知识库问答系统

把前面所有能力串起来做一个最小可用版:

@Configuration
@RequiredArgsConstructor
public class KbConfig {

    private final ChatLanguageModel chatModel;
    private final EmbeddingModel embeddingModel;
    private final RedisChatMemoryStore redisStore;

    @Bean
    public EmbeddingStore<TextSegment> store() {
        // 生产换成 ES / Milvus
        return new InMemoryEmbeddingStore<>();
    }

    @Bean
    public ContentRetriever retriever(EmbeddingStore<TextSegment> store) {
        return EmbeddingStoreContentRetriever.builder()
                .embeddingStore(store)
                .embeddingModel(embeddingModel)
                .maxResults(5)
                .minScore(0.6)
                .build();
    }

    @Bean
    public KbAssistant kbAssistant(ContentRetriever retriever) {
        return AiServices.builder(KbAssistant.class)
                .chatLanguageModel(chatModel)
                .contentRetriever(retriever)
                .tools(new TicketTools())              // 提交工单
                .chatMemoryProvider(uid -> MessageWindowChatMemory.builder()
                        .id(uid)
                        .maxMessages(30)
                        .chatMemoryStore(redisStore)
                        .build())
                .build();
    }
}

public interface KbAssistant {
    @SystemMessage("""
            你是「公司内部知识库助手」。
            规则:
            1. 优先用提供的检索资料回答;
            2. 资料不足以回答时,调用 createTicket 创建工单;
            3. 回答必须中文、引用资料编号,例如 [来源 1]。
            """)
    String ask(@MemoryId String userId, @UserMessage String question);
}

public class TicketTools {
    @Tool("当知识库找不到答案时,创建一个客服工单,返回工单号")
    public String createTicket(@P("用户ID") String userId,
                               @P("问题描述") String question) {
        // 真正落库
        return "TKT-" + System.currentTimeMillis();
    }
}

Controller:

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/kb")
public class KbController {

    private final KbAssistant assistant;
    private final EmbeddingStoreIngestor ingestor;

    @PostMapping("/upload")
    public String upload(@RequestPart("file") MultipartFile file) throws IOException {
        Path tmp = Files.createTempFile("kb-", "-" + file.getOriginalFilename());
        file.transferTo(tmp);
        Document doc = FileSystemDocumentLoader.loadDocument(tmp);
        ingestor.ingest(doc);
        return "ok";
    }

    @PostMapping("/ask")
    public Map<String, String> ask(@RequestParam String uid, @RequestBody String q) {
        return Map.of("answer", assistant.ask(uid, q));
    }
}

5 分钟你就有了一个支持上传 PDF、多用户记忆、可创建工单的内部 ChatGPT。


十三、生产化清单(强烈建议照着做)

维度推荐做法
模型至少配置主备两家供应商(如 OpenAI + 通义),通过 @Primary + 故障切换
Embedding中文优先 BGE / m3e,维度与向量库 mapping 必须一致
向量库<1M 条用 PgVector;中等规模用 Elasticsearch;高 QPS 用 Milvus
Memory单测/演示用 InMemory;生产用 Redis,注意按 token 裁剪
Prompt抽到独立 .st / .txt 文件,配合版本管理
Token 控制TokenWindowChatMemory + 系统消息精简
限流基于用户 ID 维度做漏桶(Redis Lua),保护账单
审计每次请求落库:用户、prompt、response、token、latency
安全用户输入做 prompt-injection 过滤;工具调用做白名单
测试LangChain4j 提供 OpenAiChatModelMock;评估用 Ragas / TruLens

十四、常见坑

  1. 维度不一致:换 embedding 模型必须重建索引;常见错误 dimensions does not match
  2. JSON 解析失败:模型偶尔不按格式返回;建议加 responseFormat = JSONJsonSchema 严格模式。
  3. 工具描述模糊@Tool 描述写得越精确,LLM 越容易选对工具,参数解析也越准。
  4. Memory 无限增长:一定要设上限(maxMessagesmaxTokens),否则费用爆炸。
  5. 流式异常没关闭 SSE:必须在 onErrorcompleteWithError,否则连接泄漏。
  6. 大文件 RAG 切片过粗:默认 recursive(500, 50) 适合大多数场景;代码 / 表格类文档需要自定义 splitter。
  7. 国内模型适配:通义、DeepSeek、Moonshot 都兼容 OpenAI 协议,把 base-url 换掉即可。

十五、参考资源


总结

LangChain4j 把「跟 LLM 打交道的工程脚手架」做完了:

  • ChatLanguageModel 屏蔽厂商差异;
  • AiServices 让你像写普通 Java 接口一样写 LLM 应用;
  • ChatMemory + Tools + RAG 三大件覆盖 90% 业务场景;
  • Spring Boot Starter 让接入成本接近零。

给 Java 后端开发的建议路径:先用 AiServices + Tools 做一个客服机器人 → 加上 RAG 接入企业文档 → 引入 Streaming + Memory → 做多路召回与重排 → 接入可观测性,就是一个能上线的生产级 LLM 应用。

祝你写出第一个让产品同学惊呼"这居然是 Java 写的?"的 AI 应用 🚀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值