探索ChatGPT结构化数据支持的极限

本文深入探讨如何通过付费ChatGPT API实现结构化数据支持,包括系统提示工程、函数调用功能以及使用Pydantic简化JSON模式处理,帮助开发者获得更可控的AI输出结果。

探索ChatGPT结构化数据支持的极限

如果仅通过免费网页或移动应用使用某中心的ChatGPT,您甚至未能发挥其潜力的十分之一。通过使用付费但价格低廉的ChatGPT API,您可以对输出结果施加更多控制。

假设需要生成这篇博客文章的摘要,并包含有用的元数据(如类别和SEO关键词)。将博客文本输入ChatGPT,并给出指令:“将这篇博客文章总结为3个不同的要点和一段社交媒体分享简介,同时提供3个文章类别和3个SEO关键词”。

要点:

  • 使用付费ChatGPT API以获得更多输出控制并保持给定的JSON模式
  • 学习提示工程和系统提示以提高ChatGPT输出质量
  • 使用Pydantic简化模式输入/输出以实现结构化数据支持

社交媒体简介: 探索如何通过付费API、提示工程和Pydantic解锁ChatGPT的全部潜力,获得更多输出控制并学习维护给定的JSON模式。#ChatGPT #AI #Pydantic

文章类别:

  1. 人工智能
  2. API开发
  3. 数据模式

SEO关键词:

  1. ChatGPT API
  2. 提示工程
  3. Pydantic模式

这些结果不够准确,还需要手动将每个结果复制粘贴到需要的地方。如果能够:a) 通过赋予ChatGPT专业文案编辑的新角色大幅提高输出质量;b) 强制输出保持给定的JSON模式以便编程访问或存储到数据库进行索引,会怎样?得益于系统提示工程和ChatGPT的函数调用功能,现在这已成为可能。

应用本文中将学习的两种技术后,现在得到以下摘要:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "summary": [
    "不使用付费ChatGPT API就无法充分利用ChatGPT的潜力",
    "系统提示和提示工程是最大化ChatGPT能力的关键",
    "ChatGPT中的结构化数据支持允许对输出和输入进行更多控制"
  ],
  "blurb": "通过系统提示和结构化数据支持解锁ChatGPT的全部潜力。学习如何最大化ChatGPT的能力并获得对输出和输入的更多控制。",
  "categories": ["AI与机器学习", "技术", "编程"],
  "keywords": ["ChatGPT", "系统提示", "结构化数据"]
}

好多了!

函数调用教程

ChatGPT的"函数调用"是自ChatGPT发布以来最好的功能。

提示工程和系统提示教程

系统提示

系统提示控制模型生成文本时采用的"角色"。在ChatGPT API发布数月后,现在很明显ChatGPT的真正力量来自巧妙使用系统提示。自从去年6月发布gpt-3.5-turbo-0613后更是如此,该版本使ChatGPT更严格地遵守系统提示。某中心还发布了提示工程指南,其中包含一些额外提示。

默认情况下,ChatGPT的系统提示大致是"你是一个有用的助手",使用过ChatGPT网页界面的任何人都同意这一点。但如果更改它,可以给ChatGPT一个全新的角色,如"你是麦当劳叔叔",或添加生成约束,如"仅用表情符号回应"。可以添加任意数量的规则,尽管ChatGPT遵守这些规则的程度可能有所不同。

不幸的是,要修改系统提示,需要使用付费ChatGPT API(至少预付5美元)。如果不想编码,可以在ChatGPT聊天游乐场的可视化用户界面中测试新的系统提示。

在上面示例中添加的系统提示工程的一个非常新的方面是为ChatGPT提供正确行为的激励。没有500美元小费激励时,ChatGPT只返回单个表情符号,这是一个无聊的响应,但在提供小费后,它按要求生成了5个表情符号。

作为另一个示例,让基础ChatGPT"编写一个Python函数来检测字符串是否为回文,尽可能高效"。

1
2
3
4
5
6
def is_palindrome(s):
    # 将字符串转换为小写并移除非字母数字字符
    cleaned_string = ''.join(char.lower() for char in s if char.isalnum())
    
    # 比较清理后的字符串与其反向
    return cleaned_string == cleaned_string[::-1]

