找回密码
 立即注册
首页 业界区 安全 8. Spring AI tools/function-call

8. Spring AI tools/function-call

上官泰 4 天前
8. Spring AI  tools/function-call

@
目录

  • 8. Spring AI  tools/function-call

    • 链接多个模型协调工作实战 - 初代tools:



        • 背景:
        • 票务助手
        • 效果
        • 代码:


    • tools/function-call

      • 使用
      • 原理
      • 源码
      • tools注意事项:


  • 最后:

链接多个模型协调工作实战 - 初代tools:

背景:

大模型如果它无法和企业API互联那将毫无意义! 比如我们开发一个智能票务助手, 当用户需要退票, 基础大模型它肯定做不到, 因为票务信息都存在了我们系统中, 必须通过我们系统的业务方法才能进行退票。 那怎么能让大模型“调用”我们自己系统的业务方法呢? 今天叫大家通过结构化输入连接多个模型一起协同完成这个任务:
票务助手

1.png

效果

2.png

3.png

输入姓名和预定号:
4.png

5.png

普通对话:
6.png

代码:
  1. public class AiJob {
  2.      record Job(JobType jobType, Map<String,String> keyInfos) {
  3.     }
  4.     public enum JobType{
  5.         CANCEL,
  6.         QUERY,
  7.         OTHER,
  8.     }
  9. }
复制代码
  1. /**
  2. *
  3. */
  4. @Configuration
  5. public class AiConfig {
  6.     @Bean
  7.     public ChatClient planningChatClient(DashScopeChatModel chatModel,
  8.                                          DashScopeChatProperties options,
  9.                                          ChatMemory chatMemory) {
  10.         DashScopeChatOptions dashScopeChatOptions = DashScopeChatOptions.fromOptions(options.getOptions());
  11.         dashScopeChatOptions.setTemperature(0.7);
  12.             return  ChatClient.builder(chatModel)
  13.                     .defaultSystem("""
  14.                             # 票务助手任务拆分规则
  15.                             ## 1.要求
  16.                             ### 1.1 根据用户内容识别任务
  17.                            
  18.                             ## 2. 任务
  19.                             ### 2.1 JobType:退票(CANCEL) 要求用户提供姓名和预定号, 或者从对话中提取;
  20.                             ### 2.2 JobType:查票(QUERY) 要求用户提供预定号, 或者从对话中提取;
  21.                             ### 2.3 JobType:其他(OTHER)
  22.                             """)
  23.                     .defaultAdvisors(
  24.                             MessageChatMemoryAdvisor.builder(chatMemory).build()
  25.                     )
  26.                     .defaultOptions(dashScopeChatOptions)
  27.                     .build();
  28.     }
  29.     @Bean
  30.     public ChatClient botChatClient(DashScopeChatModel chatModel,
  31.                                     DashScopeChatProperties options,
  32.                                          ChatMemory chatMemory) {
  33.         DashScopeChatOptions dashScopeChatOptions = DashScopeChatOptions.fromOptions(options.getOptions());
  34.         dashScopeChatOptions.setTemperature(1.2);
  35.         return  ChatClient.builder(chatModel)
  36.                 .defaultSystem("""
  37.                            你是XS航空智能客服代理, 请以友好的语气服务用户。
  38.                             """)
  39.                 .defaultAdvisors(
  40.                         MessageChatMemoryAdvisor.builder(chatMemory).build()
  41.                 )
  42.                 .defaultOptions(dashScopeChatOptions)
  43.                 .build();
  44.     }
  45. }
复制代码
  1. @RestController
  2. public class MultiModelsController {
  3.     @Autowired
  4.     ChatClient planningChatClient;
  5.     @Autowired
  6.     ChatClient botChatClient;
  7.     @GetMapping(value = "/stream", produces = "text/stream;charset=UTF8")
  8.     Flux<String> stream(@RequestParam String message) {
  9.         // 创建一个用于接收多条消息的 Sink
  10.         Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
  11.         // 推送消息
  12.         sink.tryEmitNext("正在计划任务...<br/>");
  13.         new Thread(() -> {
  14.         AiJob.Job job = planningChatClient.prompt().user(message)
  15.                 .call().entity(AiJob.Job.class);
  16.         switch (job.jobType()){
  17.             case CANCEL ->{
  18.                 System.out.println(job);
  19.                 // todo.. 执行业务
  20.                 if(job.keyInfos().size()==0){
  21.                     sink.tryEmitNext("请输入姓名和订单号.");
  22.                 }
  23.                 else {
  24.                     sink.tryEmitNext("退票成功!");
  25.                 }
  26.             }
  27.             case QUERY -> {
  28.                 System.out.println(job);
  29.                 // todo.. 执行业务
  30.                 sink.tryEmitNext("查询预定信息:xxxx");
  31.             }
  32.             case OTHER -> {
  33.                 Flux<String> content = botChatClient.prompt().user(message).stream().content();
  34.                 content.doOnNext(sink::tryEmitNext) // 推送每条AI流内容
  35.                         .doOnComplete(() -> sink.tryEmitComplete())
  36.                         .subscribe();
  37.             }
  38.             default -> {
  39.                 System.out.println(job);
  40.                 sink.tryEmitNext("解析失败");
  41.             }
  42.         }
  43.         }).start();
  44.         return sink.asFlux();
  45.     }
  46. }
