Spring AI,初体验

解锁更多精彩,关注公众号:闲人张

本文主要介绍了spring ai的工具调用和结构化输出功能,通过具体的示例来体验整个调用及实现过程,同时对一些使用方法进行总结说明。

前言

目前调用AI的框架语言主要以Python为主,虽然也有一些Java语言实现的,但在使用和功能上仍有一些差距。而Spring做为Java的半壁江山,Spring AI自然也需要体验一下。 一些Java的调用框架:

  • langchain4j
  • semantic-kernel (依赖了azure-sdk-for-java)
  • azure-sdk-for-java

通过官方的架构交互,可以看到主要分为Prompt、ChatClient、ChatResponse

image.png

下面来看看Spring AI是如何实现工具调用和结构化输出的。

引入pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>0.8.1-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
    </dependency>

这里使用azure的api,目前官方的版本是0.8.1,引入后我们可以看到内部也是引用了azure的sdk,目前依赖的版本是1.0.0-beta.7,感兴趣的同学其实也可以直接使用azure的sdk进行调用。

image.png

添加模型配置

在yml中添加模型的配置

1
2
3
4
5
6
7
8
9
spring:
  ai:
  azure:
    openai:
      api-key: 331321**********************
      endpoint: https://xxxx.openai.azure.com/
      chat:
        options:
          deployment-name: gpt-4-32k

经过以上配置,我们就可以直接使用spring-ai提供的api进行调用了。

工具调用

Spring AI 提供了灵活且用户友好的注册和调用自定义函数的方式。通常,自定义函数需要提供函数 name、 description和函数调用的参数。具体实现只需定义一个Bean,返回java.util.Function。

注册

我们通过一个具体的示例看下,mock一个根据日期获取课程的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MockCourseService implements Function<MockCourseService.Request, MockCourseService.Response> {
    @Override
    public Response apply(Request request) {
        String[] courseList = null;
        switch (request.dayOfWeek) {
            case "星期一" -> courseList = new String[]{"java", "python"};
            case "星期二" -> courseList = new String[]{"c++", "js"};
            default -> courseList = new String[]{"php"};
        }
        return new Response(courseList);
    }

    @JsonClassDescription("获取课程入参")
    public record Request(@JsonProperty(required = true, value = "dayOfWeek") @JsonPropertyDescription("星期几,如:星期一")String dayOfWeek) {}
    public record Response(String[] courseList) {}

    @Configuration
    static class Config {
        //方式一
        @Bean
        @Description("根据星期几查询对应的课程")
        public Function<MockCourseService.Request, MockCourseService.Response> courseFunctionInfo1() {
            return new MockCourseService();
        }
        //方式二
        @Bean
        public FunctionCallback courseFunctionInfo2() {
            return FunctionCallbackWrapper.builder(new MockCourseService())
                    .withName("getCourseByDate")
                    .withDescription("根据星期几查询对应的课程")
                    .build();
        }

    }
}

可以看到需要实现Function接口,并且提供了两种方式来注册自定义函数。

  • 方式一的函数名称为courseFunctionInfo1,并且采用了@JsonClassDescription@JsonProperty@JsonPropertyDescription@Description等注解对函数及其参数进行描述说明
  • 方式二的函数名称为getCourseByDate,通过spring ai 提供的FunctionCallback接口注册自定义函数,并使用FunctionCallbackWrapper包装器来设置函数名称和描述。

调用

我们定义一个接口来调用自定义函数:

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/function")
public ChatResponse function() {
    SystemMessage systemMessage = new SystemMessage("你是一个智能助手,请调用工具回答问题。");
    UserMessage userMessage = new UserMessage("今天星期二有哪些课?");
    Set<String> functions = Set.of("getCourseByDate","addressFunction");
    Prompt prompt = new Prompt(List.of(systemMessage,userMessage), AzureOpenAiChatOptions.builder().withFunctions(functions).build());
    System.out.println(prompt.toString());
    ChatResponse response = chatClient.call(prompt);
    System.out.println(response.getResult().getOutput().getContent());
    return response;
}

可以看到基本上都是一个思路,通过构建Prompt对象,然后调用chatClient.call(prompt)即可。通过debug,发现实际是将tool转换OPEN AI function的标准,然后调用openai的api。感兴趣的可以去查看源码以及打开日志来查看具体的调用过程。

image.png

运行后可以看到如下输出:

1
2
3
4
5
Prompt{messages=[SystemMessage{content='你是一个智能助手,请调用工具回答问题。', properties={}, messageType=SYSTEM}, UserMessage{content='今天星期二有哪些课?', properties={}, messageType=USER}], modelOptions=org.springframework.ai.azure.openai.AzureOpenAiChatOptions@11e70d12}

今天星期二你有以下课程:
1. c++
2. js

结构化输出

spring ai 提供了OutputParser来支持返回结果的格式化输出,并提供了以下的实现:

image.png

下面通过构建一个BeanOutputParser来看下调用的过程:

首先定义一个返回对象

1
2
3
4
5
6
@Data
public class ResultResponse {
    @JsonPropertyDescription("日期")
    String date;
    List<String> course;
}

可以使用@JsonPropertyDescription对参数进行描述

调用

借用之前定义的函数,我们让接口返回为定义的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 @GetMapping("/outputParse")
 public ResultResponse outputParse() {
     var outputParser = new BeanOutputParser<>(ResultResponse.class);
     String format = outputParser.getFormat();
     System.out.println("format: " + format);
     String systemPrompt = "你是一个智能助手,请调用工具回答问题。 {format}";
     PromptTemplate promptTemplate = new PromptTemplate(systemPrompt, Map.of("format", format));
     Message message = promptTemplate.createMessage();
     UserMessage userMessage = new UserMessage("今天星期二有哪些课");
     Set<String> functions = Set.of("getCourseByDate","addressFunction");
     Prompt prompt = new Prompt(List.of(message,userMessage), AzureOpenAiChatOptions.builder().withFunctions(functions).build());
     ChatResponse response = chatClient.call(prompt);
     return outputParser.parse(response.getResult().getOutput().getContent());
 }

可以看到,我们只需要定义一个BeanOutputParser,然后调用其format方法,通过对其输出,我们可以看到OutputParser实际上是将定义的结构化数据转换为了一段Prompt:

1
2
3
4
Your response should be in JSON format.
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
Do not include markdown code blocks in your response.
Here is the JSON Schema instance your output must adhere to:
{
  "$schema" : "https://json-schema.org/draft/2020-12/schema",
  "type" : "object",
  "properties" : {
    "course" : {
      "type" : "array",
      "items" : {
        "type" : "string"
      }
    },
    "date" : {
      "type" : "string",
      "description" : "日期"
    }
  }
}
1
2


查看最终结果可以看到完全是按照我们定义的结构化数据来输出的:

1
2
3
4
5
6
7
{
"date": "星期二",
"course": [
"c++",
"js"
]
}

以上就是本篇的全部内容,后续将会对spring ai 其他的功能做更深度的体验,欢迎关注!

解锁更多精彩,关注公众号:闲人张

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%