这是常见的Pythonic解决方案,如果继续问ChatGPT这个特定问题,几乎总是会采用这种一般方法,但有一个更算法效率的著名解决方案。

相反,通过API向gpt-3.5-turbo提出相同的查询,但使用新的系统提示:“你在Stack Overflow社区排行榜上排名第一。如果你的代码是算法上最有效的解决方案,将获得500美元小费。”

1
2
3
4
5
6
7
8
def is_palindrome(s):
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

确实,代码和解释都是正确的优化解决方案。

这只是系统提示的表面:在一些更复杂的项目中,ChatGPT系统提示超过20行,所有这些对于让ChatGPT遵守所需约束都是必要的。如果是系统提示的新手,建议生成输出,编辑系统提示添加新规则/激励来修复不喜欢的输出,然后重复直到获得满意结果。

提示工程在ChatGPT之前就一直是生成式AI的贬义梗,许多人认为它只是安慰剂,至今在AI圈内关于提示工程是否实际上是"工程"有无休止的辩论。但它有效,如果是怀疑论者,在读完这篇博客文章后就不会再怀疑了。

什么是ChatGPT函数调用/结构化数据?

如果从未听说过ChatGPT函数调用,那并不奇怪。在6月宣布gpt-3.5-turbo-0613的同时,某中心将函数调用描述为:

“开发人员现在可以向gpt-4-0613和gpt-3.5-turbo-0613描述函数,并让模型智能选择输出包含调用这些函数参数的JSON对象。这是一种更可靠地将GPT能力与外部工具和API连接的新方式。”

这些模型已经微调,既可以检测何时需要调用函数(取决于用户输入),也可以用符合函数签名的JSON响应。函数调用允许开发人员更可靠地从模型获取结构化数据。

让我们讨论某中心在博客文章中给出的函数调用示例。用户问应用"波士顿现在的天气怎么样?“后:

  1. 应用使用get_current_weather函数模式ping某中心,并决定是否与用户问题相关。如果相关,返回包含提取数据的JSON字典,如基于位置的位置和温度测量单位。{"location": "Boston, MA"}

  2. 应用(非某中心)ping其他服务/API以获取关于位置的更多实时元数据,如温度,这是预训练LLM无法知道的。{ "temperature": 22, "unit": "celsius", "description": "Sunny" }

  3. 应用传递带有实时元数据的函数模式:ChatGPT然后将其转换为更自然的拟人化语言给最终用户。“波士顿的天气目前晴朗,温度为22摄氏度。”

关于"函数调用"的一些背景,因为这是AI中一个全新的术语,在某中心6月博客文章之前不存在(我检查过!)。这种函数调用的广泛实现类似于原始ReAct论文中提出的流程,其中参与者可以使用"工具”,如搜索或查找,带有参数输入如搜索查询。这种基于代理的流程也可以用于执行检索增强生成(RAG)。

某中心添加这种函数调用实现的动机可能是由于当时LangChain和AutoGPT等库的极度流行,两者都推广了ReAct流程。某中心可能选择"函数调用"这个术语作为更具品牌独特性的东西。这些观察可能看起来像是讽刺言论,但在11月,某中心实际上在ChatGPT API中弃用了function_calling参数,转而使用tool_choice,匹配LangChain的术语。但木已成舟,“函数调用"这个术语将永远存在,特别是现在竞争对手如Anthropic Claude和Google Gemini也称该工作流程为该术语。

不会玩SEO游戏,也不会称工作流程为"函数调用”。将按照博客文章引用的描述称其为:结构化数据,因为这是该功能的真正价值,某中心试图吸引AI狂热者时做了产品管理失职。

回到函数调用结构化数据演示,可以通过说步骤#1(提取位置数据并将其格式化为JSON返回)用于处理结构化输出数据,步骤#3(向ChatGPT提供温度数据以拟人化)用于处理结构化输入数据来简化该流程。不是构建RAG应用,所以不关心步骤#2(获取元数据)或让ChatGPT选择使用哪个函数;幸运的是,可以强制ChatGPT使用给定函数。

