LangChain4j写一次工具调用-SpringAI的另一副面孔

LangChain4j 写一次工具调用:Spring AI 的另一副面孔

选型篇的末尾说过,两个框架的差别都在工单循环外面,循环本身一模一样。纸上对比不过瘾,这篇把 LangChain4j 这边跑通,同一个查天气的工单换个框架实现,差别落在哪一眼可见。跑完两个框架的 demo,选型时那三道判断题就不再是背下来的,而是亲手试过的。

台子怎么搭

LangChain4j 不挑工程,纯 Java 就能跑,加一个主依赖和你要的模型适配(版本口径:1.x 主线,核验 2026-10-25,动手前查 Maven 最新版)。

xml 复制代码
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>1.22.0</version>
</dependency>

模型客户端直接 new 出来即可,以 OpenAI 兼容接口为例,baseUrl 和 apiKey 指向你的供应商。

java 复制代码
ChatModel model = OpenAiChatModel.builder()
        .baseUrl("https://api.example.com/v1")
        .apiKey(System.getenv("AI_API_KEY"))
        .modelName("gpt-4o-mini")
        .build();

工具还是那个方法

和 Spring AI 一样,工具是普通类里的普通方法加一个 @Tool 注解,说明书放在描述里。包路径在 1.x 是 dev.langchain4j.service.tool.Tool,旧版本在 dev.langchain4j.agent.tool,以你用的版本为准。

java 复制代码
class WeatherTools {

    @Tool("查询指定城市的当前天气")
    String getWeather(String city) {
        return city + ",晴,25 度";   // 真实场景在这里发 HTTP
    }
}

方法名会被翻译成工单里的工具名,参数名翻译成参数名,这套翻译逻辑和 Spring AI 逐字相同。

接口就是门面

LangChain4j 的调用入口长得不像 Spring,它用一个接口当门面,AiServices 把接口和工具绑起来,调接口方法就像调本地代码。

java 复制代码
interface Assistant {
    String chat(String userMessage);
}

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

String answer = assistant.chat("北京今天多少度?");
System.out.println(answer);

工单循环在底下照常转:模型写工单、框架解析、执行 getWeather、回填、模型总结,只是这一圈活全藏在接口代理后面。这副面孔的气质是「声明式」,Spring AI 则是「构建器式」,两种都是 Java 程序员熟悉的语法,差别只在口味。
#mermaid-svg-s4id3FrWFMVRRZu4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s4id3FrWFMVRRZu4 .error-icon{fill:#552222;}#mermaid-svg-s4id3FrWFMVRRZu4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s4id3FrWFMVRRZu4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s4id3FrWFMVRRZu4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s4id3FrWFMVRRZu4 .marker.cross{stroke:#333333;}#mermaid-svg-s4id3FrWFMVRRZu4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s4id3FrWFMVRRZu4 p{margin:0;}#mermaid-svg-s4id3FrWFMVRRZu4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 .cluster-label text{fill:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 .cluster-label span{color:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 .cluster-label span p{background-color:transparent;}#mermaid-svg-s4id3FrWFMVRRZu4 .label text,#mermaid-svg-s4id3FrWFMVRRZu4 span{fill:#333;color:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 .node rect,#mermaid-svg-s4id3FrWFMVRRZu4 .node circle,#mermaid-svg-s4id3FrWFMVRRZu4 .node ellipse,#mermaid-svg-s4id3FrWFMVRRZu4 .node polygon,#mermaid-svg-s4id3FrWFMVRRZu4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s4id3FrWFMVRRZu4 .rough-node .label text,#mermaid-svg-s4id3FrWFMVRRZu4 .node .label text,#mermaid-svg-s4id3FrWFMVRRZu4 .image-shape .label,#mermaid-svg-s4id3FrWFMVRRZu4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-s4id3FrWFMVRRZu4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s4id3FrWFMVRRZu4 .rough-node .label,#mermaid-svg-s4id3FrWFMVRRZu4 .node .label,#mermaid-svg-s4id3FrWFMVRRZu4 .image-shape .label,#mermaid-svg-s4id3FrWFMVRRZu4 .icon-shape .label{text-align:center;}#mermaid-svg-s4id3FrWFMVRRZu4 .node.clickable{cursor:pointer;}#mermaid-svg-s4id3FrWFMVRRZu4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s4id3FrWFMVRRZu4 .arrowheadPath{fill:#333333;}#mermaid-svg-s4id3FrWFMVRRZu4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s4id3FrWFMVRRZu4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s4id3FrWFMVRRZu4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4id3FrWFMVRRZu4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s4id3FrWFMVRRZu4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4id3FrWFMVRRZu4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s4id3FrWFMVRRZu4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s4id3FrWFMVRRZu4 .cluster text{fill:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 .cluster span{color:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s4id3FrWFMVRRZu4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s4id3FrWFMVRRZu4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-s4id3FrWFMVRRZu4 .icon-shape,#mermaid-svg-s4id3FrWFMVRRZu4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s4id3FrWFMVRRZu4 .icon-shape p,#mermaid-svg-s4id3FrWFMVRRZu4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s4id3FrWFMVRRZu4 .icon-shape .label rect,#mermaid-svg-s4id3FrWFMVRRZu4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s4id3FrWFMVRRZu4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s4id3FrWFMVRRZu4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s4id3FrWFMVRRZu4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LangChain4j 的写法
Spring AI 的写法
@Tool 标记 Bean 方法
ChatClient.prompt()

