跳到内容

验证与 Reasking

与其将 AI 中的“自我批评”或“自我反思”视为新概念,不如将它们视为带有清晰错误消息的验证错误,系统可以使用这些错误消息进行自我修正。

Pydantic

Pydantic 为 Python 提供了可定制且富有表现力的验证框架。Instructor 利用 Pydantic 的验证框架,为基于代码和基于 LLM 的验证提供统一的开发者体验,并提供基于验证错误纠正 LLM 输出的重问(reasking)机制。要了解更多信息,请查看关于验证器的 Pydantic 文档

好的 LLM 验证就是好的验证

如果您想查看更多关于验证器的示例,请查看我们的博客文章好的 LLM 验证就是好的验证

基于代码的验证示例

首先使用 typing_extensions 中的 Annotation 类定义一个带有验证器的 Pydantic 模型。

使用 Pydantic 的内置验证强制执行命名规则

from pydantic import BaseModel, ValidationError
from typing_extensions import Annotated
from pydantic import AfterValidator


def name_must_contain_space(v: str) -> str:
    if " " not in v:
        raise ValueError("Name must contain a space.")
    return v.lower()


class UserDetail(BaseModel):
    age: int
    name: Annotated[str, AfterValidator(name_must_contain_space)]


try:
    person = UserDetail(age=29, name="Jason")
except ValidationError as e:
    print(e)
    """
    1 validation error for UserDetail
    name
      Value error, Name must contain a space. [type=value_error, input_value='Jason', input_type=str]
        For further information visit https://errors.pydantic.dev/2.9/v/value_error
    """

基于代码的验证输出

1 validation error for UserDetail
name
   Value error, name must contain a space (type=value_error)

正如我们所见,当 name 属性不包含空格时,Pydantic 会引发验证错误。这是一个简单的示例,但它展示了如何使用 Pydantic 来验证模型的属性。

基于 LLM 的验证示例

基于 LLM 的验证也可以接入到同一个 Pydantic 模型中。在这里,如果 answer 属性包含违反“不要说令人反感的内容”规则的内容,Pydantic 将会引发验证错误。

import instructor
from openai import OpenAI
from instructor import llm_validator
from pydantic import BaseModel, ValidationError, BeforeValidator
from typing_extensions import Annotated


# Apply the patch to the OpenAI client
client = instructor.from_openai(OpenAI())


class QuestionAnswer(BaseModel):
    question: str
    answer: Annotated[
        str,
        BeforeValidator(llm_validator("don't say objectionable things", client=client)),
    ]


try:
    qa = QuestionAnswer(
        question="What is the meaning of life?",
        answer="The meaning of life is to be evil and steal",
    )
except ValidationError as e:
    print(e)
    """
    1 validation error for QuestionAnswer
    answer
      Assertion failed, The statement promotes objectionable behavior by encouraging evil and stealing. [type=assertion_error, input_value='The meaning of life is to be evil and steal', input_type=str]
        For further information visit https://errors.pydantic.dev/2.9/v/assertion_error
    """

基于 LLM 的验证输出

需要注意的是,这里的错误消息是由 LLM 而非代码生成的,因此对于重新向模型提问(re-asking)会很有帮助。

1 validation error for QuestionAnswer
answer
   Assertion failed, The statement is objectionable. (type=assertion_error)

使用 Reasking 逻辑纠正输出

验证器是确保输出某些属性的绝佳工具。当您使用 patch() 方法与 openai 客户端时,可以使用 max_retries 参数来设置重新向模型提问以纠正输出的次数。

它是抵御两种形式不良输出的绝佳防御层:

  1. Pydantic 验证错误(基于代码或基于 LLM)
  2. JSON 解码错误(当模型返回不良响应时)

步骤 1:使用验证器定义响应模型

请注意,字段验证器要求 name 为大写,但用户输入是小写。如果 name 不是大写,验证器将引发一个 ValueError

import openai
import instructor
from pydantic import BaseModel, field_validator

# Apply the patch to the OpenAI client
client = instructor.from_openai(openai.OpenAI())


class UserDetails(BaseModel):
    name: str
    age: int

    @field_validator("name")
    @classmethod
    def validate_name(cls, v):
        if v.upper() != v:
            raise ValueError("Name must be in uppercase.")
        return v

步骤 2:使用带有重试的客户端

在这里,UserDetails 模型作为 response_model 传入,并且 max_retries 设置为 2。

import instructor
import openai
from pydantic import BaseModel

client = instructor.from_openai(openai.OpenAI(), mode=instructor.Mode.TOOLS)


class UserDetails(BaseModel):
    name: str
    age: int


model = client.chat.completions.create(
    model="gpt-3.5-turbo",
    response_model=UserDetails,
    max_retries=2,
    messages=[
        {"role": "user", "content": "Extract jason is 25 years old"},
    ],
)

print(model.model_dump_json(indent=2))
"""
{
  "name": "Jason",
  "age": 25
}
"""

幕后发生了什么?

在幕后,instructor.from_openai() 方法将 max_retries 参数添加到 openai.ChatCompletion.create() 方法中。如果 UserDetails 中的 name 属性未能通过大写验证,max_retries 参数将触发最多 2 次重试。

from pydantic import ValidationError


try:
    ...
except ValidationError as e:
    kwargs["messages"].append(response.choices[0].message)
    kwargs["messages"].append(
        {
            "role": "user",
            "content": f"Please correct the function call; errors encountered:\n{e}",
        }
    )

高级验证技术

目前文档尚不完整,但我们有一些正在努力改进文档的高级验证技术,例如模型级验证和使用验证上下文。请查看我们关于验证引文的示例,其中涵盖了

  1. 验证包含所有属性的整个对象,而不是一次验证一个属性
  2. 使用一些“上下文”来验证对象:在此示例中,我们使用 context 来检查引文是否在原始文本中存在。

优化 Token 使用量

Pydantic 在抛出错误时会自动在错误消息中包含一个 URL,以便用户可以了解更多关于所抛出特定错误的信息。有些用户可能希望移除此 URL,因为它增加了额外的 Token,否则可能不会为验证过程带来太多价值。

我们创建了一个小的辅助函数,您可以在下方使用它来移除错误消息中的此 URL

from instructor.utils import disable_pydantic_error_url
from pydantic import BaseModel, ValidationError
from typing_extensions import Annotated
from pydantic import AfterValidator

disable_pydantic_error_url()  # (1)!


def name_must_contain_space(v: str) -> str:
    if " " not in v:
        raise ValueError("Name must contain a space.")
    return v.lower()


class UserDetail(BaseModel):
    age: int
    name: Annotated[str, AfterValidator(name_must_contain_space)]


try:
    person = UserDetail(age=29, name="Jason")
except ValidationError as e:
    print(e)
    """
    1 validation error for UserDetail
    name
      Value error, Name must contain a space. [type=value_error, input_value='Jason', input_type=str]
        For further information visit https://errors.pydantic.dev/2.9/v/value_error
    """
  1. 我们通过将环境变量 PYDANTIC_ERRORS_INCLUDE_URL 设置为 0 来禁用此错误。这仅在脚本执行期间有效,函数调用结束后,原始行为将恢复。

要点总结

通过整合这些高级验证技术,我们不仅提高了 LLM 生成内容的质量和可靠性,也为构建更自主、更有效的系统铺平了道路。