利用 Llama.cpp 或 Gemini 的 API 强制 JSON 输出的教程

打印 上一主题 下一主题

主题 815|帖子 815|积分 2445

怎样从 LLM 获取 JSON 输出:实用指南

怎样从 LLM 获取 JSON 输出:实用指南


添加图片解释,不超过 140 字(可选)
         接待来到雲闪世界。大型语言模型 (LLM) 善于生成文本,但要获得像 JSON 这样的结构化输出通常需要奇妙的提示,并希望 LLM 能够明白。荣幸的是,JSON 模式在 LLM 框架和服务中变得越来越广泛。这让您可以定义所需的精确输出架构。
这篇文章介绍了利用 JSON 模式进行束缚生成。我们将利用一个复杂、嵌套且真实的 JSON 模式示例来指导 LLM 框架/API(如 Llama.cpp 或 Gemini API)生成结构化数据,特别是游客位置信息。这篇文章基于之前关于利用Guidance进行束缚生成的文章,但重点介绍了更广泛接纳的 JSON 模式。
虽然比Guidance更有限,但 JSON 模式的更广泛支持使其更易于访问,尤其是对于基于云的 LLM 提供商而言。
在个人项目中,我发现虽然 JSON 模式对于 Llama.cpp 来说很简单,但要使其与 Gemini API 共同利用则需要一些额外的步骤。本文分享了这些解决方案,以帮助您有效地利用 JSON 模式。


我们的 JSON 模式:旅游地点文档
我们的示例模式代表一个TouristLocation。这是一个非平凡的结构,具有嵌套对象、列表、罗列和各种数据类型(如字符串和数字)。
这是一个简化的版本:
  1. {
  2. “name” : “string” ,
  3. “location_long_lat” : [ “number” , “number” ] ,
  4. “climate_type” : { “type” : “string” , “enum” : [ “热带” , “沙漠” , “温带” , “大陆” , “极地” ] } ,
  5. “activity_types” : [ “string” ] ,
  6. “attraction_list” : [
  7. {
  8. “name” : “string” ,
  9. “description” : “string”
  10. }
  11. ] ,
  12. “tags” : [ “string” ] ,
  13. “description” : “string” ,
  14. “most_notably_known_for” : “string” ,
  15. “location_type” : { “type” : “string” , “enum” : [ “城市” , “国家” , “机构” , “地标” , “国家公园” , “岛屿” , “地区” , “大陆” ] } ,
  16. “父母” : [ “字符串” ]
  17. }
复制代码
您可以手写这种类型的模式,也可以利用 Pydantic 库生成它。下面是一个简化的示例:
  1. 从typing导入 列表
  2. 从pydantic导入BaseModel,Field
  3. class  TouristLocation ( BaseModel ):
  4.     """旅游地点模型"""
  5.      high_season_months: List [ int ] = Field(
  6.         [], description= "该地点游客最多的月份列表(1-12)"
  7.      )
  8.     tags: List [ str ] = Field(
  9.         ...,
  10.         description= "描述地点的标签列表(例如可访问、可持续、阳光充足、便宜、昂贵)" ,
  11.         min_length= 1 ,
  12.     )
  13.     description: str = Field(..., description= "位置的文本描述" )
  14. # 示例用法和架构输出
  15. location = TouristLocation(
  16.     high_season_months=[ 6 , 7 , 8 ],
  17.     tags=[ "海滩" , "阳光充足" , "家庭友好型" ],
  18.     description= "美丽的海滩,拥有白色的沙滩和清澈的蓝色海水。" ,
  19. )
  20. schema = location.model_json_schema()
  21. print (schema)
复制代码
  1. TouristLocation此代码使用 Pydantic 定义了数据类的简化版本。它有三个字段:
复制代码


  • high_season_months:表示该地点访问量最大的月份(1-12)的整数列表。默认为空列表。
  • tags:利用“可访问”、“可连续”等标签形貌位置的字符串列表。此字段是必填项 ( ...),而且必须至少包罗一个元素 ( min_length=1)。
  • description:包罗位置文本形貌的字符串字段。此字段也是必填的。
