Spring AI 2.0:Tool
2026/8/17 20:17:20 网站建设 项目流程

一、简介

使用@Tool注解标注一个工具方法,使用@ToolParam来标记工具方法参数。

二、代码

DTO

/** * 航班订单信息 */@Data@BuilderpublicclassFlightOrder{/** id */privateLongid;/** 订单号 */privateStringorderNo;/** 航班号 */privateStringflightNumber;/** 乘客姓名 */privateStringname;/** 出发城市 */privateStringfromCity;/** 到达城市 */privateStringtoCity;}

ChatEventVO

@Data@Builder@NoArgsConstructor@AllArgsConstructorpublicclassChatEventVO{/** * 文本内容 */privateObjecteventData;/** * 事件类型,1001-数据事件,1002-停止事件,1003-参数事件 */privateinteventType;}

ChatEventTypeEnum

/** * 聊天消息事件类型 */@GetterpublicenumChatEventTypeEnum{DATA(1001,"数据事件"),STOP(1002,"停止事件"),PARAM(1003,"参数事件");privatefinalintvalue;privatefinalStringdesc;ChatEventTypeEnum(intvalue,Stringdesc){this.value=value;this.desc=desc;}@OverridepublicStringtoString(){returnthis.name();}}

ToolResultHolder

/** * 工具结果保持器,用来存储tools中得到的结果,请求id 作为key, value为键值对数据 */publicclassToolResultHolder{privatestaticfinalMap<String,Map<String,Object>>HANDLER_MAP=newConcurrentHashMap<>();privateToolResultHolder(){}publicstaticvoidput(Stringkey,Stringfield,Objectresult){HANDLER_MAP.computeIfAbsent(key,k->newHashMap<>()).put(field,result);}publicstaticMap<String,Object>get(Stringkey){returnkey==null?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);varflightOrder=FlightOrder.builder().id(1L).orderNo("F1234").flightNumber(flightNumber).name(name).fromCity("北京").toCity("上海").build();varuserId=toolContext.getContext().get(Constant.USER_ID),varrequestId=Convert.toStr(toolContext.getContext().get(Constant.REQUEST_ID));varfield=StrUtil.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_EVENT=ChatEventVO.builder().eventType(ChatEventTypeEnum.STOP.getValue()).build();@GetMapping(value="/chat",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<String>chat(@RequestParam("message")Stringmessage){// 生成请求idvarrequestId=IdUtil.fastSimpleUUID();// 获取用户idvaruserId=UserContext.getUser();Flux<String>content=chatClient.prompt()// 通过工具上下文传递参数.toolContext(Map.of(Constant.REQUEST_ID,requestId,Constant.USER_ID,userId)).user(message).stream().content().contactWith(.concatWith(Flux.defer(()->{// 通过请求id获取到参数列表,如果不为空,就将其追加到返回结果中vartoolResultMap=ToolResultHolder.get(requestId);if(CollUtil.isNotEmpty(toolResultMap)){ToolResultHolder.remove(requestId);// 清除参数列表// 响应给前端的参数数据varparamChatEvent=ChatEventVO.builder().eventData(toolResultMap).eventType(ChatEventTypeEnum.PARAM.getValue()).build();// 自定义一个结束标记,前端解析到就不会再请求,表示本轮会话全部结束returnFlux.just(paramChatEvent,STOP_EVENT);}});returncontent;}}

航班订单信息查询 和 取消订单 都正常调用了。

三、最佳实践

  1. 对于工具方法大模型推测的参数值不能完全信任,一定要严格校验。
  2. 工具方法一定要注意性能,尽可能快的执行(能缓存就缓存)。
  3. 工具方法不要返回不确定Optional值。
  4. 通过SpringSecurity对用户进行必要的权限校验或者维护角色role和tool的关系,在方法内部通过用户id查询是否有权限,没有权限报错。
  5. 每个tool都会解析成json,如果tool过多,会导致占用上下文过大,也增加模型的选择难度。

现在是每次对话都会将工具作为上下文,当有的对话使用不到该工具时那么就会对上下文带来负面影响,好的方法使用使用Skill,按需加载工具。

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

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

立即咨询