LangChain4j 实战:用 Java 原生 AI Services 编排大模型

LangChain4j 是面向 Java 生态的大模型应用开发框架,让你用声明式接口、工具调用与 RAG,几行代码就把大模型接进 Spring Boot 服务。相比手写 HTTP 调用,它把模型抽象、记忆、检索与结构化输出统一成一整套类型安全的 API,是 Java 团队落地 AI 功能最顺手的轮子之一。

一、LangChain4j 解决什么痛点

早期在 Java 里接大模型,基本是拼 HTTP 请求、手动解析 JSON、自己攒对话历史。一旦要上工具调用、向量检索、多轮记忆,样板代码会迅速膨胀。LangChain4j 把这些能力抽象成三层:模型层(ChatLanguageModel)、服务层(AI Services 声明式接口)、编排层(记忆、检索、工具)。开发者只写接口和注解,框架负责把 Java 对象与大模型消息互相转换。本站此前介绍过 Spring AI 接入大模型 的另一种思路,两者定位相近但风格不同:Spring AI 更贴合 Spring 体系,LangChain4j 则提供了更”对话式”的 AI Services 写法。

二、快速接入:依赖与基础调用

以 Maven 为例,引入核心包与具体的模型实现(这里用 OpenAI,换成 Ollama、通义或智谱只需替换artifactId):

<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>

最基础的生成只需三行,ChatLanguageModel 是和具体厂商解耦的抽象:

ChatLanguageModel model = OpenAiChatModel.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .modelName("gpt-4o-mini")
    .build();

String answer = model.generate("用一句话解释什么是向量数据库");
System.out.println(answer);

注意 modelName 与 apiKey 都走构建器注入,生产环境务必从配置中心或环境变量读取,不要硬编码密钥。这也和 Spring Boot 3 升级踩坑 里强调的外部化配置原则一致。

三、AI Services:声明式接口才是精髓

LangChain4j 最迷人的地方是把”和大模型对话”变成定义一个 Java 接口。你只需写方法签名和提示词注解,框架在运行时生成实现类,自动完成消息拼装与结果解析:

