☰
Agent智能体开发实战:用LangChain4j在Java中实现Function Calling
2026/10/3 6:32:01 网站建设 项目流程

1. Java 开发者为什么需要 LangChain4j 做 Agent 智能体

大模型本身只是一个文字回答机器,它看不到你后台数据库、商户数据、活动数据、短信剩余额度。举个短信项目的例子:运营问 AI「给商户 A 生成 618 家电短信」,AI 不知道商户 A 还有多少短信余额、活动是否到期。Function Calling 就是给 AI 配一套可拨打的业务电话,AI 自己判断缺数据时,主动调用你写好的 Java 接口查数据,拿到真实业务数据后,再生成准确文案。

LangChain4j 是 Java 生态里做 Agent 智能体最顺手的框架之一。它把工具注册、参数绑定、调用链编排、会话记忆这些脏活累活都封装好了,你只需要用@Tool注解标记业务方法,框架自动提取方法描述和参数说明交给大模型识别。对于 Java 后端来说,这意味着不用切换到 Python 生态,直接在 Spring Boot 项目里就能跑通一个可扩展的 Agent。

这篇文章面向的是有 Java 基础、想在自己的业务系统里落地 Agent 智能体的开发者。我会从依赖配置开始,一步步带你跑通工具注册、参数绑定、调用链编排,最后用 TaoToken 统一 Key 接入模型服务,在本地跑通一个完整的 Java Agent 示例。整个过程不需要你手写 if/else 去控制调用顺序,LLM 会自主规划任务步骤,框架负责循环交互。

适合谁看:正在做短信平台、客服系统、营销工具等需要 AI 调用业务接口的 Java 后端;想用 LangChain4j 但不知道从哪下手的开发者;已经用过 Spring AI 但觉得 Agent 能力不够灵活的团队。不适合谁:只想调一次大模型 API 生成文案、不需要多步骤工具联动的场景,那种直接同步调用即可,上 Agent 反而增加复杂度。

我试过在本地用 LangChain4j 0.35.0 版本跑通整个流程,踩过依赖冲突和工具描述不清晰的坑,下面把可复制的配置和代码都整理出来。

2. TaoToken 前置准备:统一 Key 与 API 通道接入模型服务

在写 Agent 代码之前,先把模型服务通道准备好。LangChain4j 本身不绑定任何模型厂商,它通过ChatLanguageModel接口对接不同的模型服务。你可以用 OpenAI 兼容的接口,也可以用 TaoToken 提供的统一 API 通道,这样切换模型时不用改代码,只改配置。

TaoToken 的作用是统一 Key 和 API 通道。你不需要在代码里硬编码多个厂商的密钥,也不用为每个模型单独写适配层。它提供 OpenAI 兼容的/v1/chat/completions接口,LangChain4j 的OpenAiChatModel可以直接对接。

2.1 获取 API Key 与确认 Base URL

首先到 TaoToken 控制台创建一个 API Key。地址是https://taotoken.net/api-keys,登录后点「创建密钥」,复制生成的 Key,格式类似sk-xxxxxxxx。这个 Key 只显示一次,记得保存到安全的地方。

Base URL 用https://taotoken.net/api,注意不要加 UTM 参数,这是给程序调用的地址。模型 ID 根据你需要的模型填写,比如gpt-4o-mini、claude-3-5-sonnet等,具体可用模型列表在控制台的模型对话页面可以查看。

2.2 在 Spring Boot 中配置模型 Bean

我习惯把模型配置放在application.yml里,通过@ConfigurationProperties注入。这样本地开发和线上环境可以用不同的配置文件,不用改代码。

# application.yml langchain4j: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY:sk-your-key-here} model-name: gpt-4o-mini temperature: 0.7 timeout: 60s max-retries: 2

然后在配置类里创建ChatLanguageModelBean:

