LangChain4j 工具调用别一上来就全量暴露:ToolProvider 才是工程化入口 很多 Java 开发者第一次接入 LangChain4j Tool Calling 时最容易写出这样的代码把订单查询、库存查询、退款申请、用户画像、优惠券、工单查询等方法都标上Tool然后一次性塞给模型。Demo 里这没问题。真实项目里这会很快变成隐患提示词变长、工具选择变慢、模型误调用概率升高更麻烦的是权限边界会被“工具列表”悄悄绕开。这篇只讲一个问题在 LangChain4j 项目里什么时候不该使用静态工具列表为什么更应该用ToolProvider做动态工具选择。工具不是越多越智能模型调用工具时并不是“看见工具就一定会正确使用工具”。它需要根据用户问题、工具名称、工具描述、参数 Schema 来判断是否调用、调用哪个、如何填参数。工具越多至少带来四类工程问题。问题真实表现Token 成本上升每次请求都要把工具描述和参数结构发给模型选择干扰变大相似工具太多模型可能选错权限变复杂不同用户、租户、角色不应看到同一批工具维护成本上升工具描述一改影响所有对话场景这和 Java 后端里的接口设计很像。我们不会把所有 Service 方法都暴露成一个公共 Controller也不会让所有用户都看到后台管理接口。AI 工具调用也是一样工具是模型可以触达业务系统的入口入口越靠近业务就越需要按场景收敛。Tool适合稳定的小工具ToolProvider适合真实业务LangChain4j 支持用Tool注解把 Java 方法暴露给模型这很适合计算器、时间转换、简单查询这类稳定工具。例如class TimeTools { Tool(Get current server time) String currentTime() { return java.time.LocalDateTime.now().toString(); } }但如果工具列表依赖用户身份、租户、套餐、当前页面、业务意图就不适合只靠静态Tool。这时应该考虑ToolProvider。它的价值不是“换一种写法注册工具”而是允许你在每次请求时动态决定这次对话到底给模型哪些工具。一个典型判断可以是普通用户只能看到只读查询工具客服角色可以看到工单处理工具财务角色可以看到账单查询工具退款、改价、删除这类高风险动作默认不直接开放用户问题明显和订单无关时不提供订单工具这样做的核心目标不是“限制模型”而是减少模型需要判断的空间。一个更接近项目的写法下面示例演示思路根据用户上下文和问题内容动态提供订单查询工具。代码重点是结构不建议直接复制到生产环境具体类名和方法签名可能随 LangChain4j 版本变化实际项目以官方文档为准。Bean ToolProvider customerToolProvider(OrderToolService orderToolService) { return request - { UserContext user UserContextHolder.current(); String message request.userMessage(); ToolProviderResult.Builder result ToolProviderResult.builder(); if (user.hasPermission(order:read) looksLikeOrderQuestion(message)) { ToolSpecification queryOrder ToolSpecification.builder() .name(query_order_status) .description(Query order status by order id for the current user) .parameters(JsonObjectSchema.builder() .addStringProperty(orderId, Order id provided by user) .required(orderId) .build()) .build(); result.add(queryOrder, (toolExecutionRequest, memoryId) - orderToolService.queryOrderStatus( toolExecutionRequest.arguments(), user ) ); } return result.build(); }; } private boolean looksLikeOrderQuestion(String message) { return message.contains(订单) || message.contains(物流) || message.contains(发货) || message.contains(退款进度); }再配合 AI Service 使用AiService public interface CustomerAssistant { SystemMessage( You are a customer service assistant. Use tools only when the users question requires business data. Do not guess order status or payment status. ) String chat(MemoryId String userId, UserMessage String message); }这里有一个关键点权限判断不能只写在提示词里。提示词可以提醒模型“不要越权”但真正的权限控制必须在 Java 代码里完成。也就是说ToolProvider决定“模型能看到什么工具”业务 Service 决定“工具执行时能访问什么数据”。两层都要有。工具描述要像接口契约不要像产品文案很多工具调用不稳定不是模型能力不行而是工具描述写得太随意。坏例子Query order info这个描述太宽。模型不知道它能不能查退款、能不能查物流、能不能查别人的订单。更好的描述应该包含边界Query the shipping and payment status of an order that belongs to the current authenticated user. Do not use this tool for refund application or invoice questions.工具参数也一样。不要把参数都设计成一个query字符串然后让模型自由发挥。能结构化就结构化例如orderId、skuId、startDate、endDate、status。这会让工具调用更稳定也更方便后端做校验、审计和测试。从 Java 工程角度看工具 Schema 就是给模型看的接口文档。接口文档越清楚调用越可靠接口边界越模糊线上问题越难排查。动态工具选择还能控制成本Tool Calling 的成本不只在工具执行也在模型上下文。如果一次请求带上 30 个工具每个工具又有较长描述和复杂参数 Schema那么即使模型最后一个工具都不调用这些内容也会进入请求上下文。所以第一版可以先做简单规则问订单只给订单查询工具问库存只给库存工具问售后只给售后工具问闲聊不给业务工具涉及写操作先让模型生成建议不直接执行动作当工具数量继续增长可以再考虑 LangChain4j 文档中提到的 Tool Search 思路把工具描述向量化按用户问题检索出少量候选工具再交给模型选择。这个方向更适合工具规模较大的 Agent 平台但第一版没必要过度设计。写操作工具要比读操作更克制在企业系统里查询类工具和动作类工具应该分开治理。查询订单状态最坏情况通常是返回错误或空数据但取消订单、创建退款、修改地址、发送优惠券都会改变业务状态。我的建议是读操作可以先接入但必须做用户和租户过滤写操作默认不要直接执行先做确认流程金额、库存、权限相关操作必须二次校验工具执行结果要记录 requestId、userId、toolName、arguments、result工具异常不要把数据库错误、内部接口地址直接返回给模型LangChain4j 提供的是工具调用框架真正的工程质量仍然来自后端系统自己的权限、事务、幂等和审计设计。如果把工具调用理解成“模型帮我调用几个 Java 方法”项目很容易停留在 Demo。更合理的理解是LangChain4j 把大模型接入了 Java 应用的调用链而ToolProvider让我们有机会在模型触达业务系统之前先做一次工程化筛选。工具少一点、准一点、边界清楚一点往往比一次性暴露几十个工具更接近生产可用。