Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)

最近在学习 Java AI 应用开发,先用 Spring Boot 搭了一个最小项目,完成了大模型调用,然后在这个基础上继续接入 AI Service 和 Tool Calling。

这篇文章记录整个实现过程,也把中间遇到的几个配置问题整理出来。

本文最终实现的效果是:

复制代码
用户发送自然语言请求
        ↓
Spring Boot 接收请求
        ↓
大模型判断是否需要调用工具
        ↓
LangChain4j 执行指定的 Java 方法
        ↓
工具返回业务数据
        ↓
大模型整理后回复用户

目前使用固定数据模拟查询,后续可以把 Tool 接到 Service、MyBatis 和 MySQL。


一、项目环境

本文使用的环境如下:

复制代码
JDK 17
Spring Boot 3.5.x
Maven
LangChain4j 1.18.0-beta28

一开始使用的是:

复制代码
JDK 8
Spring Boot 2.6.13

项目可以正常启动,但无法正常使用当前版本的 LangChain4j Spring Boot Starter,自动配置也没有生效。因此学习项目建议直接使用 JDK 17 和 Spring Boot 3。


二、创建 Spring Boot 项目

创建一个 Maven 类型的 Spring Boot 项目,依赖先选择:

复制代码
Spring Web

项目创建后,确认 pom.xml 中的 Java 版本为 17:

复制代码
<properties>
    <java.version>17</java.version>
</properties>

完整的核心依赖如下:

复制代码
<dependencies>

    <!-- Spring MVC、Controller、内置 Tomcat -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- OpenAI 兼容模型支持 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
        <version>1.18.0-beta28</version>
    </dependency>

    <!-- AI Service、Tool Calling 等 Spring 集成能力 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
        <version>1.18.0-beta28</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>

这两个 LangChain4j 依赖的作用不同。

复制代码
langchain4j-open-ai-spring-boot-starter

负责读取模型配置并创建 ChatModel

复制代码
langchain4j-spring-boot-starter

负责支持 @AiService、Tool Calling 等更高层能力。

添加完成后,刷新 Maven 依赖。


三、配置大模型

在下面位置创建配置文件:

复制代码
src/main/resources/application.yml

配置内容如下:

复制代码
server:
  port: 8080

langchain4j:
  open-ai:
    chat-model:
      base-url: http://langchain4j.dev/demo/openai/v1
      api-key: demo
      model-name: gpt-4o-mini
      temperature: 0.3
      log-requests: true
      log-responses: true

几个配置项需要分开理解。

base-url

复制代码
base-url: http://langchain4j.dev/demo/openai/v1

这是模型服务的接口地址,不是具体模型。

api-key

复制代码
api-key: demo

这是接口调用凭证。当前使用的是演示配置,只适合本地学习。

model-name

复制代码
model-name: gpt-4o-mini

这一项才是具体使用的模型名称。

整体关系可以理解为:

复制代码
base-url:请求发到哪个模型服务
api-key:用什么凭证调用
model-name:具体调用哪个模型

四、先完成最简单的模型调用

创建 Controller:

复制代码
package com.hsw.langchain4jdemo.controller;

import dev.langchain4j.model.chat.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/ai")
public class AiController {

    private final ChatModel chatModel;

    public AiController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatModel.chat(message);
    }
}

启动项目后访问:

复制代码
http://localhost:8080/api/ai/chat?message=请介绍一下Java

如果可以正常返回内容,说明下面这条链路已经打通:

复制代码
Controller
    ↓
ChatModel
    ↓
LangChain4j
    ↓
大模型接口
    ↓
返回文本

ChatModel 是什么

ChatModel 并不是大模型本身,而是 LangChain4j 对聊天模型能力定义的统一接口。

代码中注入的是:

复制代码
ChatModel chatModel

Spring 容器中实际创建的对象一般是对应模型的具体实现,例如:

复制代码
OpenAiChatModel

可以类比:

复制代码
List<String> list = new ArrayList<>();

List 是接口,ArrayList 是具体实现。

ChatModel 的作用就是屏蔽不同模型服务之间的请求细节,让业务代码统一使用:

复制代码
chatModel.chat(message);

五、使用 AI Service

直接调用 ChatModel 适合验证模型连接,但后面要做系统提示词、对话记忆、Tool Calling 和 RAG,通常会使用 AI Service。

创建接口:

复制代码
com.hsw.langchain4jdemo.assistant.Assistant

代码如下:

复制代码
package com.hsw.langchain4jdemo.assistant;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;

@AiService
public interface Assistant {

    @SystemMessage("""
            你是一名Java学习助手。
            回答尽量准确、清晰。
            遇到不确定的数据时不要编造。
            """)
    String chat(String message);
}

这里没有编写 AssistantImpl

