掌握结构化输出技巧,让大语言模型为你生成可直接用于代码的干净数据,提升应用效率。
一、结构化输出的重要性
结构化输出,简单来说,就是让大语言模型以一种清晰、可预测、有组织的格式返回信息,例如整齐的列表、带有标签的数据字段(键值对形式:key: value),甚至是简单的表格,而不是一长段对话式的文本。这一概念在实际应用中具有举足轻重的意义。
以构建一个利用人工智能分析客户评论的应用程序为例。当我们向模型询问“总结这条评论,并告诉我提到的产品和客户的总体情绪”时,模型可能给出这样的回复:“嗯,看起来客户简·D购买了‘MegaWidget 3000’,并且非常不满意。她提到产品只用了两天就坏了,整个体验让她感到非常沮丧。” 这样的回答对于人类来说很容易理解,但对于应用程序的代码而言,却充满挑战。代码需要从这段文本中费力地找出产品名称(“MegaWidget 3000”),判断情绪(“不满意” 还是 “沮丧” 才是主要情绪呢?),甚至可能还需要提取客户名称(如果有需要的话)。而且,如果模型下次使用了不同的表述,比如用 “感到失望” 代替 “不满意”,那么用于解析情绪的代码可能就会出错。
相比之下,结构化的输出则能避免这些问题。例如:
review_analysis:
product_name: MegaWidget 3000
sentiment: Negative
summary: 客户报告产品在两天后损坏,导致沮丧情绪。
这种结构化的格式将混乱的对话式文本转化为干净、有序的数据,使得代码能够可靠地处理。
二、获取结构化输出的方法
通常,获取大语言模型结构化输出主要有两种途径:原生模型功能和提示工程。

原生模型功能
随着人工智能模型的不断发展,一些模型增加了内置的请求结构化数据的方法。例如,谷歌的Gemini模型通常可以直接使用函数描述或特定的模式,有时还能与Pydantic等工具集成;OpenAI的模型(如GPT-4)提供了 “JSON模式” 或 “函数调用” 等功能,旨在将输出强制转换为特定的JSON结构。然而,这些原生功能虽然强大,但往往依赖于特定的模型。不同模型请求结构化数据的方式可能不同,这可能会使开发者锁定在特定的供应商,并且可能需要学习其特定的API或库,增加了使用的复杂性。
提示工程
与依赖原生模型功能不同,提示工程是一种更为通用的方法。它的核心思想非常简单,就是在向模型发出的指令(即提示)中,直接明确地告诉模型你期望的输出格式。这种方法不依赖于特定的模型,几乎适用于大多数大语言模型。它充分利用了模型理解和遵循指令的核心能力,让开发者能够灵活地定义所需的结构,例如请求YAML或特定的JSON格式。此外,通过提示工程获取结构化输出,不需要预先学习复杂的模型特定API,降低了使用门槛,对于初学者来说是一种更为友好的选择。接下来,我们将重点介绍基于提示工程的三个必备技巧。
三、技巧一:使用YAML而非仅依赖JSON
在请求大语言模型进行结构化输出时,选择合适的格式至关重要。JSON(Ja vaScript Object Notation)在Web开发和API中应用广泛,但它对于大语言模型来说,有时可能会带来一些问题,特别是在处理包含引号或多行文本的情况时。
JSON有严格的规则,字符串必须用双引号(")括起来。如果文本本身包含双引号,则必须使用反斜杠(\)进行转义,例如\"。同样,字符串中的换行符需要表示为\n。大语言模型在处理这些规则时常常会遇到困难,这主要源于它们处理文本的方式——分词(tokenization)。大语言模型会将文本分解为较小的部分(词元),这些词元可能是完整的单词、单词的一部分,或者是单个字符/符号。在分词过程中,像\这样的转义字符或\n这样的格式标记有时会被不恰当地拆分,模型也可能难以在其庞大的训练数据中学习何时以及如何正确应用这些复杂的上下文规则。由于这种底层的分词机制,大语言模型在一致地处理转义字符方面表现不佳。
例如,当要求模型提取一段对话 “Alice said: “Hello Bob. How are you?”” 并以JSON格式输出时,正确的格式应该是{"dialogue": "Alice said: \"Hello Bob.\nHow are you?\""}。但由于分词的挑战,要让模型每次都准确无误地生成这样的格式并不容易,很容易出现错误。
相比之下,YAML(YAML Ain’t Markup Language)在处理这类情况时具有明显的优势。YAML旨在更易于人类阅读,并且对于字符串,特别是多行字符串,具有更灵活的规则,这使得它对转义和格式错误的敏感度较低。同样以提取上述对话为例,用YAML格式表示则更加简洁:
speaker: Alice
dialogue: |
Alice said: "Hello Bob.
How are you?"
在这个例子中,不需要对引号进行转义,换行也显得很自然。这里使用了YAML的块标量风格(|)。
YAML提供了多种强大的处理多行字符串的方式。其中,字面量风格(|)会精确保留块中的换行符,源YAML中的每一行新行在结果字符串中都会变成一个换行符(\n)。例如:
literal_style: |
Line 1
Line 2
Line 4
其结果字符串为:"Line 1\nLine 2\n\nLine 4\n"(注意其中的双换行和最后的换行)。
折叠风格(>)则会将块内的大多数换行符折叠为空格,将文本视为一长行,仅为了可读性而进行换行。它会保留空白行(这些空白行在结果字符串中会变成\n)。例如:
folded_style: >
This is actually
just one long sentence,
folded for readability.
This starts a new paragraph.
其结果字符串为:"This is actually just one long sentence, folded for readability.\nThis starts a new paragraph.\n"(注意其中的空格和单个\n)。
此外,还可以通过添加 “咬尾指示符”(+、-)来进一步控制块标量末尾的换行符处理方式。默认情况下(无指示符,即|或>),如果有一个尾随换行符,会保留它,但会删除任何额外的尾随换行符;使用|+或>+表示保留所有尾随换行符;使用|-或>-则表示删除所有尾随换行符,包括最后一个(如果存在)。
在实际应用中,当提示大语言模型进行结构化输出,且输出内容可能包含复杂字符串时,我们应该明确要求使用YAML格式,并将输出包含在yaml …块中。如果可能出现多行文本且格式很重要,还应在提示中指定多行风格(|或>)。一般情况下,除非有特定需求,否则不需要指定咬尾指示符。同时,即使YAML具有灵活性,也一定要在代码中对输出进行解析和验证,可以使用断言(assert)或其他模式检查方法,以确保数据的准确性和可用性。通过使用YAML,尤其是其多行处理能力,可以显著减少因JSON的严格规则和大语言模型的分词挑战而导致的格式错误。