复制代码
tools/function-call

7.png

想做企业级智能应用开发, 你肯定会有需求要让大模型和你的企业 API 能够互连,
因为对于基础大模型来说, 他只具备通用信息,他的参数都是拿公网进行训练,并且有一定的时间延迟, 无法得知一些具体业务数据和实时数据, 这些数据往往被各软件系统存储在自己数据库中:
比如我问大模型:“中国有多少个叫徐庶的” 他肯定不知道, 我们就需要去调用政务系统的接口。
比如我现在开发一个智能票务助手, 我现在跟AI说需要退票, AI怎么做到呢? 就需要让AI调用我们自己系统的退票业务方法,进行操作数据库。
在之前我们可以通过链接多个模型的方式达到, 但是很麻烦, 那用tools, 可以轻松完成。
tool calling也可以直接叫tool(也称为function-call), 主要用于提供大模型不具备的信息和能力:

  • 信息检索:可用于从外部源(如数据库、Web 服务、文件系统或 Web 搜索引擎)检索信息。目标是增强模型的知识,使其能够回答无法回答的问题。例如,工具可用于检索给定位置的当前天气、检索最新的新闻文章或查询数据库以获取特定记录。 这也是一种检索增强方式。
  • 采取行动:例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。目标是自动执行原本需要人工干预或显式编程的任务。例如,可以使用工具为与聊天机器人交互的客户预订航班,在网页上填写表单等。
8.png
需要使用tools必须要先保证大模型支持。 比如ollama列出了支持tool的模型
9.png

使用

10.png


  • 声明 tools (大模型调用的方法工具)的类:
  1. @Service  // 注意要注入到 IOC容器当中
  2. class NameCountsTools {
  3.     // @Tool 注解表示,告诉大模型提供的方法类,可以被你大模型调用使用,标识是可以被大模型调用的方法工具
  4.     @Tool(description = "长沙有多少名字的数量")
  5.     String LocationNameCounts(
  6.         // @ToolParam()使用该上述 @Tool标识的方法,要那些参数才可以调用,大模型会自动从用户的历史对话当中提取
  7.         // 出需要的“名字”信息,然后作为参数,去调用该 @Tool()标识的方法工具,如果用户对话当中没有提供
  8.         // 大模型就会告知用户需要提供“名字”
  9.             @ToolParam(description = "名字,可以是英文名") // description = "名字,可以是英文名" 这个是用于让大模型识别,
  10.         // 从而正确的从用户的历史对话当中提取的,赋值上去。
  11.             String name) {
  12.         return "10个";
  13.     }
  14. }
复制代码

  • 将Tool类配置为bean(非必须)
  • @Tool 用户告诉大模型提供了什么工具
  • @ToolParam 用于告诉大模型你要用这个工具需要什么参数(非必须)
  • 将上面声明的 Tools 类 绑定到 ChatClient(对应的大模型当中去)
  1. @SpringBootTest
  2. public class ToolTest {
  3.     ChatClient chatClient;
  4.     @BeforeEach
  5.     public  void init(@Autowired
  6.                       DashScopeChatModel chatModel,
  7.                       @Autowired  // 因为 NameCountsTools Tools 工具类,已经被我们加入到了IOC容器了
  8.                       NameCountsTools nameCountsTools) {
  9.         chatClient = ChatClient.builder(chatModel)
  10.                 .defaultTools(nameCountsTools) // 给大模型附加上我们的 Tools 工具类
  11.                 .build();
  12.     }
  13.     @Test
  14.     public void testChatOptions() {
  15.         String content = chatClient.prompt()
  16.                 .user("长沙有多少个叫徐庶的/no_think")
  17.                 // .tools() 也可以单独绑定当前对话,绑定上 Tools 工具类
  18.                 .call()
  19.                 .content();
  20.         System.out.println(content);
  21.     }
  22. }
复制代码
11.png

原理

12.png


  • 当我们设置了defaultTools 相当于就告诉了大模型我提供了什么工具, 你需要用我的工具必须给我什么参数, 底层实际就是将这些信息封装了json提供给大模型
  • 当大模型识别到我们的对话需要用到工具, 就会响应需要调用tool
源码

13.png