然后,代码会创建该类的一个实例TouristLocation,并用它model_json_schema()来获取模型的 JSON Schema 表示。该 Schema 定义了该类所需的数据的结构和类型。
model_json_schema()返回:
  1. { 'description':'旅游地点模型',
  2. 'properties':{ 'description':{ 'description':'地点的文字描述
  3.                                                ',
  4.                                 'title':'描述',
  5.                                 'type':'string' },
  6.                 'high_season_months':{ 'default':[],
  7.                                        'description':'月份列表(1-12)'
  8.                                                       ,'地点
  9.                                                       访问量最大',
  10.                                        'items':{ 'type':'integer' },
  11.                                        'title':'旺季月份',
  12.                                        'type':'array' },
  13.                 'tags':{ 'description':'描述地点的标签列表'
  14.                                         (例如可访问,可持续,阳光明媚,'
  15.                                         '便宜,昂贵)',
  16.                          'items':{ 'type' : 'string' },
  17.                          'minItems' : 1,
  18.                          'title' : '标签' ,
  19.                          'type' : 'array' }},
  20. 'required' : [ 'tags' , 'description' ],
  21. 'title' : 'TouristLocation' ,
  22. 'type' : 'object' }
复制代码
现在我们有了模式,让我们看看怎样实施它。首先在 Llama.cpp 中利用其 Python 包装器,其次利用 Gemini 的 API。


方法 1:利用 Llama.cpp 的简单方法
Llama.cpp,一个用于在本地运行 Llama 模型的 C++ 库。它对初学者很友爱,而且拥有一个活跃的社区。我们将通过其 Python 包装器利用它。
以下是利用它生成数据的方法TouristLocation:
  1. # 导入和其他内容
  2. # 模型初始化:
  3. checkpoint = “lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF”
  4. model = Llama.from_pretrained(
  5.     repo_id=checkpoint,
  6.     n_gpu_layers=-1,
  7.     filename= “*Q4_K_M.gguf” ,
  8.     verbose=False,
  9.     n_ctx=12_000,
  10. )
  11. messages = [
  12.     {
  13.         “role” : “system” ,
  14.         “content” : “您是一位以 JSON 格式输出的有用助手。”
  15.          f “遵循此模式 {TouristLocation.model_json_schema()}” ,
  16.     },
  17.     { “role” : “user” , “content” : “生成有关美国夏威夷的信息。” },
  18.     { "role" : "assistant" , "content" : f "{location.model_dump_json()}" },
  19.     { "role" : "user" , "content" : "生成有关卡萨布兰卡的信息" },
  20. ]
  21. response_format = {
  22.     "type" : "json_object" ,
  23.     "schema" : TouristLocation.model_json_schema(),
  24. }
  25. start = time.time()
  26. output = model.create_chat_completion(
  27.     messages=messages, max_tokens=1200, response_format=response_format
  28. )
  29. print(outputs[ "choices" ][0][ "message" ][ "content" ])
  30. print(f"时间:{time.time() - start}")
复制代码
  1. [/code] 代码首先导入必要的库并初始化 LLM 模型。然后,它定义与模型对话的消息列表,包括指示模型根据特定架构以 JSON 格式输出的系统消息、用户对夏威夷和卡萨布兰卡信息的请求以及利用指定架构的助手相应。
  2. Llama.cpp 在底层利用上下文无关语法来束缚结构并为新城市生成有效的 JSON 输出。
  3. 在输出中我们得到以下生成的字符串:
  4. [code]{ 'activity_types' :  [ '购物' , '美食与美酒' , '文化' ] ,
  5. 'attraction_list' :  [ { 'description' : '世界上最大的清真寺之一 '
  6.                                      ',摩洛哥建筑的象征' ,
  7.                        'name' : '哈桑二世清真寺' } ,
  8.                      { 'description' : '一座历史悠久的城墙城市,拥有狭窄的 '
  9.                                      '街道和传统商店' ,
  10.                        'name' : '老麦地那' } ,
  11.                      { 'description' : '一座历史悠久的广场,拥有美丽的 '
  12.                                      '喷泉和周围的建筑' ,
  13.                        'name' : '穆罕默德五世广场' } ,
  14.                      { 'description' : '一座美丽的天主教堂,建于 ' '20世纪
  15.                                      初' ,                       'name' : '卡萨布兰卡大教堂' } , { 'description' : '风景秀丽的海滨长廊,'                                      '可欣赏到城市和大海的美丽景色' ,                       'name' : 'Corniche' } ] , 'climate_type' : 'temperate' , 'description' : '一座拥有丰富历史和文化的繁华大城市' , 'location_type' : 'city' , 'most_notably_known_for' : '其历史建筑和文化'                            '意义' , 'name' : '卡萨布兰卡' , 'parents' : [ '摩洛哥' , '非洲' ] , 'tags' : [ 'city' , 'cultural' , 'historical' ,'昂贵的' ] }
复制代码
然后可以将其剖析为我们的 Pydantic 类的一个实例。


方法 2:克服 Gemini API 的怪癖
Gemini API 是 Google 的托管 LLM 服务,其文档中声称 Gemini Flash 1.5 仅支持有限的 JSON 模式。不外,只需进行一些调整即可实现该功能。
以下是使其工作的一样平常说明:
  1. schema = TouristLocation.model_json_schema()
  2. schema = replace_value_in_dict(schema.copy(), schema.copy())
  3. del schema[ "$defs" ]
  4. delete_keys_recursive(schema, key_to_delete= "title" )
  5. delete_keys_recursive(schema, key_to_delete= "location_long_lat" )
  6. delete_keys_recursive(schema, key_to_delete= "default" )
  7. delete_keys_recursive(schema, key_to_delete= "default" )
  8. delete_keys_recursive(schema, key_to_delete= "minItems" )
  9. print (schema)
  10. messages = [
  11.     ContentDict(
  12.         role= "user" ,
  13.         parts=[
  14.             "您是一位能以 JSON 格式输出的得力助手。"
  15.             f"请遵循此模式{TouristLocation.model_json_schema()} "
  16.          ],
  17.     ),
  18.     ContentDict(role= "user" , parts=[ "生成有关美国夏威夷的信息。" ]),
  19.     ContentDict(role= "model" , parts=[ f" {location.model_dump_json()} " ]),
  20.     ContentDict(role= "user" , parts=[ "生成有关卡萨布兰卡的信息" ]),
  21. ]
  22. genai.configure(api_key=os.environ[ "GOOGLE_API_KEY" ])
  23. # 将 `response_mime_type` 与 `response_schema` 结合使用需要 Gemini 1.5 Pro 模型
  24. model = genai.GenerativeModel(
  25.     "gemini-1.5-flash" ,
  26.     # 设置 `response_mime_type` 以输出 JSON
  27.     # 将架构对象传递给 `response_schema` 字段
  28.     generation_config={
  29.         "response_mime_type" : "application/json" ,
  30.         "response_schema" : schema,
  31.     },
  32. )
  33. response =模型.生成内容(消息)
  34. 打印(响应.文本)
复制代码
以下是怎样克服 Gemini 的局限性的方法:

  • 用完整定义替换$ref: Gemini 偶然发现了架构引用($ref)。当您有嵌套对象定义时会用到它们。用架构中的完整定义替换它们。
  1. def  replace_value_in_dict ( item, original_schema ):
  2.     # 来源:https://github.com/pydantic/pydantic/issues/889
  3.     if  isinstance (item, list ):
  4.         return [replace_value_in_dict(i, original_schema) for i in item]
  5.     elif  isinstance (item, dict ):
  6.         if  list (item.keys()) == [ "$ref" ]:
  7.             definitions = item[ "$ref" ][ 2 :].split( "/" )
  8.             res = original_schema.copy()
  9.             for definition in definitions:
  10.                 res = res[definition]
  11.             return res
  12.         else :
  13.             return {
  14.                 key: replace_value_in_dict(i, original_schema)
  15.                 for key, i in item.items()
  16.             }
  17.     else :
  18.         return item
复制代码

  • 删除不支持的键: Gemini 尚未处置惩罚“title”、“AnyOf”或“minItems”等键。请从您的架构中删除这些键。这会导致架构的可读性低沉和限定性低沉,但假如对峙利用 Gemini,我们别无选择。
  1. delete_keys_recursive(d, key_to_delete):
  2.     if isinstance(d, dict):
  3.         # Delete the key if it exists
  4.         if key_to_delete in d:
  5.             del d[key_to_delete]
  6.         # Recursively process all items in the dictionary
  7.         for k, v in d.items():
  8.             delete_keys_recursive(v, key_to_delete)
  9.     elif isinstance(d, list):
  10.         # Recursively process all items in the list
  11.         for item in d:
  12.             delete_keys_recursive(item, key_to_delete)
复制代码

  • 罗列的一次性或少量提示: Gemini 有时会遇到罗列题目,它会输出全部大概的值,而不是单个选择。这些值在单个字符串中也由“ |”分隔,根据我们的架构,这些值是无效的。利用一次性提示,提供正确格式的示例,以引导它实现所需的行为。
通过应用这些转换并提供清晰的示例,您可以利用 Gemini API 成功生成结构化 JSON 输出。


结论
JSON 模式答应您直接从 LLM 获取结构化数据,从而使其更适合现实应用。虽然 Llama.cpp 等框架提供了简单的实现,但您大概会遇到 Gemini API 等云服务的题目。
希望本博客能让您更好地现实了解 JSON 模式的工作原理,以及即使在利用目前仅提供部门支持的 Gemini API 时怎样利用它。
现在我能够让 Gemini 以 JSON 模式工作,我就可以完成我的 LLM 工作流程的实现,此中需要以特定方式构造数据。
本文的主要代码可关注雲闪世界。(Aws解决方案架构师vs开辟职员&GCP解决方案架构师vs开辟职员)
订阅频道(https://t.me/awsgoogvps_Host)
TG互换群(t.me/awsgoogvpsHost)


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张国伟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表