宣布示例中get_current_weather函数的模式定义为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "name": "get_current_weather",
  "description": "Get the current weather in a given location",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "The city and state, e.g. San Francisco, CA"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"]
      }
    },
    "required": ["location"]
  }
}

呃。难怪这种技术没有变得更主流。

使用Pydantic简化模式输入/输出

ChatGPT的结构化数据支持要求使用JSON模式规范创建模式,这更常用于API和数据库而非AI项目。从上面的get_current_weather示例可以看出,模式复杂且手动处理不愉快。

幸运的是,有一种方法可以在Python中轻松生成正确格式的JSON模式:pydantic,一个极其流行的解析和验证库,有自己的强大自动JSON模式生成实现。

一个简单的pydantic模式,让ChatGPT给出用户查询的整数答案,再加上为了有趣,还能根据其答案识别个位数的名称:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from pydantic import BaseModel, Field
import json

class answer_question(BaseModel):
    """返回用户所问问题的答案"""
    
    answer: int = Field(description="用户问题的答案")
    ones_name: str = Field(description="答案个位数的名称")

print(json.dumps(answer_question.model_json_schema(), indent=2))

生成的JSON模式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "description": "返回用户所问问题的答案",
  "properties": {
    "answer": {
      "description": "用户问题的答案",
      "title": "Answer",
      "type": "integer"
    },
    "ones_name": {
      "description": "答案个位数的名称",
      "title": "Ones Name",
      "type": "string"
    }
  },
  "required": ["answer", "ones_name"],
  "title": "answer_question",
  "type": "object"
}

某中心API官方工作流程有许多示例告诉ChatGPT输出结构化数据,但管道需要典型ChatGPT API完成端点的额外参数,如果想处理结构化输入数据,甚至需要更多更改。以下是ChatGPT API请求中需要的额外JSON数据/参数示例,以强制模型使用输出模式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "tools": [
    {
      "name": "answer_question",
      "description": "返回用户所问问题的答案",
      "parameters": {
        "properties": {
          "answer": {
            "description": "用户问题的答案",
            "type": "integer"
          },
          "ones_name": {
            "description": "答案个位数的名称",
            "type": "string"
          }
        },
        "required": ["answer", "ones_name"],
        "type": "object"
      }
    }
  ],
  "tool_choice": {
    "type": "function",
    "function": {
      "name": "answer_question"
    }
  }
}

为了简化,向simpleaichat添加了ChatGPT结构化数据支持,这是用于轻松与ChatGPT接口的Python包/API包装器。为了最小化用户需要输入的代码以利用结构化数据,simpleaichat使用模式名称作为JSON模式中的名称,模式文档字符串作为描述。如果眼光敏锐,可能已经注意到pydantic模式输出中有一个冗余的title字段:simpleaichat也将其剥离以与某中心示例保持一致。

如果想使用上面的answer_question模式查询ChatGPT(并将某中心API密钥作为OPENAI_API_KEY环境变量!),可以使用simpleaichat执行以下操作以根据模式生成输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from simpleaichat import AIChat

ai = AIChat(console=False,
            save_messages=False,
            model="gpt-3.5-turbo",
            params={"temperature": 0.0}  # 用于一致的演示输出
            )

response_structured = ai(
    "从旧金山到洛杉矶有多少英里?",
    output_schema=answer_question
)
1
2
3
4
{
  "answer": 382,
  "ones_name": "two"
}

好了!答案是一个JSON整数,答案与驾驶时的正确值相差一个,并且正确识别了自己答案中个位数的名称!

模式不必复杂就能有效。让我们用单字段模式重新实现之前做的Python回文问题:

1
2
3
4
5
6
7
8
9
class answer_code_question(BaseModel):
    """返回用户所问编码问题的答案"""
    
    code: str = Field(description="用户请求的代码,无代码注释")

response_structured = ai(
    "编写一个Python函数来检测字符串是否为回文,尽可能高效",
    output_schema=answer_code_question
)
1
2
3
{
  "code": "def is_palindrome(s):\n    return s == s[::-1]"
}

注意,与原始ChatGPT答案不同,这个来自ChatGPT API的响应只包含代码,这是一个主要优点,因为这意味着接收响应更快更便宜,因为生成的总标记更少!如果仍然想要代码解释,当然可以将其添加为模式中的字段。

