掘金 人工智能 09月29日
Spring AI 集成工具实现大模型企业级应用
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了如何利用Spring AI框架,通过“工具调用”(Tool Calling/Function Calling)机制,实现大模型与企业内部API的无缝对接。文章以一个智能票务助手为例,阐述了如何让大模型理解用户意图,并调用企业业务方法(如退票、查票)来处理具体业务场景。通过声明工具类、配置ChatClient并绑定工具,可以有效地扩展大模型的应用范围,使其能够访问私有数据和执行特定操作,从而构建更具实用性的企业级智能应用。文章还讨论了工具使用中的常见问题及解决方案,如参数推算、幻觉问题、权限控制和工具数量管理等。

🚀 **工具调用(Tool Calling)是连接大模型与企业API的关键**:基础大模型仅具备通用知识,无法访问企业内部的私有数据和业务逻辑。通过Tool Calling,大模型可以像调用函数一样调用企业的API,从而处理退票、查询信息等具体业务,实现真正意义上的企业级智能应用。

🔧 **Spring AI 简化了工具集成流程**:通过在Spring AI中声明带有`@Tool`和`@ToolParam`注解的Java类,可以方便地定义大模型可调用的工具及其参数。将这些工具类配置到`ChatClient`中,即可让大模型理解并使用这些工具,极大地降低了集成难度。

💡 **解决工具使用中的常见问题**:文章探讨了工具调用中可能出现的参数推算不准确、大模型“幻觉”问题、工具数量过多导致选择困难等挑战。提出了通过加严参数描述、后端校验、系统Prompt限制、RAG技术动态选择工具等方法来提升工具调用的准确性和鲁棒性。

🔒 **精细化权限控制与性能优化**:对于高风险的工具接口,可以通过Spring Security进行权限控制,确保只有授权用户才能调用。同时,建议工具方法参数不宜过多,并优先处理耗时操作的异步化和性能优化,以提升用户体验和系统效率。

8. Spring AI tools/function-call

@[toc]

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

背景:

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

票务助手

效果

输入姓名和预定号:

普通对话:

代码:
public class AiJob {     record Job(JobType jobType, Map<String,String> keyInfos) {    }    public enum JobType{        CANCEL,        QUERY,        OTHER,    }}
/** *  */@Configurationpublic class AiConfig {    @Bean    public ChatClient planningChatClient(DashScopeChatModel chatModel,                                         DashScopeChatProperties options,                                         ChatMemory chatMemory) {        DashScopeChatOptions dashScopeChatOptions = DashScopeChatOptions.fromOptions(options.getOptions());        dashScopeChatOptions.setTemperature(0.7);            return  ChatClient.builder(chatModel)                    .defaultSystem("""                            # 票务助手任务拆分规则                            ## 1.要求                            ### 1.1 根据用户内容识别任务                                                        ## 2. 任务                            ### 2.1 JobType:退票(CANCEL) 要求用户提供姓名和预定号, 或者从对话中提取;                            ### 2.2 JobType:查票(QUERY) 要求用户提供预定号, 或者从对话中提取;                            ### 2.3 JobType:其他(OTHER)                            """)                    .defaultAdvisors(                            MessageChatMemoryAdvisor.builder(chatMemory).build()                    )                    .defaultOptions(dashScopeChatOptions)                    .build();    }    @Bean    public ChatClient botChatClient(DashScopeChatModel chatModel,                                    DashScopeChatProperties options,                                         ChatMemory chatMemory) {        DashScopeChatOptions dashScopeChatOptions = DashScopeChatOptions.fromOptions(options.getOptions());        dashScopeChatOptions.setTemperature(1.2);        return  ChatClient.builder(chatModel)                .defaultSystem("""                           你是XS航空智能客服代理, 请以友好的语气服务用户。                            """)                .defaultAdvisors(                        MessageChatMemoryAdvisor.builder(chatMemory).build()                )                .defaultOptions(dashScopeChatOptions)                .build();    }}
@RestControllerpublic class MultiModelsController {    @Autowired    ChatClient planningChatClient;    @Autowired    ChatClient botChatClient;    @GetMapping(value = "/stream", produces = "text/stream;charset=UTF8")    Flux<String> stream(@RequestParam String message) {        // 创建一个用于接收多条消息的 Sink        Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();        // 推送消息        sink.tryEmitNext("正在计划任务...<br/>");        new Thread(() -> {        AiJob.Job job = planningChatClient.prompt().user(message)                .call().entity(AiJob.Job.class);        switch (job.jobType()){            case CANCEL ->{                System.out.println(job);                // todo.. 执行业务                if(job.keyInfos().size()==0){                    sink.tryEmitNext("请输入姓名和订单号.");                }                else {                    sink.tryEmitNext("退票成功!");                }            }            case QUERY -> {                System.out.println(job);                // todo.. 执行业务                sink.tryEmitNext("查询预定信息:xxxx");            }            case OTHER -> {                Flux<String> content = botChatClient.prompt().user(message).stream().content();                content.doOnNext(sink::tryEmitNext) // 推送每条AI流内容                        .doOnComplete(() -> sink.tryEmitComplete())                        .subscribe();            }            default -> {                System.out.println(job);                sink.tryEmitNext("解析失败");            }        }        }).start();        return sink.asFlux();    }}

tools/function-call

想做企业级智能应用开发, 你肯定会有需求要让大模型和你的企业 API 能够互连,

因为对于基础大模型来说, 他只具备通用信息,他的参数都是拿公网进行训练,并且有一定的时间延迟, 无法得知一些具体业务数据和实时数据, 这些数据往往被各软件系统存储在自己数据库中:

比如我问大模型:“中国有多少个叫徐庶的” 他肯定不知道, 我们就需要去调用政务系统的接口。

比如我现在开发一个智能票务助手, 我现在跟AI说需要退票, AI怎么做到呢? 就需要让AI调用我们自己系统的退票业务方法,进行操作数据库。

在之前我们可以通过链接多个模型的方式达到, 但是很麻烦, 那用tools, 可以轻松完成。

tool calling也可以直接叫tool(也称为function-call), 主要用于提供大模型不具备的信息和能力:

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

需要使用tools必须要先保证大模型支持。 比如ollama列出了支持tool的模型

使用

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

