一、简介使用Tool注解标注一个工具方法使用ToolParam来标记工具方法参数。二、代码DTO/** * 航班订单信息 */DataBuilderpublicclassFlightOrder{/** id */privateLongid;/** 订单号 */privateStringorderNo;/** 航班号 */privateStringflightNumber;/** 乘客姓名 */privateStringname;/** 出发城市 */privateStringfromCity;/** 到达城市 */privateStringtoCity;}ChatEventVODataBuilderNoArgsConstructorAllArgsConstructorpublicclassChatEventVO{/** * 文本内容 */privateObjecteventData;/** * 事件类型1001-数据事件1002-停止事件1003-参数事件 */privateinteventType;}ChatEventTypeEnum/** * 聊天消息事件类型 */GetterpublicenumChatEventTypeEnum{DATA(1001,数据事件),STOP(1002,停止事件),PARAM(1003,参数事件);privatefinalintvalue;privatefinalStringdesc;ChatEventTypeEnum(intvalue,Stringdesc){this.valuevalue;this.descdesc;}OverridepublicStringtoString(){returnthis.name();}}ToolResultHolder/** * 工具结果保持器用来存储tools中得到的结果请求id 作为key value为键值对数据 */publicclassToolResultHolder{privatestaticfinalMapString,MapString,ObjectHANDLER_MAPnewConcurrentHashMap();privateToolResultHolder(){}publicstaticvoidput(Stringkey,Stringfield,Objectresult){HANDLER_MAP.computeIfAbsent(key,k-newHashMap()).put(field,result);}publicstaticMapString,Objectget(Stringkey){returnkeynull?null:HANDLER_MAP.get(key);}publicstaticObjectget(Stringkey,Stringfield){returnOptional.ofNullable(HANDLER_MAP.get(key)).map(map-map.get(field)).orElse(null);}publicstaticvoidremove(Stringkey){HANDLER_MAP.remove(key);}}Configuration一般情况下配置类中只做一些公共的默认配置如日志拦截器消息记忆、消息优化等像一些工具等都是放在Controller中单独配置。ConfigurationpublicclassSpringAiConfig{BeanpublicChatClientchatClient(DeepSeekChatModeldeepSeekChatModel,ChatMemorychatMemory,MyToolsmyTools){returnChatClient.builder(deepSeekChatModel).defaultSystem(你是AI票务助手回答用户的问题。).defaultTools(myTools).build();}}定义工具方法航班订单信息查询 和 取消订单ToolParam中的description可以写更多的和该参数相关的提示词。ToolContext 通过toolContext用户大模型向工具传参ComponentpublicclassMyTools{Tool(description航班订单信息查询)publicFlightOrderqueryFlightOrder(ToolParam(description航班号)StringflightNumber,ToolParam(description姓名)Stringname,ToolContexttoolContext){System.out.println(航班订单信息查询:flightNumber name);varflightOrderFlightOrder.builder().id(1L).orderNo(F1234).flightNumber(flightNumber).name(name).fromCity(北京).toCity(上海).build();varuserIdtoolContext.getContext().get(Constant.USER_ID),varrequestIdConvert.toStr(toolContext.getContext().get(Constant.REQUEST_ID));varfieldStrUtil.format({}_{},StrUtil.lowerFirst(FlightOrder.class.getSimpleName()),flightOrder.getId());// 将接口的返回值放入到Holder中便于在大模型中获取到封装成json返回给前端ToolResultHolder.put(requestId,field,courseInfo);returnflightOrder;}Tool(description取消订单)publicStringcancelOrder(ToolParam(description订单号)StringorderNo,ToolParam(description出发城市)StringfromCity,ToolParam(description到达城市)StringtoCity){// 参数校验System.out.println(取消订单:orderNo fromCity→toCity);return订单取消成功;}}RestControllerpublicclassSpringAIController{AutowiredprivateChatClientchatClient;// 输出结束的标记privatestaticfinalChatEventVOSTOP_EVENTChatEventVO.builder().eventType(ChatEventTypeEnum.STOP.getValue()).build();GetMapping(value/chat,producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringchat(RequestParam(message)Stringmessage){// 生成请求idvarrequestIdIdUtil.fastSimpleUUID();// 获取用户idvaruserIdUserContext.getUser();FluxStringcontentchatClient.prompt()// 通过工具上下文传递参数.toolContext(Map.of(Constant.REQUEST_ID,requestId,Constant.USER_ID,userId)).user(message).stream().content().contactWith(.concatWith(Flux.defer(()-{// 通过请求id获取到参数列表如果不为空就将其追加到返回结果中vartoolResultMapToolResultHolder.get(requestId);if(CollUtil.isNotEmpty(toolResultMap)){ToolResultHolder.remove(requestId);// 清除参数列表// 响应给前端的参数数据varparamChatEventChatEventVO.builder().eventData(toolResultMap).eventType(ChatEventTypeEnum.PARAM.getValue()).build();// 自定义一个结束标记前端解析到就不会再请求表示本轮会话全部结束returnFlux.just(paramChatEvent,STOP_EVENT);}});returncontent;}}航班订单信息查询 和 取消订单 都正常调用了。三、最佳实践对于工具方法大模型推测的参数值不能完全信任一定要严格校验。工具方法一定要注意性能尽可能快的执行能缓存就缓存。工具方法不要返回不确定Optional值。通过SpringSecurity对用户进行必要的权限校验或者维护角色role和tool的关系在方法内部通过用户id查询是否有权限没有权限报错。每个tool都会解析成json如果tool过多会导致占用上下文过大也增加模型的选择难度。现在是每次对话都会将工具作为上下文当有的对话使用不到该工具时那么就会对上下文带来负面影响好的方法使用使用Skill按需加载工具。