您的位置首页 >要闻 > 人工智能 >

Java 接入大模型 Function Calling原理拆解与三种实现路线

已完成联网核对:OpenAI 官方函数调用文档、DeepSeek Responses/Tool Calls 文档、阿里云百炼 Java SDK 示例、Spring AI 2.0 工具调用参考、LangChain4j 工具文档,均在近期更新。以下按你的固定模板成稿。

Function Calling 这件事,本质上是模型把 “我想调哪个函数、传什么参数” 写成一段结构化 JSON 交给你,真正执行代码的是你的 Java 服务,模型从头到尾都没有碰过你的数据库。想跑通它,只需要守住一条闭环:请求里带 tools 定义 → 模型返回函数名和 arguments → Java 端校验参数并执行 → 把结果以工具消息回传 → 模型生成最终答复或继续下一轮调用。Java 落地有三条路线:自己手写 HTTP 循环(控制力最强、排障最直接)、Spring AI 2.0 的 @Tool + ToolCallingAdvisor(声明式、循环托管)、LangChain4j 的 AI Services + @Tool(两层抽象、动态工具灵活)。2026 年这三个变化值得注意:strict: true 的结构化输出已成主流厂商标配,工具调用默认并行,MCP 让远程工具和本地 @Tool 共用同一套 ToolCallback 接口。

一、一次工具调用到底发生了什么

官方文档把这条链路拆成五步,缺任何一步都拿不到最终答案:

步骤发起方关键内容
1你的 Java 应用在请求中带 tools 数组,每个工具含 name、description、parameters(JSON Schema)
2模型返回工具调用,含调用 id、函数名、arguments(是 JSON 字符串,不是对象)
3你的 Java 应用解析参数、校验、分发到本地方法执行
4你的 Java 应用以工具角色消息回传结果,必须带对应的调用 id
5模型结合工具输出生成自然语言答复,或再发起下一批调用

两套 API 的形状不一样,混着记最容易出 bug:

对比项Chat CompletionsResponses API
工具定义位置tools[].function.{name,description,parameters}tools[] 顶层直接放 name、parameters
模型返回message.tool_calls[],含 id / function.name / function.argumentsfunction_call item,含 call_id / name / arguments
结果回传role: "tool" + tool_call_idfunction_call_output item + call_id
多轮衔接自己维护完整 messages可用 previous_response_id 续接

DeepSeek 的文档里有一句提醒很值得贴在代码注释上:模型并不总是生成合法 JSON,还可能虚构出你 schema 里没定义的参数,所以执行前必须校验。

二、模型眼里的 “工具”,其实就是一段 JSON Schema

工具描述写得好不好,直接决定模型会不会在该调的时候调、调得对不对。几条硬经验:

  • name 要唯一且稳定,别用中文、别带空格;OpenAI 与 DeepSeek 都限制在 128 字符内,通义千问的 DashScope 限制更紧,工具名最长 64 字符。
  • description 写 “什么时候用”,不要写 “这是什么”。写 “用户询问物流进度、发货时间、退款到账时调用”,比写 “订单查询工具” 命中率高得多。
  • parameters 只放模型必须填的字段,能用枚举就别用自由文本。
  • 开 strict: true 时,Schema 必须落在厂商支持的子集内,否则请求直接报 400。

跨厂商 strict 子集的公共部分,可以按这张表自查:

Schema 特性strict 模式下的处理
根节点必须是 type: "object",不能是数组或 anyOf
每个 object必须写 additionalProperties: false
required所有属性都要列进 required
可选字段用 "type": ["string","null"] 模拟,字段仍留在 required 里
oneOf / allOf / not / if-then-else不支持,改用带判别字段的 anyOf
prefixItems(元组数组)不支持,改用同构数组 items
pattern / format / minLength多数厂商 strict 模式不支持,别指望它做格式约束
minimum / maximum / minItems同上,校验交给 Java 端
嵌套深度不超过 5 层
属性总数不超过 100 个

还要分清 JSON mode 和 Structured Outputs:JSON mode 只保证 “能解析”,不保证符合你的 Schema;strict: true 才保证参数严格匹配。另外 Azure 与 OpenAI 都明确写了,结构化输出与并行工具调用不兼容,要开 strict 就把 parallel_tool_calls 设为 false。

三、三条 Java 路线先摆在一起看

