用Spring AI Alibaba 开发AI大模型应用系列(3)——格式化输出
为啥要结构化输出
因为大模型输出的都是字符串,但是大模型应用很多时候是需要程序能够读懂(协议或者字段),所以我们就需要拿到大模型的反馈时,进行格式化输出。
比如我在扣子平台上做一个自动文章搬运的智能体,先是要获取文章,其次是要改写,最后呢要生成几张图片。
这里说一下格式化输出,就是在将文章丢个大模型进行改写时,要求大模型按我的格式输出,例如提示词这样写:基于整个文章的脉络,总结出4个插图的绘画提示词。
这样的话,每次文章改写后,都只会出现4个插图的绘画提示词。然后再下一个节点的时候,取出提示词去绘画,最后插入到文章当中。
如果大家有兴趣的话,回复一个“感兴趣”,我单独安排一篇文章,好好说说这个智能体。
LangChain中的定义
为啥要说LangChain,毕竟人家年长一些,值得借鉴。 文档地址:
https://python.langchain.com/docs/concepts/output_parsers/
有字符串,json,dict,List[str],pydantic.BaseModel,Enum,datetime.datetime,Dict[str, str]
说明一下几个常用的:
CSV解析器
:CommaSeparatedListOutputParser,模型的输出以逗号分隔,以列表形式返回输出
日期时间解析器:DatetimeOutputParser,可用于将 LLM 输出解析为日期时间格式 JSON解析器:
JsonOutputParser,确保输出符合特定JSON对象格式。
XML解析器:XMLOutputParser,允许以流行的XML格式从LLM获取结果
例如json解析器:
Spring AI中的定义
中文版对照:
说明:在 LLM 调用之前,转换器会将期望的输出格式(output format instruction)附加到 prompt 中,为模型提供生成所需输出结构的明确指导,这些指令充当蓝图,塑造模型的响应以符合指定的格式。
Spring AI API
关注三个:
BeanOutputConverter:看起来是java类,其实是返回的json字符串,然后再反序列化成java类的。
MapOutputConverter:它也是返回的json字符串,然后将JSON 负载转换为 java.util.Map<String, Object> 实例。
ListOutputConverter:该实现指导 AI 模型生成逗号分隔的格式化输出(csv格式),最终转换器将模型文本输出转换为 java.util.List。
实质就是大模型返回json或者csv格式等特定格式的字符串,可自行格式化成需要的格式或者对象,API也提供了几个方便的转换器。
代码实例
前面2个例子好像没有把依赖放进来,依赖是这样的:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 项目根元素,定义Maven项目的基本信息 -->
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- Maven POM文件的版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目的组织标识,通常是公司或组织的域名反写 -->
<groupId>com.zmgd.chat</groupId>
<!-- 项目的唯一标识符,通常是项目名称 -->
<artifactId>ollama-ds-chat</artifactId>
<!-- 项目的版本号,SNAPSHOT表示开发版本 -->
<version>1.0-SNAPSHOT</version>
<!-- 项目属性配置,用于定义项目中使用的各种版本号和编码等 -->
<properties>
<!-- 项目修订版本号 -->
<revision>1.0.0</revision>
<!-- 项目源代码的编码格式 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 项目报告输出时的编码格式 -->
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 项目使用的Java版本 -->
<java.version>17</java.version>
<!-- Maven编译时使用的Java版本 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- Spring AI框架的版本号 -->
<spring-ai.version>1.0.0-M6</spring-ai.version>
<!-- Spring AI Alibaba的版本号 -->
<spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
<!-- Spring Boot框架的版本号 -->
<spring-boot.version>3.4.0</spring-boot.version>
<!-- Maven插件版本配置 -->
<!-- Maven部署插件版本 -->
<maven-deploy-plugin.version>3.1.1</maven-deploy-plugin.version>
<!-- Maven扁平化插件版本 -->
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<!-- Maven编译插件版本 -->
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
</properties>
<!-- 项目依赖配置 -->
<dependencies>
<!-- Spring Boot Web启动器,提供Web应用开发所需的基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Spring AI Ollama启动器,提供与Ollama AI模型交互的功能 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
</dependencies>
<!-- 项目构建配置 -->
<build>
<plugins>
<!-- Maven部署插件配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<!-- 跳过部署阶段 -->
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<!-- Maven仓库配置 -->
<repositories>
<!-- Spring里程碑仓库,用于下载Spring相关的里程碑版本依赖 -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<!-- 禁用快照版本 -->
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
/**
* map格式化输出,思考下,能用流式输出吗?
* @param query
* @return
*/
@GetMapping("/chatMap")
public Map<String, Object> chatMap(@RequestParam(value = "query", defaultValue = "请为我描述下宇宙的特性") String query) {
//创建一个用户消息,这个是文本块,用三个引号包裹,去掉think标签尝试了多次,不行,呵呵呵~~
String promptUserSpec = """
format: key为描述的东西,value为对应的值
outputExample: {format},请直接返回答案,不要包含<think>标签。
""";
String format = mapConverter.getFormat(); //获取map转换器的格式,getFormat()返回的是一个字符串可以点进去看看,就是一个提示词,呵呵呵~~
logger.info("map format: {}",format);
String result = ollamaiChatClient.prompt(query)//prompt返回一个ChatClientRequestSpec对象,
.user(u -> u.text(promptUserSpec)//设置用户消息
.param("format", format))//设置参数
.call().content();//获取结果
//用正则表达式去掉<think>标签以及标签中的内容
result = result.replaceAll("(?s)<think>.*?</think>", "").replace("```json", "").replace("```", "");
logger.info("result: {}", result);
assert result != null;
Map<String, Object> convert = null;
try {
//使用map转换器转换结果
convert = mapConverter.convert(result);
logger.info("反序列成功,convert: {}", convert);
} catch (Exception e) {
logger.error("反序列化失败",e);
}
return convert;
}
稍微的打个样,有这个功能即可。 输出结果:
小尾巴,别嫌弃! 》》 作为一名有着10多年开发经验的Java全栈工程师,一直做牛马,现在从最小的个人开发者做起,学习AI,分享AI,用AI重塑行业产品,虽然收入锐减,但总是暂时的,共勉。
如果你有这样的困惑?私我下,也许是一个领先别人半年的机会
- “AI这么火,但我根本不知道从哪里开始…”
- “听说大模型很强,可我做的业务能用上吗?”
- “团队没懂AI的人,想用AI,但怕走错方向、浪费时间。”
- “豆包 会用,但总觉得用得很浅,没变现价值。”