tools注意事项:


  • 参数或者返回值不支持:
14.png

推荐: pojo record java基础类型 list map

  • Tools参数无法自动推算问题
问题:大模型无法将我们历史对话当中的信息,赋值转换到我们对应的 name 属性值当中。

  • 温度(即模型随机性)太低,AI可能缺失自由度变得比较拘谨(从一定程度可以解决, 但是不推荐)
  • 也可以通过描述 @ToolParam(description = "经度") 和@Tool(description)  的 description 的值 更加明确
  1. @Tool(description = "获取指定位置天气,根据位置自动推算经纬度")
  2.     public String getAirQuality(@ToolParam(description = "纬度") double latitude,
  3.                                 @ToolParam(description = "经度") double longitude) {
  4.         return "天晴";
  5.     }
复制代码

  • 大模型“强行适配”Tool参数的幻觉问题
问题:就是比如大模型将我们 的 “男,女”识别成了我们的姓名 name 赋值上了。

  • 加严参数描述与校验
  1. @Parameter(description = "真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)")
  2. String name
复制代码

  • 后端代码加强校验和兜底保护,比较稳,靠谱的方案。
  • 系统 Prompt 设定限制
  1. “严禁随意补全或猜测工具调用参数。
  2. 参数如缺失或语义不准,请不要补充或随意传递,请直接放弃本次工具调用。”
复制代码

  • 特别:高风险接口(如资金、风控等)tools方法加强人工确认,多走一步校验。

  • 工具暴露的接口名、方法名、参数名要可读、业务化


  • AI是“看”你的签名和注释来决定用不用工具的;
  • 尽量避免乱码、缩写等。

  • 方法参数数量不宜过多


  • 建议每个工具方法尽量少于5个参数,否则AI提示会变复杂、出错率高。
工具方法不适合做超耗时操作, 更长的耗时意味着用户延迟响应时间变长,
性能优化 能异步处理就异步处理、 查询数据 redis
6. 关于Tools的权限控制
可以利用SpringSecurity限制
  1. @Tool(description = "退票")
  2.     @PreAuthorize("hasRole('ADMIN')")
  3.     public String cancel(
  4.             // @ToolParam告诉大模型参数的描述
  5.       @ToolParam(description = "预定号,可以是纯数字") String ticketNumber,
  6.       @ToolParam(description = "真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)") String name
  7.            ) {
  8.         // 当前登录用户名
  9.         String username = SecurityContextHolder.getContext().getAuthentication().getName();
  10.         // 先查询 --->先校验
  11.         ticketService.cancel(ticketNumber, name);
  12.         return username+"退票成功!";
  13.     }
复制代码
将tools和权限资源一起存储, 然后动态设置tools
  1. .defaultToolCallbacks(toolService.getToolCallList(toolService))
复制代码
根据当前用户读取当前用户所属角色的所有tools
  1. public List<ToolCallback> getToolCallList(ToolService toolService) {
  2.        // 1 获取 Tools 处理的方法
  3.         Method method = ReflectionUtils.findMethod(ToolService.class, "cancel",String.class,String.class);
  4.        // 构建 Tool 定义信息 动态配置的方式 @Tool @ToolParam 都无效
  5.         ToolDefinition toolDefinition = ToolDefinition.builder()
  6.                 .name("cancel")
  7.                 .description("退票")  // 对应@Tool注解当中的 description
  8.                  // 对应@ToolParam() 注解
  9.                 .inputSchema("""
  10.                         {
  11.                           "type": "object",
  12.                           "properties": {
  13.                             "ticketNumber": {
  14.                               "type": "string",
  15.                               "description": "预定号,可以是纯数字"
  16.                             },
  17.                             "name": {
  18.                               "type": "string",
  19.                               "description": "真实人名"
  20.                             }
  21.                           },
  22.                           "required": ["ticketNumber", "name"]
  23.                         }
  24.                         """)
  25.                 .build();
  26. // 一个 ToolCallback 对应一个 tool
  27.         ToolCallback toolCallback = MethodToolCallback.builder()
  28.                 .toolDefinition(toolDefinition) // 将对应的 toolDefinition = @ToolParam 传入
  29.                 .toolMethod(method)  // method = @Tools 配置
  30.                 .toolObject(toolService) // 不能自己 new ,自己 new 的无法解析依赖注入
  31.                 .build();
  32.         return List.of(toolCallback);
  33.     }
复制代码

  • tools过多导致AI出现选择困难证
问题:
a. token上限
b. 选择困难证
tools的描述作用 保存 向量数据库。
实现方式:

  • 把所有的tools描述信息存入到向量数据库,做相似性检索。
  • 每次对话的时候根据当前对话信息检索到相似的tools(RAG)
  • 然后动态设置tools
最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
15.gif


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册