LangChain4j 会在项目启动时为这个接口创建代理对象,并把它注册到 Spring 容器中。

调用过程类似:

复制代码
Assistant 接口
      ↓
LangChain4j 动态代理
      ↓
ChatModel
      ↓
大模型

Controller 可以改成:

复制代码
package com.hsw.langchain4jdemo.controller;

import com.hsw.langchain4jdemo.assistant.Assistant;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/ai")
public class AiController {

    private final Assistant assistant;

    public AiController(Assistant assistant) {
        this.assistant = assistant;
    }

    @GetMapping("/assistant")
    public String assistant(@RequestParam String message) {
        return assistant.chat(message);
    }
}

测试地址:

复制代码
http://localhost:8080/api/ai/assistant?message=线程池是什么

六、添加第一个 Tool

普通模型只能根据已有知识生成内容,无法直接查询项目中的数据库或业务接口。

例如用户输入:

复制代码
帮我查询用户1001的信息

模型并不知道项目里的用户数据。如果直接回答,很可能会编造。

Tool Calling 的作用就是让模型在需要真实数据时,调用后端提供的 Java 方法。

创建工具类:

复制代码
com.hsw.langchain4jdemo.tool.UserTool

代码如下:

复制代码
package com.hsw.langchain4jdemo.tool;

import dev.langchain4j.agent.tool.Tool;
import org.springframework.stereotype.Component;

@Component
public class UserTool {

    @Tool("根据用户ID查询用户信息")
    public String queryUser(Long userId) {

        // 暂时使用固定数据模拟数据库查询
        if (Long.valueOf(1001L).equals(userId)) {
            return """
                    用户ID:1001
                    用户名:张三
                    年龄:25
                    会员等级:黄金会员
                    """;
        }

        return "没有找到对应的用户";
    }
}

这里的 @Tool 不是 Controller 接口,也不要求它是 HTTP 接口。

它就是一个普通 Java 方法,只不过通过 @Tool 告诉大模型:

复制代码
系统拥有一个根据用户ID查询用户信息的能力

真实项目中一般会写成:

复制代码
UserTool
   ↓
UserService
   ↓
UserMapper
   ↓
MySQL

例如:

复制代码
@Component
public class UserTool {

    private final UserService userService;

    public UserTool(UserService userService) {
        this.userService = userService;
    }

    @Tool("根据用户ID查询用户信息")
    public UserDTO queryUser(Long userId) {
        return userService.getById(userId);
    }
}

七、把 Tool 注册到 AI Service

修改 Assistant

复制代码
package com.hsw.langchain4jdemo.assistant;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.spring.AiService;

@AiService(
        tools = "userTool"
)
public interface Assistant {

    @SystemMessage("""
            你是一个用户信息查询助手。

            查询用户信息时必须调用工具,
            不允许自行编造用户数据。
            """)
    String chat(String message);
}

这里写的是:

复制代码
tools = "userTool"

而不是:

复制代码
tools = UserTool.class

因为当前版本中,tools 属性接收的是 Spring Bean 名称。

工具类是:

复制代码
@Component
public class UserTool

Spring 默认会把它注册成:

复制代码
userTool

所以 AI Service 中填写:

复制代码
tools = "userTool"

如果手动指定 Bean 名称:

复制代码
@Component("userQueryTool")
public class UserTool {
}

对应配置也需要改成:

复制代码
@AiService(
        tools = "userQueryTool"
)

八、测试 Tool Calling

启动项目后访问:

复制代码
http://localhost:8080/api/ai/assistant?message=帮我查询用户1001的信息

完整流程如下:

复制代码
用户输入:
帮我查询用户1001的信息
        ↓
Assistant 接收消息
        ↓
大模型分析用户意图
        ↓
模型决定调用 queryUser
        ↓
LangChain4j 执行 UserTool.queryUser(1001)
        ↓
Java 方法返回用户数据
        ↓
工具结果再次交给大模型
        ↓
大模型整理后返回最终答案

需要注意,大模型并没有直接执行 Java 方法。

实际分工是:

复制代码
大模型:决定调用哪个工具以及传递什么参数
LangChain4j:解析调用请求并执行 Java 方法
Java 业务代码:真正查询数据库或调用业务服务

九、wiringMode 是什么

在配置 AI Service 时,可能会看到下面这种写法:

复制代码
@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        tools = "userTool"
)

wiringMode 控制 AI Service 的依赖如何装配。

自动装配

默认情况下,LangChain4j 会从 Spring 容器中查找合适的模型和其他依赖。

复制代码
@AiService(
        tools = "userTool"
)

对于当前只有一个 ChatModel 的学习项目,使用自动装配比较简单。

显式装配

复制代码
wiringMode = AiServiceWiringMode.EXPLICIT

表示不再自动选择,而是要求开发人员明确指定所使用的模型、工具等 Bean。

