为了账号安全,请及时绑定邮箱和手机立即绑定

如何用Gemini API确保稳定输出JSON格式

你是否曾经需要一个大型语言模型始终输出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
  • 最佳实践和限制

跳到代码片段: 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 (点击链接查看)

基于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))

[{'recipe_name': '巧克力豆曲奇'}, {'recipe_name': '燕麦葡萄干曲奇'}, {'recipe_name': '肉桂糖饼干'}, {'recipe_name': '糖饼干'}, {'recipe_name': '花生酱饼干'}]

实际上,我在下面用一个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 # 输出数据框



![](https://imgapi.imooc.com/9742b16709c1500b04100384.jpg)

这是一例很好的情况,对于想要将生成式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 工作流程。
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

0 评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消