Skip to content

Spring AI Function Call 实践 ​

用户问「SKU-1001 还有多少库存?」应用可以把查询库存的方法交给模型选择,再用实际查询结果回答。本文用一条内存库存记录跑通这条链路。函数调用的消息原理见 Function Call 介绍。

1. 准备项目 ​

使用 JDK 17 或以上版本、Maven 3.6.3 或以上版本,以及支持工具调用的模型。示例固定使用 Spring Boot 3.5.15 和 Spring AI 1.1.8。新建 Maven 项目,写入 pom.xml。Spring AI 1.1.8 发布记录

xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.15</version>
        <relativePath/>
    </parent>
    <groupId>example</groupId>
    <artifactId>stock-tool-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.1.8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

写入 src/main/resources/application.yml。示例是命令行程序,通过环境变量 OPENAI_API_KEY 读取模型服务密钥。

yaml
spring:
  main:
    web-application-type: none
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini

2. 声明工具并注册到请求 ​

创建 src/main/java/example/stock/StockApplication.java。示例把 SKU-1001 的可用库存设为 37 件。商品不存在时返回 found: false,让模型能区分「查无此商品」和「库存为零」。

java
package example.stock;

import java.util.Map;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class StockApplication {
    public static void main(String[] args) {
        SpringApplication.run(StockApplication.class, args).close();
    }

    @Bean
    CommandLineRunner askStock(ChatClient.Builder builder, StockTools stockTools) {
        return args -> {
            String sku = args.length == 0 ? "SKU-1001" : args[0];
            String answer = builder.build()
                    .prompt()
                    .user("请用库存工具查询 %s 的可用库存。如果查无此商品,请直接说明。".formatted(sku))
                    .tools(stockTools)
                    .call()
                    .content();
            System.out.println(answer);
        };
    }
}

@Component
class StockTools {
    private final Map<String, Integer> stock = Map.of("SKU-1001", 37);

    @Tool(name = "get_stock", description = "根据商品 SKU 查询可用库存,单位为件")
    public StockResult getStock(@ToolParam(description = "商品 SKU,例如 SKU-1001") String sku) {
        if (sku == null || !sku.matches("SKU-[0-9]{4}")) {
            throw new IllegalArgumentException("SKU 格式应为 SKU- 加四位数字");
        }
        System.out.printf("tool=get_stock sku=%s%n", sku);
        Integer available = stock.get(sku);
        return new StockResult(sku, available != null, available);
    }

    record StockResult(String sku, boolean found, Integer available) {
    }
}

@Tool 指定模型可以看到的工具名和用途,@ToolParam 补充参数说明,Spring AI 据此生成工具的输入 Schema。.tools(stockTools) 把该实例的工具加入这一次请求。若在构建客户端时用 defaultTools(),工具会作为后续请求的默认选项。单次请求显式提供工具时,该列表会覆盖默认工具。Spring AI Tool Calling

3. 运行并确认工具真的执行了 ​

在终端设置 OPENAI_API_KEY,然后从项目根目录运行:

bash
mvn spring-boot:run

如果模型发起工具调用,控制台会先出现 Java 方法的日志,随后打印模型的回答。模型的措辞可能不同。

text
tool=get_stock sku=SKU-1001
SKU-1001 的可用库存为 37 件。

查看未找到的分支时,可以用以下命令把 SKU 改成 SKU-9999。

bash
mvn spring-boot:run "-Dspring-boot.run.arguments=SKU-9999"

在这次请求中,应用和模型的往返过程是:

  1. Spring AI 把用户问题和 get_stock 的定义一并发给模型。
  2. 模型选中工具,返回工具名和参数,例如 {"sku":"SKU-1001"}。
  3. Spring AI 查找回调、转换参数并执行 getStock(),把返回值序列化为工具结果。
  4. Spring AI 把结果交回模型,模型据此生成自然语言回答。Spring AI Tool Calling

4. 排查常见问题 ​

  • 有回答,控制台没有 tool=get_stock。 核对模型是否支持工具调用、当前请求是否传了 .tools(stockTools),再检查提示是否明确要求查询库存。测试时以执行日志或断点为准。
  • 模型传入了无效 SKU。 检查工具参数说明与方法校验是否一致。示例要求 SKU- 加四位数字,符合格式但查不到库存的商品会返回 found: false。
  • 接入真实库存服务。 在工具或业务服务内保留查询权限、参数校验和错误处理。模型生成的参数需要按外部输入处理。

本例把 Java 方法注册到当前应用。需要让其他 Agent 也能连接并发现同一个工具时,可以继续阅读 Spring AI MCP 实践。