JSON格式的优势
在应用开发过程中,其实我们使用的最多的结构为json结构,其有非常明显的优势;
- 轻量级:格式紧凑,比XML等其他数据交换格式更轻便,因此传输速度更快,占用带宽更少。
- 易于解析:基于文本,在各种编程语言中非常流行,都可以轻松解析和生成JSON数据。
- 平台无关性:语言无关性,可以在不同的系统和编程语言之间无缝交换数据。
- 支持复杂数据结构:表示复杂的对象和数组结构,这使得它非常适合表示层次化数据。
- 易于人类阅读:键值对格式,易于理解和阅读。
- 自描述性:有意义的键,这有助于理解数据结构的目的。
所以在AI应用开发,希望AI大模型可以返回Josn结构的数据,方便与现有系统的集成,同时也方便结果的解析。
大模型如何返回Json
提示词方案
在早期大模型并不支持json结构数据返回,到目前为止也不是所有的大模型都支持的。前期只能通过prompt(提示词)来实现。
prompt:查询某个导演最受欢迎的电影,包括:电影名称、电影的描述、电影的发行时间、演员列表,其中演员信息包括:姓名、年龄、参演过的最受欢迎的电影。
通过prompt提示词,我们希望大模型能够返回如下的json结构的数据。
json
{
"name":xxxx,
"description": xxxx,
"publishDate": xxxx,
"performers": [
{
"name":xxx,
"age":xxx,
"films": [xxx,xxx,xxx]
}
]
}
通过提示词让大模型返回json结构数据不够稳定,可能会出现json结构错误、缺失一些符号的情况,所以这种情况基本上不推荐,除非选择的大模型不支持Json mode方式。如果支持该方式,那么毋庸置疑选择json mode方式。
Json mode 方案
Json mode的方式使用上也非常简单,直接在请求时开启即可,比如;
-
对于OpenAI
javaOpenAiChatModel.builder() ... .responseFormat("json_object") .build();
-
对于Azure OpenAI
javaAzureOpenAiChatModel.builder() ... .responseFormat(new ChatCompletionsJsonResponseFormat()) .build();
-
对于 Vertex AI Gemini
javaVertexAiGeminiChatModel.builder() ... .responseMimeType("application/json") .build();
-
对于 Mistral AI
javaMistralAiChatModel.builder() ... .responseFormat(MistralAiResponseFormatType.JSON_OBJECT) .build();
-
对于Ollama
javaOllamaChatModel.builder() ... .format("json") .build();
该方式依然需要写好提示词,然后开启json mode。在可靠性上该方式没有任何问题,但是在返回性能会比普通差一些,这个很好理解,因为要处理json相关内容。
Json Schema 方案
在Json mode的方式,依然需要我们使用自然语言进行描述json的返回。但是当json的结构或者校验逻辑足够复杂时,自然语言描述显得有些力不从心了。
Json Schema不仅能约定Json的结构,还能约定数据类型、文本规则等,也是大模型支持比较好的方式。对Spring AI 结构化输出源码分析时,其就是使用的Json Schema方案。 然后我们在分析getFormat()方法
java
// 根据返回值的类型,转换为json schema,使用jackson和jsonschema-generator配合完成
private void generateSchema() {
JacksonModule jacksonModule = new JacksonModule();
SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(DRAFT_2020_12, PLAIN_JSON)
.with(jacksonModule);
SchemaGeneratorConfig config = configBuilder.build();
SchemaGenerator generator = new SchemaGenerator(config);
JsonNode jsonNode = generator.generateSchema(this.typeRef.getType());
ObjectWriter objectWriter = new ObjectMapper().writer(new DefaultPrettyPrinter()
.withObjectIndenter(new DefaultIndenter().withLinefeed(System.lineSeparator())));
try {
this.jsonSchema = objectWriter.writeValueAsString(jsonNode);
}
catch (JsonProcessingException e) {
logger.error("Could not pretty print json schema for jsonNode: " + jsonNode);
throw new RuntimeException("Could not pretty print json schema for " + this.typeRef, e);
}
}
// 将提示词 + json schema拼接一起
public String getFormat() {
String template = """
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.
Remove the ```json markdown from the output.
Here is the JSON Schema instance your output must adhere to:
```%s```
""";
return String.format(template, this.jsonSchema);
}
将获取到Json Schema 拼装在提示后,让大模型按照Json Schema返回。这就是 Spring AI框架的实现方式。
一个Json Schema结构如下;
json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"publishDate": {
"type": "string"
},
"performers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"age": {
"type": "string"
},
"films": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
每个字段可以加上 description 加强大模型对Json的理解。使用起来好是好,但是内容不够紧凑,含有一些对大模型无意义的内容,容易导致token超窗口大小等。但是对于Java开发者来说算是比较好的选择了。
prompt:请返回xxx导演最受欢迎的电影,请严格按照 + Json Schema 返回
TS DSL 方案
可以使用TypeScript 语法来约束DSL,AI的理解能力和生成效果都非常稳定。TypeScript描述的DSL如下;
TypeScript
export interface Film {
name: string; // "电影名字"
description: string; // "请用50字以内概括电影的内容"
publisDate: date; // "电影发行时间"
performers: {
name: string;
sex: 男 | 女 ;
}[];
}
从ts定义的DSL 与 Json Schema对比,很明显 TypeScript 定义的体积更小。
prompt:请返回xxx导演最受欢迎的电影,请严格按照 + TypeScript DSL 返回
Json格式返回适应场景
我们可以分场景进行讨论。
- 对于非Web交互式的,比如完成函数调用,属于同步调用,非常适合使用json格式。
- 对于Wen交互式的,需要流式返回的,一部分一部分返回,导致json结构被破坏无法完成展示,无法使用json格式。
- json 格式过大,比如{}, : 无用字符,需要较高的调用成本,对成本要求的 不适合用json格式。
那么我们选择什么格式的数据返回呢?
思考方向就是,既有结构,还的体积小。通过分析yml结构数据满足我们的需求,不仅拥有更小的体积,同时支持流式解析。
总结
一般推荐使用:json mode + prompt + typescript 方式。因为Json mode是大模型支持的,是最可靠的。 如果有特殊需求,比如流式输出:yml + prompt + typescript 方式。
在思考技术方案,思维要发散,多种技术结合,才能有创新!!!!