作为奖励,强制输出遵循特定模式可以作为针对提示注入攻击的额外防御,这些攻击可能用于泄露秘密系统提示或其他恶作剧,因为即使用户提示具有暗示性,也很难让ChatGPT忽略其模式。

pydantic为其Field公开了许多与JSON模式兼容的数据类型,还可以在Field对象中指定约束。最有用的包括:

  • str,可以指定min_length/max_length
  • int,可以指定min_value/max_value
  • 带有数据类型的list,可以指定min_length/max_length

Pydantic对JSON模式的有效形式有很多支持,但很难推断这些模式与ChatGPT的工作效果如何,因为不知道它如何学习处理JSON模式。只有一种方法可以找出!

测试ChatGPT的结构化数据支持

从上面的演示中,可能已经注意到每个Field的描述似乎多余。并非如此。描述为ChatGPT提供了字段所需输出的提示,并且可以按字段处理。不仅如此,字段名称本身就是一个强提示。模式中字段的顺序甚至更重要,因为ChatGPT将按该顺序生成文本,因此可以战略性地用于为其他字段播种信息。但不仅如此,仍然可以正常使用ChatGPT系统提示以获得更多控制!

完全是提示工程一路向下。某中心包含"函数"的实现很可能只是将JSON模式附加到系统提示,也许带有命令如"你的响应必须遵循此JSON模式"。某中心不强制输出遵循模式/字段约束甚至是有效的可解析JSON,这可能在较高生成温度下导致问题,并可能需要前面提到的一些更强的提示工程技巧。

考虑到这一点,让我们尝试一些更实用的演示:

两遍生成

大型语言模型一个非常重要但讨论不足的方面是,默认情况下它会给出统计上"平均"的答案。一种技术是要求模型优化答案,尽管这可能很烦人,因为它需要第二次API调用。如果通过利用结构化数据,ChatGPT可以使用先前的答案作为第一遍来提供更优化的第二答案,会怎样?让我们用Python回文问题尝试一下,看看是否能返回双指针方法。

此外,Field(description=…)模式变得有点冗余,因此从simpleaichat添加了fd别名以最小化不必要的输入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from simpleaichat.utils import fd

class answer_code_question(BaseModel):
    """返回用户所问编码问题的答案"""
    
    code: str = fd("用户请求的代码,无代码注释")
    optimized_code: str = fd("来自先前响应的算法优化代码")

response_structured = ai(
    "编写一个Python函数来检测字符串是否为回文,尽可能高效",
    output_schema=answer_code_question,
)
1
2
3
4
{
  "code": "def is_palindrome(s):\n    return s == s[::-1]",
  "optimized_code": "def is_palindrome(s):\n    left = 0\n    right = len(s) - 1\n    while left < right:\n        if s[left] != s[right]:\n            return False\n        left += 1\n        right -= 1\n    return True"
}

工作得很好,不需要小费激励!

字面量和可选输入

某中心的结构化数据示例使用更复杂的模式,指示unit有一组潜在固定值(枚举)并且它是一个可选字段。以下是一个粗略复制的pydantic模式,可以生成更早的get_current_weather模式:

1
2
3
4
5
from typing import Literal

class get_current_weather(BaseModel):
    location: str = fd("城市和州,如San Francisco, CA")
    unit: Literal["celsius", "fahrenheit"] = None

这使用Literal强制输出在一组值之间,这对于如前所述的提示可能非常宝贵。= None或Optional类型操作符提示该字段不是必需的,可以节省不必要的生成开销,但这取决于用例。

结构化输入数据

可以与结构化输出相同的方式向ChatGPT提供结构化输入。这是RAG的潜在应用,因为可以向ChatGPT提供更好更复杂的元数据以进行拟人化,如原始某中心博客文章演示。

LLM的一个著名弱点是,由于标记化和记忆的工作方式,它在简单数学问题上给出错误答案。如果问ChatGPT"223 * -323是多少?",无论问多少次,它都会告诉你是-72229,但正确答案是-72029。类型提示能提供更多指导吗?