路线代表写法循环谁管适合场景主要代价
手写 HTTP官方 Java SDK / HttpClient + Jackson自己多厂商适配、需要精细排障、轻量服务参数校验、重试、轮次控制全自己写
Spring AI 2.0@Tool、MethodToolCallback、FunctionToolCallbackToolCallingAdvisorSpring Boot 体系内、要 MCP 与可观测性2.0 有破坏性变更,旧写法要迁移
LangChain4jToolSpecification + ToolExecutor、AI ServicesAI Service 内部工具动态装配、多智能体编排概念多,需理解两层抽象

四、路线 A:不依赖框架,自己把循环跑通

这条路线最能看清原理,代码骨架就四段:定义工具、发请求、分发执行、回传结果。

record ToolCall(String id, String name, String arguments) {}

for (int turn = 0; turn < 6; turn++) {

var resp = client.chat(messages, tools); // 带上 tools 定义

var msg = resp.choices().get(0).message();

if (msg.toolCalls() == null || msg.toolCalls().isEmpty()) return msg.content();

messages.add(msg); // 先放模型这条带 tool_calls 的消息

for (var call : msg.toolCalls()) {

var args = mapper.readValue(call.function().arguments(), WeatherArgs.class); // 校验后再用

var result = dispatcher.invoke(call.function().name(), args);

messages.add(toolMessage(call.id(), result)); // 必须带回同一个 id

}

}

真正决定这套代码能不能上生产的,不是循环本身,而是循环外围的六件事:

防护点具体做法
参数校验Jackson 反序列化 + Jakarta Validation 双重校验,非法参数直接返回结构化错误,不要执行
轮次上限硬编码最大轮次(经验值 5–8),超限返回 “需要人工介入”,防止死循环烧钱
幂等写操作类工具用业务单号做幂等键,模型重试同一调用不会重复扣款、重复下单
超时与重试单工具超时独立设置,只对读操作重试,写操作不自动重试
权限隔离工具按租户、角色做白名单,别把所有工具无差别暴露给所有会话
审计记录调用 id、函数名、入参、出参、耗时,便于回溯模型为什么这么调

还有一条容易忽略:工具返回的内容对模型来说是 “不可信输入”。如果工具会读到外部网页、用户上传文本,返回前要过滤指令性内容,否则等于给提示注入开了后门。

五、路线 B:Spring AI 2.0 的声明式写法

在 Spring 体系里,最快的方式是给方法打注解,框架自动生成 JSON Schema 并托管整个循环。

class OrderTools {

@Tool(description = "按订单号查询订单状态,用户问物流、发货、退款进度时调用")

OrderStatus query(@ToolParam(description = "订单号,形如 SO20261113001") String orderNo,

ToolContext ctx) {

return repo.find(orderNo, ctx.getContext().get("tenantId")); // 租户信息不透给模型

}

}

String answer = ChatClient.create(chatModel)

.prompt("帮我看看 SO20261113001 到哪了")

.tools(new OrderTools())

.toolContext(Map.of("tenantId", "acme"))

.call().content();

几个必须知道的点:

  • @Tool 可以打在 public、package-private、private 方法上,静态方法也行;name 默认取方法名,同一批工具里不能重名。
  • 参数默认全是必填,要可选就加 @ToolParam(required = false) 或 @Nullable。
  • ToolContext 里的东西只到工具,不会进模型上下文,适合放租户 ID、用户 ID、追踪号。
  • returnDirect = true 表示工具结果直接返回给调用方,不再绕回模型,适合检索类工具,省一次往返。
  • 循环由 ToolCallingAdvisor 负责,它是递归 advisor,每轮重新进入下游链;DefaultChatClient 只允许链上存在一个 ToolAdvisor,注册第二个会直接报错。
  • 2.0 移除了 SpringBeanToolCallbackResolver 和 toolNames(),裸 Function Bean 按名字解析的写法没了,工具必须注册成显式的 ToolCallback Bean,用 FunctionToolCallback.builder("currentWeather", svc::getWeather).inputType(WeatherRequest.class) 构建。
  • 想让模型 “先说出理由再调工具”,用 AugmentedToolCallbackProvider 给 Schema 追加一个 innerThought 字段,模型填、你的消费者接,工具本身收到的参数不变。
  • 方法的参数和返回类型别用 Optional、CompletableFuture、Mono/Flux、Function,这些在工具调用链路里不被支持;函数式工具用 POJO 做入参,不要直接塞基本类型或 List/Map。
  • MCP 工具走 @McpTool / @McpToolParam,和本地 @Tool 共用 ToolCallback 接口,可以混着传,但重名冲突框架不管,本地和远程同名要自己改名或用过滤器挡掉。

