你是否曾经需要一个大型语言模型始终输出JSON格式,但又无法正确设置提示?你可以使用Vertex AI Gemini API的控制生成功能来帮助你始终输出JSON格式,现在Gemini 1.5 Flash版本(即Gemini 1.5 Flash版本)完全支持这项功能。特别是对于生产环境中的工作负载,这是你可能需要学习的最有用的功能之一!
将“response_mime_type”设置为“application/json”,以确保始终生成Gemini的JSON输出。
在这篇文章里,我会讲到:
- 什么是Gemini的可控生成?它解决了什么问题?
- 可控生成的例子
- 可控生成的使用场景:
- “设置好约束条件后,可以自由地进行后续的提示”
- 合成数据生成(例如用于游戏中的NPC角色)
- 结构化数据提取
- 用枚举来约束特定字段的值
- 轻松地将JSON转换为pandas DataFrame
- 最佳实践和限制
Vertex AI 受控生成文档:https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output (点击链接查看)
基于Gemini的控制生成是什么?控制生成的输出内容,这样就能确保模型的输出始终符合特定格式,从而收到格式一致的回复。
为什么可控生成是有用的?大型语言模型(LLM)因其生成非确定性响应而著称,这对于创意场景如头脑风暴或创意生成非常适用。但对于生产系统而言,你需要输出的一致性和可预测性,以便下游应用能够可靠地处理这些输出。
这到底是怎么工作的?这个功能是建立在我们最近开发的一项技术之上的,这项技术叫做受控解码,是由谷歌团队开发的。您可以在这篇论文中了解我们更多底层技术的细节。
这里是一个使用示例例如,如果你的下游应用程序需要 JSON,你可以利用受控生成来确保 Gemini 返回有效的 JSON 数据。
假设你有一个需要作为输入的下游应用,它需要以下模式,格式为 JSON:
{"recipe_name": 我的食谱名称}
不使用生成控制的示例
如果你之前不了解Gemini的可控生成,你可能会尝试提示工程。
例如,使用少量样本提示(few-shot prompting),但不使用受控生成。
# 使用少量示例提示输出JSON,不使用受控生成技术
from vertexai import generative_models
from vertexai.generative_models import GenerativeModel
model = GenerativeModel(
model_name="gemini-1.5-flash",
)
prompt = """列出几个曲奇的食谱。
输出必须是JSON格式。
示例:
[{"recipe_name": "燕麦饼干"}, {"recipe_name": "花生酱饼干"}]
[{"recipe_name": "巧克力碎片饼干"}, {"recipe_name": "蔓越莓饼干"}]
结果:
"""
response = model.generate_content(prompt)
print(response.text)
下面是输出结果:
```json
[
{"recipe_name": "巧克力豆饼干"},
{"recipe_name": "燕麦葡萄干饼"},
{"recipe_name": "花生酱饼干"},
{"recipe_name": "糖饼干"},
{"recipe_name": "姜饼球"},
{"recipe_name": "姜饼"},
{"recipe_name": "酥皮饼干"},
{"recipe_name": "夏威夷坚果饼干"},
{"recipe_name": "柠檬饼干"}
]
但是你的下游应用不能直接使用它——注意输出仍然被三重反引号(如```json 和 ```)包裹着,这意味着你仍然需要花时间写代码来处理字符串响应,去掉那些反引号。这样的后处理过程会让你感到沮丧!
此外,LLM可能仍然会**生成一些不期望出现的额外字段**,例如下面的情况,它可能会意外生成一些不需要的额外字段(如配料和步骤)。
```json
[
{
"recipe_name": "巧克力豆饼干",
"ingredients": [
{
"name": "中筋面粉",
"amount": "2 1/4 杯",
"unit": "杯"
},
{
"name": "泡打粉",
"amount": "1 茶匙",
"unit": "茶匙"
},
<...truncated for brevity...>
],
"instructions": [
"将烤箱预热至华氏 375 度(摄氏 190 度).",
"在一个中等大小的碗里,将面粉和泡打粉搅拌均匀.",
"在一个大碗里,将黄油和糖搅拌至轻盈蓬松.",
"一个一个地加入鸡蛋,然后加入香草精并搅拌均匀.",
"逐渐加入干性材料,搅拌至刚刚混合均匀.",
"拌入巧克力豆.",
"用勺子将面团舀到未涂油的烤盘上.",
"烤 9-11 分钟,或直至边缘呈金黄色.",
"在烤盘上稍微冷却几分钟后,再转移到冷却架上完全冷却."
]
}
]
```
那么,如何利用控制生成让输出符合特定格式,并去除不需要的反引号呢?
# 使用控制生成的示例(最简单的方法)
最简单的方法是使用受控生成,在配置模型时将 response_mime_type (响应 MIME 类型) 设置为 application/json。
from vertexai import generative_models
from vertexai.generative_models import GenerativeModel
model = GenerativeModel(
model_name="gemini-1.5-flash",
generation_config={"response_mime_type": "application/json"},
)
prompt = """列出几个曲奇食谱。
输出必须是JSON格式。
示例:
[{"recipe_name": "燕麦饼干"}, {"recipe_name": "花生酱饼干"}]
[{"recipe_name": "巧克力豆饼干"}, {"recipe_name": "蔓越莓饼干"}]
输出:
"""
response = model.generate_content(prompt)
print(response.text)
你看一下这个结果:
[{"recipe_name": "巧克力豆曲奇"}, {"recipe_name": "燕麦葡萄干曲奇"}, {"recipe_name": "糖曲奇"}, {"recipe_name": "姜饼曲奇"}, {"recipe_name": "花生酱曲奇"}]
就这样!你的输出已经完美格式化,可以直接使用 `[json.loads()](https://docs.python.org/3/library/json.html)` 转换为 Python 中的 JSON 对象:看,就是这样。
import json
print(json.loads(response.text))
实际上,我在下面用一个for循环运行100次,看看它是否能持续生成一致的受控生成JSON输出。
from vertexai import generative_models
from vertexai.generative_models import GenerativeModel
model = GenerativeModel(
model_name="gemini-1.5-flash",
generation_config={"response_mime_type": "application/json"},
)
prompt = """请列出几个饼干食谱。
输出必须为 JSON 格式。
示例:
[{"recipe_name": "燕麦饼干"}, {"recipe_name": "花生酱饼干"}]
[{"recipe_name": "巧克力芯片饼干"}, {"recipe_name": "蔓越莓饼干"}]
输出:
"""
结果 = []
for _ in range(100):
response = model.generate_content(prompt)
尝试:
json.loads(response.text)
结果.append(1)
除了 Exception:
结果.append(0)
结果的得分是100分——这意味着,每个响应都符合正确的JSON格式。但在移除了response_mime_type之后,结果的得分竟然变成了0(因为每个响应都被反引号包裹或无法解析为JSON)!
注释: 带有指定响应框架的受控生成
如果你不满足于仅仅生成有效的 JSON 输出,并且清楚你可能需要哪些字段,你也可以直接在 response_schema 中指定所需的字段。
例如,假设你有一些未结构化的错误日志数据,你想将这些错误日志提取并转换为特定格式的 JSON。你可以提供一个特定的模式(基于 [OpenAPI 3.0 模式](https://spec.openapis.org/oas/v3.0.3#schema) 定义),例如,并指定哪些字段在输出中必须包含。
请注意,如果指定了 `response_schema`,`response_mime_type` 必须设置为 `application/json`,这一点很重要。
response_schema = {
"type": "ARRAY",
"items": {
"type": "OBJECT",
"properties": {
"timestamp": {"type": "STRING"},
"error_code": {"type": "INTEGER"},
"error_message": {"type": "STRING"},
},
"required": ["timestamp", "error_message", "error_code"],
},
}
prompt = """
如下
[15:43:28] 错误: 无法处理图片上传: 不支持的文件格式。 (错误代码: 308)
[15:44:10] INFO: 搜索索引更新成功。
[15:45:02] 错误: 服务依赖不可用 (支付网关)。正在重试中... (错误代码: 5522)
[15:45:33] 错误: 应用程序因内存不足而崩溃。 (错误代码: 9001)
"""
response = model.generate_content(
prompt,
generation_config=GenerationConfig(
response_mime_type="application/json",
response_schema=response_schema
),
)
print(response.text)
下面的内容按 `response_schema` 格式显示。
[{"error_code": 308, "error_message": "无法处理图片上传:不支持的文件类型。" , "timestamp": "15:43:28"}, {"error_code": 5522, "error_message": "服务依赖不可用(支付网关)。正在重试中..." , "timestamp": "15:45:02"}, {"error_code": 9001, "error_message": "应用程序因内存溢出异常而崩溃了。" , "timestamp": "15:45:33"}]
这就是用Vertex AI上的Gemini API进行的可控生成哦!
但是,有哪些好的**用例**适合受控生成呢?
受控生成的使用案例
## “先设定你的限制条件,之后再自由地提出。”
如你在上面的错误日志示例中和受限的 response_schema 中所见,你可以一次性设置 response_schema 的值,将其与提示语分开设置。这使得你可以很简单地在多次使用不同提示语调用 Gemini 时,继续使用相同的 response_schema。如果你希望在不让用户担心提示语内容的情况下设置 response_schema,这将特别有用。
## 数据提取
就像处理上面的错误日志那样,你可以使用 Gemini 将非结构化数据转换为结构化数据(如 JSON 格式),从而使下游处理更简单。
## 合成数据制造
如果你在创建虚拟数据,比如像游戏中的[非玩家角色](https://en.wikipedia.org/wiki/Non-player_character)这样的[NPCs],那么你可以快速生成遵循特定模板的角色简介:
response_schema = {
"type": "ARRAY",
"items": {
"type": "OBJECT",
"properties": {
"name": {"type": "STRING"},
"age": {"type": "INTEGER"},
"occupation": {"type": "STRING"},
"background": {"type": "STRING"},
"playable": {"type": "BOOLEAN"},
"children": {
"type": "ARRAY",
"items": {
"type": "OBJECT",
"properties": {
"name": {"type": "STRING"},
"age": {"type": "INTEGER"},
},
"required": ["name", "age"],
},
},
},
"required": ["name", "age", "occupation", "children"],
},
}
prompt = """
生成一个用于视频游戏的角色资料,包括角色的名字、年龄、职业、背景故事、三个孩子各自的姓名,以及玩家是否可以控制他们。
"""
response = model.generate_content(
prompt,
generation_config=GenerationConfig(
response_mime_type="application/json",
response_schema=response_schema
),
)
print(response.text)
如下所示:
[{
"age": 42,
"children": [
{
"age": 21,
"name": "梅瑞达"
},
{
"age": 18,
"name": "弗ergus"
},
{
"age": 18,
"name": "哈里斯"
}
],
"name": "伊丽诺",
"occupation": "女王",
"background": "伊丽诺,这位深受爱戴的繁荣王国的女王,以她的智慧、优雅和坚定不移的力量而闻名。在她丈夫不幸去世后,她成功地应对了无数的挑战,赢得了她人民和邻国统治者的尊敬。然而,一个新的威胁出现,这将考验伊丽诺的坚韧与韧性,迫使她面对她的过去",
"playable": "可玩角色"
},
{
"age": 25,
"children": [],
"name": "凯伦",
"occupation": "猎人",
"background": "凯伦是一位依靠直觉和对荒野的了解来生活的熟练猎人和追踪者。他独立性强,对外来者警惕,但对信任的人忠诚不渝。被过去的悲剧所困扰,凯伦努力在复仇和正义之间找到平衡",
"playable": "可玩角色"
}
]
用枚举限制某些特定字段的值
情感分析通常会将评论分为“好、坏”或“正面、负面”等,但如果希望响应始终限定在特定的可能值,比如“积极、消极、中立”,或者评分始终必须是整数,你又该怎么办?
通过控制生成,你可以指定可能的值,并使用枚举:
```with controlled generation, you can specify what you want the possible values to be```
你可以使用枚举来指定可能的值:
model = GenerativeModel("gemini-1.5-flash")
response_schema = {
"type": "ARRAY",
"items": {
"type": "OBJECT",
"properties":{
"review": {"type": "STRING"},
"sentiment":{"type": "STRING",
"enum": ["POSITIVE", "NEGATIVE", "NEUTRAL"]
}
}
}
}
prompt = """
分析以下产品评论,给出情感分类的结果并为每个评论提供解释。
- "简直爱死它了!这是我吃过的最好吃的冰淇淋了。" 评分:4,口味:草莓芝士饼
- "还可以,不过对我来说有点太甜了。" 评分:1,口味:芒果塔
"""
response = model.generate_content(
prompt,
generation_config=GenerationConfig(
response_mime_type="application/json", response_schema=response_schema
),
)
print(response.text)
如下所示,情感将被归类为“POSITIVE”、“NEGATIVE”或“NEUTRAL”其中之一:
[{"review": "我超喜欢!这是我吃过的最好吃的冰淇淋。", "sentiment": "正面"}, {"review": "不错,但对我来说有点太甜了。", "sentiment": "负面"}]
## 如何简单地将 JSON 转换为 pandas 数据帧
通过受控生成,将 Gemini 的 JSON 输出转换为 Pandas DataFrame 变得简单多了。
例如,假设我们有一个控制生成的输出结果(一个 `str` 字符串对象),我们首先可以将其作为 Python 中的 JSON 对象来加载,
导入 json
json_response = json.loads(response.text)
print(json_response)
见下:
或
请看下面的内容:
[{"recipe_name": "巧克力芯片曲奇饼干"}, {"recipe_name": "燕麦葡萄干曲奇饼干"}, {"recipe_name": "糖曲奇饼干"}, {"recipe_name": "姜汁糖圆曲奇饼干"}, {"recipe_name": "花生酱曲奇饼干"}]
你可以将 JSON 对象转换为 pandas 数据帧:
import pandas as pd
df = pd.DataFrame(json_response)
df # 输出数据框

这是一例很好的情况,对于想要将生成式AI的结果融入工作流程的数据分析师和数据科学家来说,因为将JSON输出强制符合特定模式会使得处理数据帧的任务更容易,例如从数据帧生成图表。
# 最佳实践及限制
关于[model behavior and response schema](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output#model_behavior_and_response_schema)的简短说明:
* 受控生成支持 OpenAPI 3.0 [模式](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/Schema) 的一部分功能。
* 使用受控生成可以强制输出格式,但不能控制实际响应内容。
* 默认情况下,字段是可选的,意味着模型可以选择填充这些字段或跳过它们。你可以将字段设为必填,强制模型提供值。如果输入提示中缺乏足够的上下文,模型将主要基于其训练数据生成响应。这可以通过将这些字段的“nullable”属性设为 True 来缓解。例如: `"Wind Speed": {"type": "INTEGER", "nullable": True}`。
* 输出标记限制与 [Gemini 模型](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models) 相同(例如,对于 Gemini 1.5 Flash,你可以使用受控生成输出 8,192 个标记)。
# 结论段落
现在你懂得了如何通过强制Gemini遵循JSON格式、特定的响应模式(schema)以及你提供的特定枚举来控制其输出内容。
如果您想亲自试一试,可以看看下面的 [Github 上的笔记本](https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/controlled-generation/intro_controlled_generation.ipynb) 或 [Vertex AI 的控制生成文档](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/control-generated-output),了解这一强大功能如何帮助您今天提升您的生成式 AI 工作流程。
共同学习,写下你的评论
暂无评论
作者其他优质文章