interface Assistant {
    @SystemMessage("你是资深 Java 工程师,回答简洁务实")
    String chat(@UserMessage("{{it}}") String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatLanguageModel(model)
    .build();

System.out.println(assistant.chat("Spring Boot 3 怎么优雅关闭?"));

@UserMessage("{{it}}") 表示把方法入参整体填进提示词;也可以用 @UserMessage("帮我把 {{dish}} 做成菜谱") 配合 @V("dish") 做变量绑定。这种写法让业务代码完全不感知大模型细节,单元测试也好 mock。

3.1 工具调用:让模型动手而不是只动嘴

当模型需要查实时数据或执行动作时,用 @Tool 把任意 Java 方法暴露给它。下面这个计算器工具,模型会在用户问”3 加 5 等于多少”时自动选择调用:

class Calculator {
    @Tool("计算两个整数的和")
    int add(@P("第一个整数") int a, @P("第二个整数") int b) {
        return a + b;
    }
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatLanguageModel(model)
    .tools(new Calculator())
    .build();

工具调用本质是把模型输出转成一次本地函数执行,再喂回上下文。关于工具设计的可靠性与回退策略,可参考本站 大模型 Function Calling 实战。

四、多轮记忆:ChatMemory

无状态的 generate 每次都是新对话。要支持多轮上下文,注入一个 ChatMemory,框架会自动维护消息窗口:

ChatMemory memory = MessageWindowChatMemory.withMaxMessages(10);

Assistant assistant = AiServices.builder(Assistant.class)
    .chatLanguageModel(model)
    .chatMemory(memory)
    .build();

assistant.chat("我叫小王,做后端");      // 模型记住上下文
assistant.chat("我刚才说我是做什么的?"); // 能正确回答

withMaxMessages(10) 是滑动窗口,超出部分丢弃,避免历史无限膨胀撑爆上下文。需要持久化会话时,可换成 Redis 或数据库实现的 ChatMemory,配合 Java 21 虚拟线程 的高并发场景也很稳。

五、RAG 落地:把私有文档接进模型

RAG(检索增强生成)是让模型回答私有知识的关键。LangChain4j 把”切分文档→向量化→检索→注入提示”封装得很干净:

EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
    .apiKey(System.getenv("OPENAI_API_KEY"))
    .modelName("text-embedding-3-small")
    .build();

EmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>();

List<Document> docs = FileSystemDocumentLoader.loadDocuments("docs/");
DocumentSplitter splitter = DocumentSplitters.recursive(500, 100);
EmbeddingStoreIngestor.ingest(docs, embeddingStore, embeddingModel, splitter);

ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
    .embeddingStore(embeddingStore)
    .embeddingModel(embeddingModel)
    .maxResults(3)
    .build();

Assistant assistant = AiServices.builder(Assistant.class)
    .chatLanguageModel(model)
    .contentRetriever(retriever)
    .build();

注入 contentRetriever 后,每次提问框架都会先检索最相关的 3 段文档,拼进提示词再生成答案。检索质量直接决定回答质量,关于混合检索与重排序的进阶技巧,见本站 RAG 进阶实战。本地部署 Embedding 模型可借助 Ollama 私有化 免 API 费用。

六、结构化输出:直接拿到 POJO

很多场景下你要的不是一段文本,而是一个对象。LangChain4j 能按返回类型自动约束模型输出 JSON 并反序列化:

class Recipe {
    public String title;
    public List<String> ingredients;
    public int minutes;
}

interface Chef {
    Recipe createRecipe(@UserMessage("{{it}}") String dish);
}

Chef chef = AiServices.builder(Chef.class)
    .chatLanguageModel(model)
    .build();

Recipe r = chef.createRecipe("番茄炒蛋");
System.out.println(r.title + " 需 " + r.minutes + " 分钟");

这种方法比自己正则解析稳定得多,适合把模型接入表单填充、工单分类、报表生成等结构化流水线。

七、生产环境几点建议

关注点建议
模型切换面向 ChatLanguageModel 编程,厂商通过 builder 注入,上线后热切换不影响业务
超时与重试OpenAiChatModel.builder().timeout(Duration.ofSeconds(30)),外层加重试与熔断
流式输出用 streamingChatLanguageModel 或 TokenStream,前端逐字渲染见 Spring AI 流式篇
成本控制maxTokens、maxResults 收紧;高频问答加语义缓存降本

流式输出如果你用的是 Spring Boot,可结合 Spring AI 流式对话 的实现做对照;两者都能让前端像打字机一样实时显示回答。

八、三个容易踩的坑

从 demo 走到生产,最常卡在三类问题。第一,结构化输出偶尔拿不到合法 JSON:模型可能多解释一句,或用 Markdown 代码块把结果包起来,导致 POJO 反序列化直接抛异常。稳妥做法是给接口加 @StructuredPrompt 明确字段格式,并在调用层做失败重试与兜底解析,别把模型当百分百可靠的函数。

第二,RAG 检索为空或答非所问:多数情况是切分粒度不对——一段太长模型抓不住重点,太短又丢掉上下文语义;也可能是 embedding 模型维度与检索器不匹配。正确做法是先用小样本人工核验 top-3 召回质量,确认命中相关段落再谈上线,而不是盲目堆文档数量。

第三,上下文超限触发 429:多轮对话窗口开太大、或一次性把整篇长文档塞进提示,都会撑爆 token 上限。按业务把 withMaxMessages 收到 8–10,超长文本改为先摘要再入上下文;高频相似问答再叠加语义缓存,既能降成本也能减少限流。把这三点提前处理好,LangChain4j 的服务才真正扛得住线上流量。

九、小结

LangChain4j 用 AI Services 把”接大模型”这件事从一堆样板代码收敛成几个接口与注解:基础调用三行搞定,工具调用让模型能动手,ChatMemory 支撑多轮,contentRetriever 一行接入 RAG,返回类型还能直接映射成 POJO。对已经用 Spring Boot 的 Java 团队来说,它是把 AI 能力塞进现有服务最低摩擦的路径。下一步可以顺着工具链把 Embedding 模型私有化、给记忆接上持久化,逐步把 demo 打磨成可上线的智能助手。

上一篇 NestJS 实战:装饰器与依赖注入构建 Node.js 后端
下一篇 Vitepress 实战:从零搭建文档站与博客
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]