六、路线 C:LangChain4j 的两层抽象

LangChain4j 把选择权交给你:底层能精确控制 Schema 和执行器,高层用接口把一切都藏起来。

层次组成典型用法
低层ChatModel + ToolSpecification + ToolExecutor工具定义来自数据库或配置中心,运行时装配
高层AI Services + @Tool / @P声明一个接口,方法上打注解,框架自动转 ToolSpecification

低层写法是 “一个定义配一个执行器”:ToolSpecification.builder().name(...).description(...).parameters(JsonObjectSchema...) 配 ToolExecutor,再包成 AiServiceTool 传给 AiServices;不想手写执行器就用 DefaultToolExecutor 反射调用 Java 方法。高层写法更省事:

interface Assistant { String chat(String msg); }

class OrderTools {

@Tool("按订单号查询订单状态")

String query(@P("订单号") String orderNo) { ... }

}

Assistant a = AiServices.builder(Assistant.class)

.chatModel(model).tools(new OrderTools()).build();

它的差异化能力集中在 “按次装配” 上:

  • ToolProvider 每次调用都会被问一次 “这次要给哪些工具”,可以按 userMessage 内容判断,只有用户提到 “预订” 才挂订单工具,工具集再大也不会把上下文撑爆。
  • InvocationParameters 和 @ToolMemoryId 能把用户 ID、会话 ID 这类隐藏参数送进工具,模型看不见。
  • 一个 AI Service 可以直接当另一个 AI Service 的工具,RouterAgent 挂三个专家服务做分流就是官方示例;代价是多一次模型往返,且子服务读不到父服务的聊天记忆。
  • 工具多到一定程度,配 toolSearchStrategy(new SimpleToolSearchStrategy()),配合 toolProvider 还能把 MCP 工具一起纳入检索。

选型上不用纠结:已经在 Spring Boot 里、想要自动配置和可观测性,选 Spring AI;要做复杂编排、多智能体、工具按租户动态装配,LangChain4j 更顺手。两者都支持流式、结构化输出和多模态,文档质量 Spring AI 略好,模型覆盖都很广。

七、多厂商差异:别把 OpenAI 的写法直接搬过去

同一个 tools 字段,各家脾气不同:

厂商值得注意的差异
OpenAI工具调用需走 Responses API(新模型已不支持 Chat Completions 调工具);strict 与并行调用不能同时开
DeepSeekstrict 属 Beta,要把 base_url 指到 /beta 才生效;parallel_tool_calls 参数会被忽略,并行始终开启;思考模式下必须把 reasoning_content 原样回传,漏了直接 400
通义千问(DashScope)工具名最长 64 字符;description 必填;用 tools 时 result_format 必须设为 message;tool_choice=required 在 Qwen 系列不支持
Gemini(LangChain4j 接入)用 toolConfig 控制,ANY 模式可指定允许调用的函数名列表,支持并行调用

最常踩的坑是思考模式的思维链回传。DeepSeek 的要求很明确:只要请求带了 tools,后续每一轮都要把上一轮的 reasoning_content 完整带回,包括那些没有发生工具调用的轮次。Java 里如果用 record 或 DTO 承接响应,很容易只映射 content 和 tool_calls,把 reasoning_content 丢掉,表现就是第二轮莫名 400。

八、上线前的验收清单

检查项通过标准
Schema 合法性每个工具单独跑一遍 strict 子集校验,根节点是 object、全字段 required、additionalProperties:false
参数健壮性传入非法 JSON、缺字段、多字段、超长字符串,工具都不应执行
循环终止构造一个永远要求调工具的模型响应,验证轮次上限能兜住
并行调用一次返回 3 个工具调用时,结果按 id 正确配对,不串行阻塞
权限普通租户无法触发管理员专属工具
成本统计单次会话的工具轮次与 token 消耗,设预算告警
可观测每次调用能查到函数名、入参、出参、耗时、调用 id

