解锁更多精彩,关注公众号:闲人张
本文主要介绍了spring ai的工具调用和结构化输出功能,通过具体的示例来体验整个调用及实现过程,同时对一些使用方法进行总结说明。
前言
目前调用AI的框架语言主要以Python为主,虽然也有一些Java语言实现的,但在使用和功能上仍有一些差距。而Spring做为Java的半壁江山,Spring AI自然也需要体验一下。 一些Java的调用框架:
- langchain4j
- semantic-kernel (依赖了azure-sdk-for-java)
- azure-sdk-for-java
通过官方的架构交互,可以看到主要分为Prompt、ChatClient、ChatResponse
下面来看看Spring AI是如何实现工具调用和结构化输出的。
引入pom
<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进行调用。
添加模型配置
在yml中添加模型的配置
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一个根据日期获取课程的方法:
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包装器来设置函数名称和描述。
调用
我们定义一个接口来调用自定义函数:
@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。感兴趣的可以去查看源码以及打开日志来查看具体的调用过程。
运行后可以看到如下输出:
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来支持返回结果的格式化输出,并提供了以下的实现:
下面通过构建一个BeanOutputParser来看下调用的过程:
首先定义一个返回对象
@Data
public class ResultResponse {
@JsonPropertyDescription("日期")
String date;
List<String> course;
}
可以使用@JsonPropertyDescription对参数进行描述
调用
借用之前定义的函数,我们让接口返回为定义的对象
@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:
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" : "日期"
}
}
}
```
查看最终结果可以看到完全是按照我们定义的结构化数据来输出的:
{
"date": "星期二",
"course": [
"c++",
"js"
]
}
以上就是本篇的全部内容,后续将会对spring ai 其他的功能做更深度的体验,欢迎关注!
解锁更多精彩,关注公众号:闲人张