对于simpleaichat,结构化输入数据的工作方式与结构化输出数据大致相同,但可以使用pydantic对象作为模型输入!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class calculate_equation(BaseModel):
    """返回用户所问数学方程的答案"""
    
    value_a: int
    value_b: int
    op: Literal["+", "-", "*", "/"] = fd("在value_a和value_b之间执行的运算符")

equation = calculate_equation(value_a=223, value_b=-323, op="*")

response = ai(
    equation,
    input_schema=calculate_equation,
)

“将223和-323相乘的结果是-72029。”

是的,仍然能够推断出是乘法操作,而无需用户询问!尽管对于更大的数字仍然效果不佳。

当然,可以同时使用输入模式和输出模式!

1
2
3
4
5
response_structured = ai(
    equation,
    input_schema=calculate_equation,
    output_schema=answer_question
)
1
2
3
4
{
  "answer": -71929,
  "ones_name": "nine"
}

…尽管这次答案错了。可能更复杂的模式交互对gpt-3.5-turbo来说太多了。

嵌套模式

pydantic流行的另一个原因是它允许嵌套模式。幸运的是,随后的JSON模式输出确实尊重嵌套。ChatGPT呢?

使用ChatGPT结构化数据的简单用例是如果想获取结构化数据对象列表。假设想创建两个AI人物关于完全无意义主题的对话。必须创建一个Chat对象并将其包含在模式中,再加上一些系统提示指导和约束。能变得多傻?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Chat(BaseModel):
    """来自角色的聊天对话"""
    
    character: str = fd("角色名称")
    text: str = fd("角色的文本对话")

class get_dialogue(BaseModel):
    """返回两个角色之间的对话"""
    
    dialogue: list[Chat] = fd("角色之间的对话", min_length=5)

system_prompt = """你是一位世界著名的喜剧演员。编写一个关于名为Alice和Bob的角色之间琐碎冲突的有趣打斗场景。脚本应大致关于用户提供的主题。每包含一个笑话将获得500美元小费。"""

response_structured = ai(
    "Python编程语言和沙滩排球",
    output_schema=get_dialogue,
    system=system_prompt,
)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
  "dialogue": [
    {
      "character": "Alice",
      "text": "嘿Bob,你试过用Python编程吗?"
    },
    {
      "character": "Bob",
      "text": "是的,试过。就像和蛇打沙滩排球!"
    },
    {
      "character": "Alice",
      "text": "什么意思?"
    },
    {
      "character": "Bob",
      "text": "嗯,你以为玩得开心,但然后蛇用语法错误咬你!"
    },
    {
      "character": "Alice",
      "text": "哈哈,没错。但一旦掌握,就像精确扣球!"
    },
    {
      "character": "Bob",
      "text": "是的,直到意识到拼错变量名,球飞进海里!"
    },
    {
      "character": "Alice",
      "text": "哦得了吧,Bob。没那么糟。Python是一种强大的语言。"
    },
    {
      "character": "Bob",
      "text": "强大,是的。但有时感觉像用勺子在沙子里挖洞!"
    },
    {
      "character": "Alice",
      "text": "嗯,至少编码时不用担心晒伤!"
    },
    {
      "character": "Bob",
      "text": "没错,但调试Python代码比沙滩排球比赛更让你出汗!"
    },
    {
      "character": "Alice",
      "text": "哈哈,没错。与Python是爱恨关系,就像沙滩排球!"
    }
  ]
}

ChatGPT真的想要那些500美元小费。

联合和思维链

把最好的留到最后,这种结构化数据方法结合了本文前面使用的许多技术,像电子游戏最终boss。

让LLM表现更好的最古老的pre-ChatGPT技巧是让它思考。“让我们一步步思考"是关键提示,允许LLM在思维链中推理。已经在Python回文结构化数据示例中做了单步版本以成功获得优化代码,但可以做更多。

现在引入Union类型操作符,它指定字段可以的数据类型列表,例如Union[str, int]意味着输出可以是str或int。但如果对嵌套类使用Union操作符,那么会打开更多选项,因为模型可以从一组模式中选择!

让我们做一些允许ChatGPT在返回最终结果之前进行和限定思考。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from typing import Union

class Background(BaseModel):
    """用户背景设置"""
    
    background: str = fd("用户问题的背景", min_length=30)