验收通过之后,把工具当成对外 API 来治理:版本化、限流、灰度、下线公告。模型只是这些 API 的第一个调用者,不是唯一调用者。

【相关 FAQ】

问题 1:Java 项目做 Function Calling,一定要引入 Spring AI 或 LangChain4j 吗?

回答 1:不必。Function Calling 的协议层只是 HTTP 加 JSON,用官方 Java SDK 或 HttpClient 配 Jackson 就能跑通完整循环,适合工具少、要多厂商适配的服务;引入 Spring AI 或 LangChain4j 的价值在于托管循环、自动生成 Schema、统一本地工具与 MCP 工具。

问题 2:Java 实现 Function Calling 时,模型返回的 arguments 为什么必须校验?

回答 2:arguments 是模型生成的 JSON 字符串,官方文档明确说它不保证合法、也可能带上 Schema 里没定义的字段;Java 端要先反序列化再用 Bean Validation 校验,校验不过就返回结构化错误让模型重试,绝不能直接把参数拼进 SQL 或下游请求。

问题 3:Spring AI 的 @Tool 和 LangChain4j 的 @Tool 有什么区别,Java 项目怎么选?

回答 3:两者都是把 Java 方法转成 Function Calling 的工具定义,差别在循环和装配方式:Spring AI 由 ToolCallingAdvisor 托管循环、工具必须注册成 ToolCallback Bean,适合 Spring Boot 体系;LangChain4j 有 ToolSpecification 低层 API 和 ToolProvider 按次装配,适合工具动态变化和多智能体编排。

问题 4:开了 strict 模式,Java 端拿到的 Function Calling 参数就一定符合 Schema 吗?

回答 4:参数结构会严格贴合你提交的 JSON Schema,但前提是 Schema 本身落在厂商支持的子集内,否则请求会直接报错;另外 strict 只保证形状,不保证业务语义正确,像金额范围、订单号是否存在,仍然要在 Java 工具里做二次校验。

问题 5:Java 接入 Function Calling 后工具越来越多,上下文放不下怎么办?

回答 5:两条路。一是按会话动态收窄工具集,LangChain4j 用 ToolProvider 按用户消息判断该挂哪些工具,Spring AI 用 McpToolFilter 挡掉不该暴露的远程工具;二是上工具检索,只把与当前问题最相关的少数工具塞进请求,LangChain4j 的 SimpleToolSearchStrategy 就是干这个的。

【参考资料】

  • [2026-11-10](DashScope API 参考:tools 字段与 tool_choice 取值)阿里云百炼
  • [2026-11-05](How to implement tool calling by using Function Calling)阿里云帮助中心
  • [2026-10-31](Responses API 参考:function_call、call_id 与 tools 定义)DeepSeek API 文档
  • [2026-10-26](Tool Calling:@Tool、MethodToolCallback 与 ToolCallingAdvisor)Spring AI Reference
  • [2026-10-21](Tool Calls:strict 模式与 JSON Schema 支持范围)DeepSeek API 文档
  • [2026-10-19](工具调用:ToolContext、直接返回与工具定义方式)Spring AI 中文参考
  • [2026-10-01](OpenWOP Spec v1 Tier 1 Structured-Output Compatibility Subset)GitHub
  • [2026-09-14](工具(函数调用):ToolSpecification、AI Services 与 ToolProvider)LangChain4j 中文文档
  • [2026-08-25](How to use structured outputs with Azure OpenAI in Microsoft Foundry Models)Microsoft Learn
  • [2026-08-11](ツール(関数呼び出し):ツール検索と ToolProvider)LangChain4j 日本語ドキュメント
  • [2026-06-15](Tool Calling in Spring AI 2.0: A Composable, Agentic Architecture)Spring Blog
  • [2026-04-24](Open Responses Specification:tool_choice 与 allowed_tools)Open Responses
  • [2026-03-24](Function Calling in the OpenAI API)OpenAI Help Center

【免责声明】

本文为技术原理与工程实践整理,接口字段、参数限制与版本行为均可能随各家官方文档更新而变化,落地前请以对应厂商最新文档为准。文中代码为示意性片段,省略了异常处理、鉴权、日志与并发控制,直接用于生产环境前请自行补全并充分测试。本文不构成任何投资建议、产品选型结论或对特定厂商的推荐,据此操作的风险由使用者自行承担。