import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.chat.ChatLanguageModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; @Configuration public class LlmConfig { @Value("${langchain4j.openai.base-url}") private String baseUrl; @Value("${langchain4j.openai.api-key}") private String apiKey; @Value("${langchain4j.openai.model-name}") private String modelName; @Bean public ChatLanguageModel chatLanguageModel() { return OpenAiChatModel.builder() .baseUrl(baseUrl) .apiKey(apiKey) .modelName(modelName) .temperature(0.7) .timeout(Duration.ofSeconds(60)) .maxRetries(2) .logRequests(true) .logResponses(true) .build(); } }

这里logRequests和logResponses打开后,控制台会打印完整的请求和响应 JSON,方便排查工具调用是否被正确触发。生产环境可以关掉,避免日志量过大。

2.3 依赖配置:pom.xml 关键片段

LangChain4j 的依赖需要和 Spring Boot 版本匹配。我用的是 Spring Boot 3.2.x + LangChain4j 0.35.0,核心依赖如下:

<properties> <langchain4j.version>0.35.0</langchain4j.version> </properties> <dependencies> <!-- LangChain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- OpenAI 兼容模型接入 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- Spring Boot 集成 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 会话记忆持久化(可选,用 Redis 时加) --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-redis</artifactId> <version>${langchain4j.version}</version> </dependency> </dependencies>

注意:langchain4j-spring-boot-starter会自动装配一些 Bean,如果你自己定义了ChatLanguageModel,可能会冲突。解决办法是在启动类上加@SpringBootApplication(exclude = {LangChain4jAutoConfig.class}),或者干脆不用 starter,只引核心包手动配置。我选择手动配置,控制权更清晰。

依赖拉下来后,先跑一个最简单的main方法验证模型通道是否通:

public class QuickTest { public static void main(String[] args) { ChatLanguageModel model = OpenAiChatModel.builder() .baseUrl("https://taotoken.net/api") .apiKey(System.getenv("TAOTOKEN_API_KEY")) .modelName("gpt-4o-mini") .build(); String answer = model.generate("用一句话解释什么是 Function Calling"); System.out.println(answer); } }

如果控制台能打印出回答,说明 TaoToken 通道已经通了。如果报 401,检查 Key 是否复制完整;如果报连接超时,检查网络是否能访问taotoken.net。这一步过了再往下写 Agent,否则后面排障会混淆是模型通道问题还是代码问题。

3. 可复制配置:用 @Tool 注册工具与 Agent 构建

这一节是核心。我会用一个短信业务场景来演示:用户让 Agent 生成商户营销短信,Agent 需要先查商户剩余短信额度、再查活动有效期,最后生成合规文案。整个过程 Agent 自主规划,不需要你写调用顺序。

3.1 用 @Tool 注解封装业务工具

LangChain4j 提供@Tool注解,标记任意业务方法为 AI 可用工具。框架会自动提取方法描述、参数说明交给大模型识别。工具描述写得越清楚,LLM 判断什么时候调用就越准确。

