Spring AI Extractor 提取器(结构化输出)

Spring 官方的叫法是结构化输出,不过我更喜欢沿用 LangChain 的叫法“提取器(Extractor)”。

官方文档(Structured Output Converter)

https://docs.spring.io/spring-ai/reference/1.0/api/structured-output-converter.html

package net.heimeng.web.test;
​
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.heimeng.common.ai.extractor.ExtractorUtils;
import net.heimeng.web.Agent4jWebApplication;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
​
import java.util.List;
​
@Slf4j
@SpringBootTest(classes = Agent4jWebApplication.class)
@DisplayName("AI 单元测试")
public class AiUnitTest {
​
    @Autowired
    private ChatModel model;
​
    @DisplayName("AI 配置项测试")
    @Test
    void propertiesTest() {
        // Given
        ChatClient client = ChatClient.builder(model).build();
​
        // Ensure the client is able to use
        log.debug(client.prompt().user("你好").call().content());
​
        // Result: 你好👋!我是人工智能助手智谱清言,可以叫我小智🤖,很高兴见到你,欢迎问我任何问题。
    }
​
    @DisplayName("Extractor 提取器测试")
    @Test
    void extractorTest() {
        // Given
        String textAboutPersons = "蔡徐坤(KUN),1998年8月2日出生于浙江省温州市," +
                "户籍湖南省吉首市,中国内地男歌手、演员、原创音乐制作人、MV导演。" +
                "朱明春的长子朱立科浙农大毕业三年后,针对温州风俗——家逢喜事分红蛋的习惯,研发出了一鸣利市红蛋," +
                "深受广大市民的喜爱,成为温州市民办喜事的必需品。之后,朱立科倡导的新型奶吧," +
                "改变了部分温州人糯米饭加紫菜汤的早餐习惯。该模式一经推出大受温州人欢迎。" +
                "2002年在市区横渎开出第一家一鸣真鲜奶吧直营店,如今,一鸣真鲜奶吧直营店、加盟连锁店在温州地区已发展至近400家。";
​
        String textAboutActors = "Generate the filmography of 5 movies for Tom Hanks and Bill Murray.";
​
        List<Person> persons = ExtractorUtils.extract(textAboutPersons, new ParameterizedTypeReference<>() {});
        log.debug(String.valueOf(persons));
        Assertions.assertNotNull(persons);
​
        Person person = ExtractorUtils.extract(textAboutPersons, Person.class);
        log.debug(person.toString());
        Assertions.assertNotNull(person);
​
        record ActorFilms(String actor, List<String> movies) {}
        List<ActorFilms> actorFilms = ExtractorUtils.extract(textAboutActors, new ParameterizedTypeReference<>() {});
        log.debug(actorFilms.toString());
        Assertions.assertNotNull(actorFilms);
    }
​
    @Data
    private static class Person {
        // 私有属性
        private String name;
        private int age;
        private String address;
        private List<String> occupations;
    }
}
package net.heimeng.common.ai.extractor;
​
import net.heimeng.common.ai.model.WithDescription;
import net.heimeng.common.core.util.SpringUtils;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.core.ParameterizedTypeReference;
​
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
​
/**
 * 提取器工具类
 *
 * @author InwardFlow
 */
public class ExtractorUtils {
​
    private static final ChatClient CLIENT = SpringUtils.getBean(ChatClient.class);
    public static <T extends WithDescription> T extractWithDescription(String text, Class<T> clazz) {
        try {
            T instance = clazz.getDeclaredConstructor().newInstance();
            return CLIENT.prompt()
                    .user(u -> u.text("""
                                    Extract information from:
                                     {text}
                                     ---
                                     Here is the description:
                                     {description}
                                """)
                            .params(Map.of("text", text, "description", instance.getDescription())))
                    .call()
                    .entity(new ParameterizedTypeReference<>() {});
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException("Unable to create an instance of " + clazz.getName(), e);
        }
    }
​
    public static <T> T extract(String text, Class<T> clazz) {
        String className = clazz.getSimpleName();
        return CLIENT.prompt()
                .user(u -> u.text("""
                                    Extract information about {className} from {text}
                                """)
                        .params(Map.of("className", className, "text", text)))
                .call()
                .entity(clazz);
    }
​
    public static <T> T extract(String text, ParameterizedTypeReference<T> typeReference) {
        return CLIENT.prompt()
                .user(u -> u.text("Extract information from: " + text))
                .call()
                .entity(typeReference);
    }
​
}

本文标题:《Spring AI Extractor 提取器(结构化输出)》作者:Scar
原文链接:https://cxk.me/post/102.html
特别注明外均为原创,转载请注明。

分享到微信

扫描二维码

可在微信查看或分享至朋友圈。

相关文章

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。