它适合以下场景:

复制代码
项目中存在多个 ChatModel
不同 Agent 使用不同模型
不同 Agent 只能使用指定工具
需要严格控制依赖关系

如果写了:

复制代码
wiringMode = AiServiceWiringMode.EXPLICIT

却只指定了 Tool,没有指定 ChatModel,就可能出现:

复制代码
Please specify either chatModel or streamingChatModel

对于当前项目,直接删除 EXPLICIT,使用自动装配即可:

复制代码
@AiService(
        tools = "userTool"
)

十、常见问题

1. 找不到 ChatModel Bean

报错:

复制代码
required a bean of type 'dev.langchain4j.model.chat.ChatModel'
that could not be found

优先检查以下内容。

配置文件是否放在:

复制代码
src/main/resources/application.yml

配置前缀是否正确:

复制代码
langchain4j:
  open-ai:
    chat-model:

依赖是否使用了 Starter:

复制代码
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>

而不是只添加普通的模型依赖。

还需要检查 JDK、Spring Boot 和 LangChain4j 版本是否匹配。


2. tools 属性类型错误

错误写法:

复制代码
@AiService(
        tools = UserTool.class
)

IDEA 会提示需要 String[],实际提供的是 Class<UserTool>

正确写法:

复制代码
@AiService(
        tools = "userTool"
)

这里填写的是 Spring Bean 名称。


3. 提示必须指定 chatModel

报错:

复制代码
Please specify either chatModel or streamingChatModel

通常是因为使用了:

复制代码
wiringMode = AiServiceWiringMode.EXPLICIT

但没有明确指定模型。

初学阶段可以先使用自动装配:

复制代码
@AiService(
        tools = "userTool"
)

4. 找不到名为 chatModel 的 Bean

报错:

复制代码
required a bean named 'chatModel' that could not be found

通常是手动写了:

复制代码
chatModel = "chatModel"

这里要求 Spring 容器里必须存在一个名称正好为 chatModel 的 Bean。

但自动配置生成的模型 Bean 不一定叫这个名字。

如果当前只有一个模型,直接删除 Bean 名称配置,让 LangChain4j 按类型自动装配更合适。


十一、当前项目结构

完成后,项目结构大致如下:

复制代码
src/main/java/com/hsw/langchain4jdemo
├── LangChain4jDemoApplication.java
├── assistant
│   └── Assistant.java
├── controller
│   └── AiController.java
└── tool
    └── UserTool.java

src/main/resources
└── application.yml

当前调用关系:

复制代码
AiController
      ↓
Assistant
      ↓
ChatModel
      ↓
大模型
      ↓
判断是否调用 UserTool
      ↓
UserTool
      ↓
返回业务数据
      ↓
大模型生成最终回答

十二、这算不算 Agent

只调用:

复制代码
chatModel.chat(message);

属于普通的大模型接入,还不能算 Agent。

加入 Tool Calling 后,模型已经可以:

复制代码
理解用户需求
选择工具
生成工具参数
获取工具执行结果
根据结果继续回答

这已经具备了基础 Agent 能力。

不过距离完整业务 Agent 还有一些内容:

复制代码
多轮对话记忆
多个工具连续调用
数据库真实查询
RAG 知识库
权限校验
高风险操作二次确认
幂等控制
工具调用日志
异常重试和超时处理

下一步可以把当前的固定用户数据替换成:

复制代码
Spring Boot
+ MyBatis-Plus
+ MySQL
+ UserTool

让模型真正查询数据库中的用户信息。

相关推荐
程序员黑豆5 小时前
Java 注释详解:单行、多行与文档注释的完整指南
java·前端·ai编程
hey_sml6 小时前
JAVA每日一学---CompletableFuture中thenApply与thenCompose区别详解
java·开发语言
Doraemomo6 小时前
数据结构-环形链表
java·数据结构·链表
萧瑟余晖7 小时前
Java深入解析篇十七之SpringCloud
java·开发语言
是未才8 小时前
从输入 URL 到页面返回:DNS、路由、TLS 与 HTTP 完整链路
java·后端·计算机网络
ttod_qzstudio8 小时前
Java 常用语法极简通关(五):类与对象——字段、方法、构造器、this 与 static
java·开发语言·python
萧瑟余晖9 小时前
Java深入解析篇十七之Spring Security
java·开发语言·spring
不老刘10 小时前
【Java入门】Java服务部署方式全景对比:从java -jar到K8s
java·kubernetes·jar
离陌在学C#12 小时前
C# 重载与重写:深入理解面向对象编程的核心概念
java·c#
洛阳泰山12 小时前
AI 应用层被 Python 卷成红海,为什么我偏要用 Java 造一个 RAG + 工作流引擎?
java·人工智能·后端