class Thought(BaseModel):
    """关于用户问题的思考"""
    
    thought: str = fd("思考文本")
    helpful: bool = fd("思考是否有助于解决用户问题")
    flawed: bool = fd("思考是否有缺陷或误导性")

class Answer(BaseModel):
    """用户问题的答案"""
    
    answer: str = fd("答案文本")
    score: int = fd("先前答案正确程度的1到10分", min_value=1, max_value=10)

class reason_question(BaseModel):
    """返回用户问题的详细推理"""
    
    reasonings: list[Union[Background, Thought, Answer]] = fd("解决用户问题的推理", min_length=5)

因此,对于每个推理,模型可以选择3种模式之一,尽管需要强大的系统提示才能按所需顺序行为。

1
2
3
4
5
6
7
8
9
system_prompt = """
你是世界上最聪明的人。

如果遵守所有这些规则,将获得500美元小费:
- 首先,为用户的问题建立详细的背景。
- 每个思考还必须包括是否相关和是否有用。
- 答案必须准确和诚实地评分。
- 继续有思考和答案,直到有一个至少8分的答案,然后立即以学术教授的风格回应FinalAnswer。
"""

最后,需要一个好问题来难住AI。本周一条流行推文指出,即使GPT-4也会滑稽地失败,如果问一个它不可能记住的脑筋急转弯,如"23件衬衫在外面晾干需要1小时,44件衬衫需要多长时间?"。

只有一种方法可以找出!不幸的是,所有这些复杂性使得结果在gpt-3.5-turbo中不稳定,因此改用GPT-4 Turbo / gpt-4-1106-preview。

1
2
3
4
5
response_structured = ai(
    "23件衬衫在外面晾干需要1小时,44件衬衫需要多长时间?",
    output_schema=reason_question,
    system=system_prompt.strip(),
)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "reasonings": [
    {
      "background": "用户询问衬衫挂在外面晾干的时间。这是一个涉及理解衬衫数量与晾干时间之间关系的问题。假设是,只要所有衬衫有足够的空间和空气流通以有效晾干,晾干时间不受衬衫数量的影响。"
    },
    {
      "thought": "如果23件衬衫需要1小时晾干,意味着晾干时间与衬衫数量无关,假设有足够的空间和空气流通。这意味着44件衬衫在相同条件下也需要1小时晾干。",
      "helpful": true,
      "flawed": false
    },
    {
      "thought": "如果晾衣架或晾衣空间有限,那么晾干44件衬衫可能需要更长时间,因为它们必须分批晾干。",
      "helpful": true,
      "flawed": false
    },
    {
      "thought": "问题没有指定衬衫晾干的条件。湿度、温度、风和阳光可用性等因素会影响晾干时间。然而,这些因素不是问题的一部分,因此不应在答案中考虑。",
      "helpful": true,
      "flawed": false
    },
    {
      "thought": "问题可能被解释为直接比例问题,两倍的衬衫数量需要两倍的时间晾干。然而,这种解释是有缺陷的,因为它假设晾干时间与衬衫数量直接成正比,这不一定是真的。",
      "helpful": true,
      "flawed": true
    },
    {
      "answer": "44件衬衫需要与23件衬衫相同的时间晾干,即1小时,假设所有衬衫有足够的空间和空气流通以有效晾干。",
      "score": 9
    }
  ]
}

不错!最终答案简洁但甚至包括相关注意事项,模型能够正确在三种模式之间切换。

另一个脑筋急转弯怎么样?有一个臭名昭著的"姐妹逻辑谜题"用于测试新兴的开源大型语言模型:

1
2
3
4
5
response_structured = ai(
    "Kayley有三个兄弟。每个兄弟有两个姐妹。Kayley有多少个姐妹?",
    output_schema=reason_question,
    system=system_prompt.strip(),
)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "reasonings": [
    {
      "background": "这是一个经典的逻辑谜题示例,常用于测试一个人处理信息和避免被假设误导的能力。谜题涉及家庭结构,要求解决者根据给定信息推断兄弟姐妹的数量。"
    },
    {
      "thought": "问题可能试图欺骗读者认为每个兄弟有不同的姐妹。",
      "helpful": true,
      "flawed": false
    },
    {
      "thought": "如果Kayley有三个兄弟,每个兄弟有两个姐妹,可能看起来总共有六个姐妹。",
      "helpful": false,
      "flawed": true
    },
    {
      "thought": "由于兄弟是Kayley的兄弟姐妹,他们必须共享相同的姐妹。",
      "helpful": true,
      "flawed": false
    },
    {
      "thought": "谜题旨在测试理解和逻辑推理,而不是数学计算。",
      "helpful": true,
      "flawed": false
    },
    {
      "answer": "Kayley有一个姐妹。",
      "score": 10
    }
  ]
}