    将Tool类配置为bean(非必须)

    @Tool 用户告诉大模型提供了什么工具

    @ToolParam 用于告诉大模型你要用这个工具需要什么参数(非必须)

    将上面声明的 Tools 类 绑定到 ChatClient(对应的大模型当中去)

@SpringBootTestpublic class ToolTest {    ChatClient chatClient;    @BeforeEach    public  void init(@Autowired                      DashScopeChatModel chatModel,                      @Autowired  // 因为 NameCountsTools Tools 工具类,已经被我们加入到了IOC容器了                      NameCountsTools nameCountsTools) {        chatClient = ChatClient.builder(chatModel)                .defaultTools(nameCountsTools) // 给大模型附加上我们的 Tools 工具类                .build();    }    @Test    public void testChatOptions() {        String content = chatClient.prompt()                .user("长沙有多少个叫徐庶的/no_think")                // .tools() 也可以单独绑定当前对话,绑定上 Tools 工具类                .call()                .content();        System.out.println(content);    }}

原理

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

源码

tools注意事项:

    参数或者返回值不支持:

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

    Tools参数无法自动推算问题

问题:大模型无法将我们历史对话当中的信息,赋值转换到我们对应的 name 属性值当中。

@Tool(description = "获取指定位置天气,根据位置自动推算经纬度")    public String getAirQuality(@ToolParam(description = "纬度") double latitude,                                @ToolParam(description = "经度") double longitude) {        return "天晴";    }
    大模型“强行适配”Tool参数的幻觉问题

问题:就是比如大模型将我们 的 “男,女”识别成了我们的姓名 name 赋值上了。

@Parameter(description = "真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)")String name
“严禁随意补全或猜测工具调用参数。参数如缺失或语义不准,请不要补充或随意传递,请直接放弃本次工具调用。”
    工具暴露的接口名、方法名、参数名要可读、业务化
    方法参数数量不宜过多

工具方法不适合做超耗时操作, 更长的耗时意味着用户延迟响应时间变长,

性能优化 能异步处理就异步处理、 查询数据 redis

6. 关于Tools的权限控制可以利用SpringSecurity限制

@Tool(description = "退票")    @PreAuthorize("hasRole('ADMIN')")    public String cancel(            // @ToolParam告诉大模型参数的描述      @ToolParam(description = "预定号,可以是纯数字") String ticketNumber,      @ToolParam(description = "真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)") String name           ) {        // 当前登录用户名        String username = SecurityContextHolder.getContext().getAuthentication().getName();        // 先查询 --->先校验        ticketService.cancel(ticketNumber, name);        return username+"退票成功!";    }

将tools和权限资源一起存储, 然后动态设置tools

.defaultToolCallbacks(toolService.getToolCallList(toolService))

根据当前用户读取当前用户所属角色的所有tools

public List<ToolCallback> getToolCallList(ToolService toolService) {       // 1 获取 Tools 处理的方法        Method method = ReflectionUtils.findMethod(ToolService.class, "cancel",String.class,String.class);       // 构建 Tool 定义信息 动态配置的方式 @Tool @ToolParam 都无效        ToolDefinition toolDefinition = ToolDefinition.builder()                .name("cancel")                .description("退票")  // 对应@Tool注解当中的 description                 // 对应@ToolParam() 注解                .inputSchema("""                        {                          "type": "object",                          "properties": {                            "ticketNumber": {                              "type": "string",                              "description": "预定号,可以是纯数字"                            },                            "name": {                              "type": "string",                              "description": "真实人名"                            }                          },                          "required": ["ticketNumber", "name"]                        }                        """)                .build();// 一个 ToolCallback 对应一个 tool        ToolCallback toolCallback = MethodToolCallback.builder()                .toolDefinition(toolDefinition) // 将对应的 toolDefinition = @ToolParam 传入                .toolMethod(method)  // method = @Tools 配置                .toolObject(toolService) // 不能自己 new ,自己 new 的无法解析依赖注入                .build();        return List.of(toolCallback);    }
    tools过多导致AI出现选择困难证

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

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

最后:

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

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI Tool Calling Function Calling LLM 企业级应用 API集成 智能客服 大模型
相关文章