
前言前一篇我们解决了一个关键问题让模型稳定返回后端能解析的结构化结果。但 Agent 到这里还只能“理解”。例如用户说帮我查订单 10001 的物流。模型可以识别出{intent:QUERY_LOGISTICS,orderNo:10001}但模型本身并不知道订单 10001 的真实物流信息。要获取真实数据就需要 Tool Calling。可以把 Tool Calling 理解为模型负责决定调用什么工具 后端负责安全地执行工具 工具结果再交给模型组织成最终回答这一篇我们会学习Tool Calling 是什么工具和普通接口有什么区别如何设计工具名称、参数和返回值为什么模型不能直接操作数据库Java 中如何封装工具如何做权限校验、参数校验和审计日志如何避免危险工具调用一、Tool Calling 是什么Tool Calling 又常被称为Function Calling 工具调用 函数调用它的核心流程是用户提出问题 | 模型理解意图 | 模型选择工具并生成参数 | 后端校验工具和参数 | 后端执行工具 | 工具返回真实结果 | 模型根据结果生成最终回答例如用户查订单 10001 的物流。模型不会直接回答物流状态而是请求{name:queryOrderLogistics,arguments:{orderNo:10001}}后端调用订单服务拿到真实数据{orderNo:10001,status:IN_TRANSIT,latestMessage:包裹已到达分拨中心}模型再回答订单 10001 当前正在运输中最新物流状态是包裹已到达分拨中心。二、Tool Calling 和普通 API 调用的区别普通 API 调用通常由前端或后端代码明确决定。前端调用 /order/10001/logistics而 Tool Calling 中调用哪个工具由模型在允许范围内决定。用户自然语言 | 模型判断 | 选择 queryOrderLogistics | 后端执行工具但这里必须强调模型有调用建议权 后端拥有最终执行权模型不能绕过权限校验 参数校验 业务规则 审计日志 风控规则三、工具不是“把所有接口暴露给模型”一个常见误区是项目有 200 个接口就给 Agent 暴露 200 个工具。这通常不是好做法。工具过多会导致模型更难选择正确工具工具描述占用更多 Token权限管理更复杂调试成本更高工具误调用概率增加高风险操作更难控制更推荐从少量、高价值、职责清晰的工具开始。例如订单助手第一版只提供getCurrentUser queryOrderDetail queryOrderLogistics queryRefundStatus createAfterSaleTicket不要一开始就开放deleteOrder updateOrderStatus executeSql runShellCommand四、一个好工具应该具备什么特点一个好工具通常具备下面几个特征。1. 名称明确不推荐doOrder handleData queryInfo推荐queryOrderDetail queryOrderLogistics createAfterSaleTicket工具名称应该让模型和开发者都能看懂。2. 职责单一不推荐manageOrder因为它可能包含查询、修改、删除、退款等太多行为。推荐拆开queryOrderDetail queryOrderLogistics applyRefund cancelOrder这样权限控制和审计更清晰。3. 参数清晰不推荐{data:10001}推荐{orderNo:10001}参数名称要表达业务含义。4. 返回结果稳定不推荐直接返回数据库所有字段。推荐返回 Agent 真正需要的信息{orderNo:10001,orderStatus:PAID,deliveryStatus:WAITING_SHIPMENT,latestLogisticsMessage:null}返回字段越稳定模型越容易正确理解。5. 具备安全边界工具必须校验当前用户是谁 能否访问目标数据 参数是否合法 当前操作是否允许 是否需要二次确认五、工具定义示例下面是queryOrderLogistics的工具说明。{name:queryOrderLogistics,description:查询当前登录用户指定订单的物流信息。仅当用户提供明确订单号时调用。,parameters:{type:object,required:[orderNo],properties:{orderNo:{type:string,description:订单号只允许数字和字母长度不超过 32 位。}},additionalProperties:false}}文字说明工具定义通常包含工具名称工具描述参数 Schema必填字段字段说明是否允许额外参数模型会根据工具描述判断是否调用。所以描述要准确。例如仅当用户提供明确订单号时调用。可以减少模型在订单号缺失时盲目调用工具。六、为什么模型不能直接连接数据库有些人会想既然模型能生成 SQL那让模型直接查数据库不就行了不建议这样做。原因包括模型可能生成错误 SQL模型可能生成危险 SQL模型可能查询超出权限的数据模型可能返回敏感字段难以做稳定审计SQL 结构变化后容易失效容易受到提示词注入影响正确方式应该是模型 - 受控工具 - Service - Mapper - 数据库而不是模型 - 任意 SQL - 数据库七、Java 中定义工具参数对象先定义工具参数。packagecom.example.agent.tool.order;importjakarta.validation.constraints.NotBlank;importjakarta.validation.constraints.Pattern;publicrecordQueryOrderLogisticsArgs(NotBlank(message订单号不能为空)Pattern(regexp^[A-Za-z0-9]{1,32}$,message订单号格式不正确)StringorderNo){}文字说明工具参数和普通 Controller DTO 一样也应该做参数校验。模型生成的参数不是可信参数。例如模型可能返回orderNo 查询全部订单或者orderNo 10001; delete from orders即使模型不会真的执行 SQL参数校验仍然是必须的。八、定义统一工具接口可以给 Agent 工具定义一个统一接口。packagecom.example.agent.tool;publicinterfaceAgentToolA,R{Stringname();Rexecute(ToolContextcontext,Aarguments);}再定义工具调用上下文。packagecom.example.agent.tool;publicrecordToolContext(LonguserId,Stringusername,StringtraceId){}文字说明ToolContext用于保存当前请求上下文。例如当前登录用户 ID 用户名 traceId 租户 ID 用户角色工具执行时不能依赖模型传入“当前用户是谁”。当前用户必须来自 JWT、拦截器或 ThreadLocal。九、实现查询订单物流工具下面以订单物流工具为例。packagecom.example.agent.tool.order;importcom.example.agent.tool.AgentTool;importcom.example.agent.tool.ToolContext;importcom.example.entity.Order;importcom.example.exception.BusinessException;importcom.example.service.OrderService;importlombok.RequiredArgsConstructor;importorg.springframework.stereotype.Component;ComponentRequiredArgsConstructorpublicclassQueryOrderLogisticsToolimplementsAgentToolQueryOrderLogisticsArgs,OrderLogisticsResult{privatefinalOrderServiceorderService;OverridepublicStringname(){returnqueryOrderLogistics;}OverridepublicOrderLogisticsResultexecute(ToolContextcontext,QueryOrderLogisticsArgsarguments){OrderorderorderService.queryUserOrder(context.userId(),arguments.orderNo());if(ordernull){thrownewBusinessException(404,未查询到当前用户的订单);}returnnewOrderLogisticsResult(order.getOrderNo(),order.getDeliveryStatus(),order.getLogisticsCompany(),order.getLatestLogisticsMessage());}}返回对象packagecom.example.agent.tool.order;publicrecordOrderLogisticsResult(StringorderNo,StringdeliveryStatus,StringlogisticsCompany,StringlatestLogisticsMessage){}文字说明这个工具没有直接调用 Mapper而是调用OrderService原因是订单归属、订单状态、权限校验等业务规则应该放在 Service 层。工具本身只是 Agent 和业务服务之间的一层适配。十、Service 层继续负责业务和权限packagecom.example.service;importcom.example.entity.Order;publicinterfaceOrderService{OrderqueryUserOrder(LonguserId,StringorderNo);}packagecom.example.service.impl;importcom.example.entity.Order;importcom.example.mapper.OrderMapper;importcom.example.service.OrderService;importlombok.RequiredArgsConstructor;importorg.springframework.stereotype.Service;ServiceRequiredArgsConstructorpublicclassOrderServiceImplimplementsOrderService{privatefinalOrderMapperorderMapper;OverridepublicOrderqueryUserOrder(LonguserId,StringorderNo){returnorderMapper.selectByOrderNoAndUserId(orderNo,userId);}}Mapper 接口packagecom.example.mapper;importcom.example.entity.Order;importorg.apache.ibatis.annotations.Mapper;importorg.apache.ibatis.annotations.Param;MapperpublicinterfaceOrderMapper{OrderselectByOrderNoAndUserId(Param(orderNo)StringorderNo,Param(userId)LonguserId);}Mapper XMLselectidselectByOrderNoAndUserIdresultTypecom.example.entity.Orderselect order_no, delivery_status, logistics_company, latest_logistics_message from orders where order_no #{orderNo} and user_id #{userId}/select文字说明这里的关键是anduser_id#{userId}即使模型生成了一个真实存在的订单号也只能查询当前登录用户自己的订单。这才是 Agent 工具正确的安全边界。十一、工具注册中心当 Agent 有多个工具时可以通过注册中心统一管理。packagecom.example.agent.tool;importorg.springframework.stereotype.Component;importjava.util.List;importjava.util.Map;importjava.util.function.Function;importjava.util.stream.Collectors;ComponentpublicclassToolRegistry{privatefinalMapString,AgentTool?,?toolMap;publicToolRegistry(ListAgentTool?,?tools){this.toolMaptools.stream().collect(Collectors.toMap(AgentTool::name,Function.identity()));}publicAgentTool?,?getTool(StringtoolName){returntoolMap.get(toolName);}}文字说明Spring 会自动注入所有实现了AgentTool接口的工具。例如QueryOrderLogisticsTool QueryOrderDetailTool CreateAfterSaleTicketTool注册中心再通过工具名称找到对应实现。这个设计比在代码中写大量if(queryOrderLogistics.equals(toolName)){...}更清晰也方便后续扩展。十二、工具执行器需要做什么工具执行器不应该拿到模型请求后直接执行。建议至少做这些检查1. 工具是否存在 2. 工具是否属于当前 Agent 的允许列表 3. 工具参数是否可以解析 4. 参数是否通过 Validation 5. 当前用户是否有权限 6. 是否超过调用次数限制 7. 是否属于高风险操作 8. 是否需要人工确认 9. 是否记录审计日志可以把执行流程理解成模型请求工具 | 检查工具白名单 | 解析参数 | 参数校验 | 权限校验 | 执行业务服务 | 脱敏工具结果 | 记录日志 | 返回模型十三、工具错误结果不要直接抛给模型工具失败时不建议把底层异常直接给模型。例如数据库异常SQLSyntaxErrorException: Unknown column ...不应该作为模型上下文返回。可以转换成受控结果{success:false,errorCode:ORDER_QUERY_FAILED,message:订单查询暂时失败请稍后重试。}文字说明这样做有几个好处不暴露数据库和系统实现模型更容易理解错误类型用户看到的提示更友好后端日志仍然可以记录完整异常方便 Agent 决定是否重试或降级十四、高风险工具必须增加确认下面这些操作属于高风险工具删除数据 取消订单 申请退款 创建支付 发送外部消息 修改权限 发布内容 执行部署命令不建议模型一次决定后直接执行。更推荐的流程用户提出请求 | 模型生成操作草稿 | 后端展示确认信息 | 用户明确确认 | 后端再次校验权限和状态 | 执行工具 | 记录审计日志例如用户说帮我取消订单 10001。Agent 应该先回答订单 10001 当前状态为已付款取消后将发起退款流程。是否确认取消用户确认后才允许执行取消工具。十五、工具结果也要控制长度工具返回数据不要过大。例如查询订单列表时不应该把 500 条订单全部交给模型。更推荐工具层分页 只返回必要字段 限制最大记录数 摘要化返回 敏感字段脱敏例如{total:128,items:[{orderNo:10001,status:PAID,amount:99.00}]}如果用户需要更多内容Agent 可以继续追问或分页查询。十六、工具调用需要限制次数Agent 可能出现这种情况调用工具 | 结果不满意 | 再次调用相同工具 | 继续调用 | 进入循环所以需要限制单次请求最大工具调用次数 单个工具最大调用次数 单个工具超时时间 总请求超时时间 最大 Token 消耗例如单次 Agent 请求最多调用 5 次工具 同一个工具最多调用 2 次 单个工具超时 5 秒文字说明这些限制不是为了让 Agent “变笨”而是防止成本失控响应时间过长工具被重复调用外部系统被大量请求复杂异常导致死循环十七、常见问题1. 模型调用了不存在的工具后端不能猜测模型想调用什么。应该返回受控错误{success:false,errorCode:TOOL_NOT_FOUND,message:当前不支持该工具调用。}同时记录日志用于优化工具描述和 Prompt。2. 工具参数不合法怎么办例如模型返回{orderNo:查询全部用户订单}应该通过 DTO 校验拒绝。不能因为模型“看起来是在完成任务”就绕过参数校验。3. 工具调用成功但模型总结错了怎么办工具结果是真实的但模型可能仍然理解错。例如工具返回status WAITING_SHIPMENT模型却说订单已经发货。这种问题需要清晰的工具返回字段Prompt 约束“只能基于工具结果回答”关键状态使用模板化后端文案记录结果用于评估高风险业务避免让模型自由解释4. 能不能让模型执行 Shell 命令默认不建议。如果确实需要操作服务器也应该限定命令白名单 限制执行目录 限制参数范围 隔离执行环境 记录审计日志 增加人工确认 禁止 root 权限不要给模型任意 Shell 权限。十八、实际开发建议工具要少而精优先暴露高价值、职责单一的能力。模型只负责选择工具真正执行必须经过后端校验。工具内部优先调用 Service不要让模型或工具直接操作数据库。工具参数必须使用 DTO、Validation 和业务校验。查询类工具与修改类工具要区分风险等级。高风险操作必须增加用户确认和审计日志。工具结果要脱敏、限长、结构化不要直接返回底层异常和全量数据。为 Agent 设置工具调用次数、超时和成本限制。十九、总结这一篇我们完成了 Agent 从“理解用户问题”到“调用真实业务能力”的关键一步。Tool Calling 的本质是模型负责决策 工具负责执行 后端负责安全和业务规则整个流程可以总结为用户输入 | 模型选择工具 | 后端校验工具和参数 | Service 执行业务 | Mapper 查询或修改数据 | 工具结果返回模型 | 模型生成最终回复学完这一篇后Agent 已经不只是聊天而是可以在受控范围内查询订单、调用接口、创建任务和连接业务系统。下一篇我们继续学习 ReAct 模式理解 Agent 如何在“思考、行动、观察”之间完成多步骤任务。