import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; @Component public class SmsBusinessTool { /** * @Tool 内的描述会自动传给 LLM,AI 依靠这段文字判断什么场景调用该工具 * @param merchantId 商户唯一标识 * @return 商户剩余可发送短信条数 */ @Tool("用于查询指定商户剩余短信额度,额度不足则无法生成营销短信") public Integer queryMerchantSmsQuota(String merchantId) { // 模拟数据库查询逻辑,实际替换为 smsQuotaMapper.getLeftCount(merchantId) if ("001".equals(merchantId)) { return 120; } return 0; } @Tool("根据活动id查询活动起止有效期,判断活动是否过期") public String queryActivityTime(String activityId) { // 模拟查询活动接口 return "活动有效期:2026-08-01 ~ 2026-08-20"; } @Tool("查询指定行业的短信合规规则,生成文案前必须调用") public String queryComplianceRule(String industry) { return "家电行业短信合规要求:不得使用'最'、'第一'等极限词,不得承诺具体效果"; } }

三个工具分别对应额度查询、活动时间查询、合规规则查询。注意@Tool里的描述要写清楚「什么时候调用」,而不是只写「这个方法是干什么的」。比如「额度不足则无法生成营销短信」这句话,就是告诉 LLM 在生成文案前必须先查额度。

3.2 组装 Agent 运行环境

Agent 的构建用Agent.builder(),需要绑定模型、工具、记忆、系统提示词。系统提示词是激活 LLM 自主规划能力的核心,要明确告知大模型需要自主拆分步骤、主动调用工具。

import dev.langchain4j.agent.Agent; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatLanguageModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration public class SmsAgentConfig { @Bean public Agent smsAgent( ChatLanguageModel chatModel, List<Object> allTools, ChatMemoryProvider chatMemoryProvider ) { return Agent.builder() // 绑定思考主体:LLM(自主规划能力载体) .chatLanguageModel(chatModel) // 向模型注入全部可用业务工具 .tools(allTools) // 会话记忆:保存历史对话、工具查询结果 .chatMemoryProvider(chatMemoryProvider) // 核心:系统提示词,激活 LLM 自主任务规划 .systemPrompt(""" 你是专业短信运营助手,收到用户复杂需求后,请自行拆解完整执行步骤。 如果缺少商户额度、活动时间、合规规则等外部数据,主动调用提供的工具获取信息; 收集齐全所有必要数据后,再输出最终合规短信文案,不要中途给出不完整答案。 """) // 框架限制最大工具调用次数,防止 LLM 无限循环调用工具死锁 .maxToolExecutions(3) .build(); } @Bean public ChatMemoryProvider chatMemoryProvider() { return memoryId -> MessageWindowChatMemory.withMaxMessages(20); } }

maxToolExecutions(3)是生产环境必备的保险丝。LLM 有时会陷入「调用工具→结果不满意→再调用」的循环,限制最大次数可以避免死锁。一般设 3 到 5 次足够。

3.3 对外提供调用入口

Controller 里只需要一行agent.run(sessionId, userDemand)启动整套自主流程,没有分步处理、没有循环、没有判断。

import dev.langchain4j.agent.Agent; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class SmsAgentController { private final Agent smsAgent; public SmsAgentController(Agent smsAgent) { this.smsAgent = smsAgent; } @GetMapping("/agent/generateSms") public String autoGenerateSms( @RequestParam String sessionId, @RequestParam String userDemand ) { // 唯一入口方法,触发完整自主规划+工具循环流程 return smsAgent.run(sessionId, userDemand); } }

3.4 工具调用前置三层校验

生产环境不能直接把 LLM 生成的参数丢给业务接口。模型生成的参数可能为空、格式错误,甚至被 Prompt 注入篡改。需要在工具方法内部做三层校验:

@Tool("用于查询指定商户剩余短信额度,额度不足则无法生成营销短信") public Integer queryMerchantSmsQuota(String merchantId) { // 第一层:入参校验 if (merchantId == null || merchantId.isBlank()) { throw new IllegalArgumentException("商户ID不能为空"); } if (!merchantId.matches("\\d{3,10}")) { throw new IllegalArgumentException("商户ID格式错误"); } // 第二层:权限校验(从当前会话上下文获取操作人) String operator = SessionContext.getCurrentOperator(); if (!permissionService.hasMerchantAccess(operator, merchantId)) { throw new SecurityException("无权查询该商户数据"); } // 第三层:调用记录审计 auditLogService.record(operator, "queryMerchantSmsQuota", merchantId); return smsQuotaMapper.getLeftCount(merchantId); }

这三层校验和 Agent 的任务规划流程无关,是纯工程保障。入参校验防止模型生成脏数据,权限校验防止越权查询,审计记录方便后期排查问题。

3.5 工具调用异常捕获与降级

业务接口报错(数据库宕机、查询超时)时,要捕获异常并返回兜底文本给大模型,避免 AI 无限循环重复调用失败工具。

@Tool("用于查询指定商户剩余短信额度,额度不足则无法生成营销短信") public Integer queryMerchantSmsQuota(String merchantId) { try { // 校验逻辑... return smsQuotaMapper.getLeftCount(merchantId); } catch (Exception e) { log.error("查询商户额度失败, merchantId={}", merchantId, e); // 返回兜底值,让 LLM 知道查询失败,而不是抛异常中断流程 return -1; } }

返回-1表示查询失败,LLM 看到这个结果会调整策略,比如提示用户稍后重试,而不是继续调用同一个工具。如果直接抛异常,框架会中断整个 Agent 流程,用户体验很差。

4. 验证请求与成功结果:跑通完整 Function Calling 链路

配置写完后,启动 Spring Boot 应用,用 curl 或浏览器发起请求验证。

4.1 发起验证请求

curl "http://localhost:8080/agent/generateSms?sessionId=test-001&userDemand=帮商户001生成一条618家电营销短信"

4.2 观察控制台日志

因为开了logRequests和logResponses,控制台会打印完整的交互过程。你会看到类似这样的日志:

第一次请求:LLM 返回工具调用指令queryMerchantSmsQuota(merchantId="001")。

框架执行工具,拿到结果120,追加到上下文。

第二次请求:LLM 返回工具调用指令queryActivityTime(activityId="618")。

框架执行工具,拿到结果活动有效期:2026-08-01 ~ 2026-08-20。

第三次请求:LLM 返回工具调用指令queryComplianceRule(industry="家电")。

框架执行工具,拿到合规规则。

第四次请求:LLM 判断数据齐全,不再调用工具,直接生成最终文案。

4.3 成功结果示例

最终返回的文案类似:

【家电狂欢】尊敬的商户,您的618家电营销短信已生成: "618家电盛典来袭,精选好物等您选购。活动时间8月1日至8月20日, 详情请咨询门店。退订回T" 剩余短信额度:120条,可放心发送。

注意文案里没有出现「最」「第一」等极限词,因为 Agent 在生成前调用了合规规则工具。活动时间也和查询结果一致,没有编造过期活动。

4.4 验证工具调用次数

在SmsBusinessTool的每个方法里加一行日志:

@Tool("用于查询指定商户剩余短信额度,额度不足则无法生成营销短信") public Integer queryMerchantSmsQuota(String merchantId) { log.info("工具被调用: queryMerchantSmsQuota, merchantId={}", merchantId); // ... }

重新发起请求,观察日志里三个工具是否都被调用了一次。如果某个工具没被调用,说明@Tool描述不够清晰,LLM 没判断出需要调用它。这时候要回去改描述,而不是改代码逻辑。

4.5 验证会话记忆

同一个sessionId连续发两次请求,第二次问「刚才查的商户额度是多少」,Agent 应该能从记忆里直接回答,不需要重新调用工具。这说明ChatMemoryProvider生效了。

curl "http://localhost:8080/agent/generateSms?sessionId=test-001&userDemand=刚才查的商户额度是多少"

如果 Agent 重新调用了queryMerchantSmsQuota,说明记忆没生效。检查MessageWindowChatMemory.withMaxMessages(20)是否配置正确,以及sessionId是否一致。

5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth

这一节整理我在本地跑 LangChain4j + TaoToken 时踩过的坑,对照真实报错给出排查路径。

5.1 401 Unauthorized

报错原文:

dev.langchain4j.exception.AuthenticationException: 401 Unauthorized

原因:API Key 错误或未正确传递。排查步骤:检查application.yml里的api-key是否以sk-开头;检查环境变量TAOTOKEN_API_KEY是否设置;检查 Key 是否被删除或过期。如果用的是 TaoToken 的 Key,到控制台确认 Key 状态是「启用」。

5.2 local proxy failed / Connection refused

报错原文:

java.net.ConnectException: Connection refused: no further information

或者:

local proxy failed: connect timed out

原因:Base URL 写错,或者本地网络无法访问taotoken.net。排查步骤:确认base-url是https://taotoken.net/api,不要多加/v1或漏掉https;用curl https://taotoken.net/api/v1/models测试网络连通性;如果公司网络有防火墙,确认出站 443 端口开放。

5.3 reading choices 相关报错

报错原文:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "choices" ...

或者:

Error reading choices from response

原因:模型返回的 JSON 结构和 LangChain4j 期望的不一致。常见于用了非 OpenAI 兼容的接口,或者模型 ID 写错导致返回了错误信息。排查步骤:打开logResponses,看原始返回 JSON 里是否有choices字段;确认model-name是 TaoToken 支持的模型 ID;如果返回的是{"error": {...}},说明模型 ID 不存在,换一个可用的。

5.4 OAuth 相关报错

报错原文:

OAuth token request failed

或者:

invalid_client: client authentication failed

原因:误用了需要 OAuth 的接口地址,或者 Key 类型不对。TaoToken 的 API Key 是直接放在Authorization: Bearer头里的,不需要走 OAuth 流程。排查步骤:确认base-url是https://taotoken.net/api而不是其他地址;确认没有在代码里配置clientId、clientSecret等 OAuth 参数;如果用了 Spring Security OAuth 客户端,检查是否误拦截了 LangChain4j 的请求。

5.5 工具未被调用

现象:Agent 直接返回文案,没有调用任何工具,导致文案里缺少商户额度、活动时间等真实数据。

原因:@Tool描述不够清晰,LLM 没判断出需要调用。排查步骤:把@Tool描述改成「生成营销短信前必须调用此工具查询商户额度」;在systemPrompt里明确写「如果缺少商户额度、活动时间、合规规则等外部数据,主动调用提供的工具获取信息」;打开logRequests看工具描述是否被正确传给 LLM。

5.6 工具调用死循环

现象:Agent 反复调用同一个工具,超过maxToolExecutions后中断。

原因:工具返回结果不符合 LLM 预期,LLM 认为数据不够,继续调用。排查步骤:检查工具返回格式是否稳定,比如不要有时返回Integer有时返回String;在工具内部捕获异常返回兜底值,而不是抛异常;适当调大maxToolExecutions到 5,但不要无限大。

5.7 会话记忆不生效

现象:同一个sessionId第二次请求,Agent 不记得之前的对话。

原因:ChatMemoryProvider配置错误,或者sessionId不一致。排查步骤:确认chatMemoryProviderBean 被正确注入到 Agent;确认两次请求的sessionId完全相同;如果用 Redis 持久化,检查 Redis 连接是否正常。

5.8 依赖冲突

报错原文:

java.lang.NoSuchMethodError: dev.langchain4j.model.chat.ChatLanguageModel.generate

原因:LangChain4j 版本和 Spring Boot Starter 版本不匹配。排查步骤:统一langchain4j.version属性,所有 LangChain4j 依赖用同一个版本;如果用了langchain4j-spring-boot-starter,检查它依赖的 LangChain4j 版本是否和手动引入的冲突;用mvn dependency:tree查看实际生效的版本。

6. 语义一致 CTA:接入文档与 Coding Plan

Agent 跑通后,下一步是把它接入你的实际业务系统。如果你需要更详细的接入文档,包括流式输出、多模型切换、Token 成本统计等进阶配置,可以到 TaoToken 的接入文档页面查看:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc。

如果你打算长期做 Java Agent 开发,需要频繁调用模型、跑批量任务、做多轮调试,可以看看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan。它适合需要稳定模型通道、按量计费的开发场景。

想先验证模型效果,可以直接在模型对话页面测试:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=chat。输入你的 Prompt,看看模型返回是否符合预期,再决定用哪个模型 ID 接入代码。

API Key 管理在控制台:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys。建议为不同环境创建不同的 Key,方便排查和隔离。

最后提醒一个实际经验:Agent 的工具描述要反复调试。我一开始把@Tool描述写成「查询商户额度」,LLM 经常不调用,直接编造一个额度。改成「生成营销短信前必须调用此工具查询商户剩余额度,额度不足则无法生成」之后,调用率明显提升。工具描述是给 LLM 看的 Prompt,不是给人看的注释,这一点和传统 Java 开发习惯不同,需要适应。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询