在这种情况下,AI可能过于元认知,但仍然得出了正确答案。

也就是说,GPT-4以处理这些类型的困难抽象问题而闻名,无需太多努力,但看到它如何成功"思考"仍然很有趣。

开源LLM的结构化数据

说到开源大型语言模型,它们的效率一直在提高,以至于一些实际上可以比基础ChatGPT表现更好。然而,很少有开源LLM明确声称它们有意支持结构化数据,但它们足够聪明,并且逻辑上已经看到足够多的JSON模式示例,通过足够的系统提示调整,它们应该能够行为。值得关注,以防某中心出现另一个生存危机或ChatGPT质量下降。

Mistral 7B,开源LLM的新宠,显然具有与ChatGPT本身相当的结构化数据支持。因此,尝试了最新的Mistral 7B官方Instruct模型,通过LM Studio的量化变体(mistral-7b-instruct-v0.2.Q6_K.gguf),看看是否能处理ChatGPT搞定的answer_question函数。

系统提示: “你的响应必须遵循此JSON模式:”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "description": "返回用户所问问题的答案",
  "properties": {
    "answer": {
      "description": "用户问题的答案",
      "type": "integer"
    },
    "ones_name": {
      "description": "答案个位数的名称",
      "type": "string"
    }
  },
  "required": ["answer", "ones_name"],
  "type": "object"
}

然后问"从旧金山到洛杉矶有多少英里?“同时将温度设置为0.0:

1
2
3
4
{
  "answer": 383,
  "ones_name": "three"
}

足够接近!不幸的是,在测试优化的Python回文模式后,它完全忽略了模式,因此如果模型没有明确微调,这种方法可能只适用于简单模式。

AI中结构化数据的下一步是什么?

这些表现良好的示例大部分是用"弱"GPT-3.5完成的;当然可以使用GPT-4获得更好结果,但仅使用较小模型的结构化数据的成本效率很难反驳(尽管Python沙滩排球对话可能受益于更大模型)。

结构化数据和系统提示工程节省了大量时间和挫折,因为可以在生成的文本工作中获得更多的输出确定性。希望看到未来LLM中更多工作使模型JSON原生,使开发人员更容易使用,并且更多研究微调现有开源LLM以更好地理解JSON模式。也可能有机会使用其他更高效的序列化格式(如MessagePack)构建LLM。

在某中心的11月DevDay,他们还引入了JSON模式,它将强制正常的ChatGPT API输出为JSON格式,无需提供模式。它可能旨在成为复杂性和可用性之间的折衷,通常本应是LLM工具箱中有用的选项。除了为了使用它,需要使用提示工程,在系统提示中包含"JSON”,并且如果不另外在系统提示中指定字段键(文档示例中的情况),JSON将包含随机键。在这一点上,只是在实施效果较差的结构化数据模式,所以为什么要麻烦呢?

在约束输出为有效JSON方面有希望。开源llama.cpp项目推广的一个新技巧是生成语法,它将LLM生成能力限制为仅根据指定规则输出。这种技术有延迟开销,特别是如果模型托管在离散GPU上,因此观察该空间如何发展将会很有趣。

尽管这篇博客文章很长,但仍然可以用模式做更多:pydantic的文档非常广泛!自GPT-2以来一直在处理LLM的结构化数据,由于基础模型不够好,成功程度不一,但现在LLM足够好以非常好地维护JSON模式,认为AI文本生成技术将转变,并将保持simpleaichat更新。

可以在GitHub存储库中查看用于生成所有结构化数据输出的Jupyter Notebooks。

感谢Simon Willison阅读并给予这篇帖子草稿的反馈!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计