.tools(bean).call()
@Tool 标记普通类方法
接口门面 + AiServices

assistant.chat(...)
同一个工单循环

写单、解析、执行、回填

逐点对照

Spring AI LangChain4j
工具形态 Spring Bean 加注解 普通类加注解
调用入口 ChatClient 流式构建 接口门面加 AiServices
装配方式 自动配置与注入 手工 builder
适合的工程 Spring 全家桶 任意 Java,包括无 Spring
自定义扩展 Advisor 链(拦截器思维) 自定义能力原语

亲手跑

三段代码加一个 main 就是完整程序,把 ChatModel 的配置换成你的供应商,运行 main 就能看到回答。更有价值的是打开日志(把 langchain4j 的日志级别调到 DEBUG),两个框架的工单报文都能看见,形状完全一致,这就是「循环相同」的实证。

跑完顺手做两个对照实验。把方法的参数加上 @Tool 的描述,看工单 JSON Schema 的变化,说明书是给模型读的。把 getWeather 里改成抛异常,对比两个框架把错误包回 tool_result 的措辞差异,这是排查问题时真会用到的细节。

记住一句话:框架选的是语法和装配习惯,工单循环永远是那一圈。

适用边界

这一篇管工具调用的最小闭环。LangChain4j 的记忆与检索模块、流式输出的 handler 写法、以及和 Spring 融合(Spring AI 之外的 LangChain4j Spring 集成)是另外的题。包路径和 Builder 的方法名随版本微调,本文按 2026-10-25 的 1.x 口径整理。两个框架的最小 demo 都在手里之后,选型篇那三道判断题可以重答一遍,答案会比上次笃定。

相关推荐
Flynn Liang1 天前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
七夜zippoe3 天前
LangChain4j vs Spring AI:Java AI 框架怎么选(对比表)
java·人工智能·spring·ai·langchain4j
lgcgkCQ5 天前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
Deng桑7 天前
SpringAI写第一个Agent-把工单循环跑起来
java·agent·工具调用·spring ai
古法老登瞎折腾7 天前
大数据智能运维实战(十一):运维 MCP 的工程化——权限、限流、审计与错误处理
java·大数据·运维·spring ai·mcp
yz_aiks8 天前
Spring AI 2.1 ChatMemory 已过时用什么替代:迁移到 Session Memory 实战(JDBC 存储与会话压缩)
java·spring boot·大模型·ai agent·spring ai
南归北隐9 天前
Spring AI Alibaba Graph框架实现MCP数据库查询实战
java·后端·spring ai·mcp·spring ai mcp
古法老登瞎折腾9 天前
大数据智能运维实战(九):让 AI 查日志——找到异常,也保留上下文
java·大数据·运维·人工智能·spring ai·mcp
古法老登瞎折腾9 天前
大数据智能运维实战(八):从调度任务追到计算引擎——补齐排查链路
java·大数据·运维·spring ai·mcp