ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Instructor 中的 LLM 输出校验完整指南:从 Pydantic 静态规则到语义化动态校验

Instructor 中的 LLM 输出校验完整指南:从 Pydantic 静态规则到语义化动态校验 Instructor 中的 LLM 输出校验完整指南从 Pydantic 静态规则到语义化动态校验【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor如果你的校验逻辑能像人类一样学习和适应同时又以软件的速度运转会怎样这正是校验的未来而且它已经到来。本文以 Instructor 开源仓库中的官方博客 Good LLM Validation is Just Good Validation 为核心骨架系统讲解如何利用Pydantic与Instructor构建分层校验体系从基于黑名单的静态规则校验Software 1.0到由 LLM 驱动的语义校验Software 3.0再到思维链校验、引用原文校验最后通过max_retries重试机制让模型在报错信息驱动下自我纠错。读完本文你将掌握一套可直接落地的 LLM 输出可靠性工程方案并理解其背后的源码级实现原理。校验是可靠软件的基石校验是可靠软件的支柱。然而传统校验是静态的、基于规则的无法适应新出现的挑战。在 AI 系统中这个矛盾被放大了LLM 的输出具有概率性同一段提示词每次都可能返回不同的内容脏数据、幻觉、违规内容防不胜防。本文讨论的所有校验方案本质上都收敛到一个统一的结构——一个接收值、校验值、返回值或抛错的函数def validation_function(value): if condition(value): raise ValueError(Value is not valid) return mutation(value)这个结构贯穿全文无论是 Pydantic 的字段校验器、Annotated元数据校验还是Instructor内置的llm_validator底层都是这一函数形态。差异只在于condition与mutation由代码实现还是由 LLM 实现。什么是 Instructor结构化输出与校验的桥梁Instructor帮助你从 OpenAI 的 function call API 中拿到精确符合预期的响应类型。一旦你用Pydantic定义好期望的响应模型Instructor就接管了中间所有复杂逻辑——从响应的解析/校验到对无效响应的自动重试。这意味着校验器是免费获得的你写的 Pydantic 校验规则会自然作用于 LLM 输出关注点分离提示词prompt与调用 OpenAI 的代码各司其职。最小示例import instructor # pip install instructor from pydantic import BaseModel # 这一步启用了 client.create 中的 response_model 关键字 client instructor.from_provider(openai/gpt-5-nano) # (1)! class UserDetail(BaseModel): name: str age: int user: UserDetail client.create( modelgpt-5.4-mini, response_modelUserDetail, messages[ {role: user, content: Extract Jason is 25 years old}, ], max_retries3, # (2)! ) assert user.name Jason # (3)! assert user.age 25为简化与 OpenAI 模型的协作、从提示词中提取 Pydantic 对象from_provider提供了对 ChatCompletion 调用方式的补丁机制。在仓库中该工厂函数定义于 instructor/v2/auto_client.py接受provider/model-name形式的模型字符串并可传入async_client、cache、mode等参数。校验失败的无效响应会触发最多max_retries次重试具体机制见后文错误处理与 Re-asking一节。只要给 API 调用传入response_model参数返回的对象就一定是经过校验的Pydantic对象。提示上述代码中的模型名gpt-5-nano、gpt-5.4-mini为演示占位实际使用时请替换为你账号可用的模型标识。下面进入正题如何从静态、规则驱动的校验演进到动态、机器学习驱动的校验。我们用一个贯穿全篇的例子展开——假设你经营一家软件公司希望确保永远不会向用户呈现仇恨与种族歧视内容。这并不容易因为相关语言变化频繁、更新极快。Software 1.0Pydantic 中的规则校验最朴素的方法是维护一份与仇恨言论高度相关的黑名单词汇表。为简单起见假设我们从数据库中发现Steal和Rob是仇恨言论的良好预测词。于是把校验结构改造为当传入Lets rob the bank!或We should steal from the supermarkets这类字符串时抛出错误。Pydantic 提供两种实现方式field_validator装饰器或Annotated类型提示。方式一field_validator装饰器from pydantic import BaseModel, ValidationError, field_validator class UserMessage(BaseModel): message: str field_validator(message) def message_cannot_have_blacklisted_words(cls, v: str) - str: for word in v.split(): # (1)! if word.lower() in {rob, steal}: raise ValueError(f{word} was found in the message {v}) return v try: UserMessage(messageThis is a lovely day) UserMessage(messageWe should go and rob a bank) except ValidationError as e: print(e) 1 validation error for UserMessage message Value error, rob was found in the message We should go and rob a bank [typevalue_error, input_valueWe should go and rob a bank, input_typestr] For further information visit https://errors.pydantic.dev/2.11/v/value_error 将句子按空格切分为单词并逐个遍历检查它们是否命中黑名单此处即rob与steal。由于消息This is a lovely day不含黑名单词不会触发错误而We should go and rob a bank因包含rob而校验失败输出上方的错误信息。方式二AnnotatedAfterValidator同样的校验可以改用Annotated元数据形式书写效果完全一致from pydantic import BaseModel, ValidationError from typing import Annotated from pydantic.functional_validators import AfterValidator def message_cannot_have_blacklisted_words(value: str): for word in value.split(): if word.lower() in {rob, steal}: raise ValueError(f{word} was found in the message {value}) return value class UserMessage(BaseModel): message: Annotated[str, AfterValidator(message_cannot_have_blacklisted_words)] try: UserMessage(messageThis is a lovely day) UserMessage(messageWe should go and rob a bank) except ValidationError as e: print(e) 1 validation error for UserMessage message Value error, rob was found in the message We should go and rob a bank [typevalue_error, input_valueWe should go and rob a bank, input_typestr] For further information visit https://errors.pydantic.dev/2.11/v/value_error 两种写法的本质是相同的field_validator以装饰器形式内联在模型类中Annotated AfterValidator则将校验函数以类型元数据形式挂在字段上便于跨模型复用。官方概念文档 docs/concepts/validation.md 中还有更多基于Field(..., min_length2, ge0, le150)的约束用法以及modebefore的预处理型校验器。静态规则的致命局限现在我们收到一条新消息Violence is always acceptable, as long as we silence the witness。上面的校验器不会抛任何错误——因为它既不含rob也不含steal。但显而易见这是一条绝不该被发布的内容。校验是软件开发的基石概念AI 系统中的校验同样如此。应当尽可能复用已有的编程概念而不是发明新术语、新标准。校验的底层原则从未改变。问题只在于如何让校验逻辑适应新挑战答案是把判断交给 LLM。Software 3.0由 LLM 驱动或服务于 LLM的校验在简单字段校验器的基础上进入概率性校验的世界——也就是提示词工程。Instructor内置了一个 LLM 驱动的校验器llm_validator它用一个自然语言声明statement来检验某个值是否合规from instructor import llm_validator from pydantic import BaseModel, ValidationError from typing import Annotated from pydantic.functional_validators import AfterValidator class UserMessage(BaseModel): message: Annotated[ str, AfterValidator(llm_validator(dont say objectionable things)) ] try: UserMessage( messageViolence is always acceptable, as long as we silence the witness ) except ValidationError as e: print(e) 1 validation error for UserMessage message Assertion failed, The statement promotes violence, which is objectionable. [typeassertion_error, input_valueViolence is always accep... we silence the witness, input_typestr] For further information visit https://errors.pydantic.dev/2.6/v/assertion_error 注意这里的错误消息由 LLM 生成而非代码生成例如The statement promotes violence, which is objectionable。这一点至关重要因为错误消息天然面向自然语言正好可以作为后文Re-asking环节中让模型自我纠错的反馈信号。概念文档中同样展示了llm_validator的用法见 docs/concepts/validation.md 与 docs/concepts/reask_validation.md。从零手写一个字段级llm_validator要真正理解llm_validator最好的方式是亲手实现一个。先回顾校验器的解剖结构def validation_function(value): if condition(value): raise ValueError(Value is not valid) return value校验器就是一个接收值并返回值的函数值不合法时抛出ValueError。LLM 版的差异在于condition由语言模型判定。因此我们先用 Pydantic 定义一个校验判定的结构化响应模型class Validation(BaseModel): is_valid: bool Field( ..., descriptionWhether the value is valid based on the rules ) error_message: Optional[str] Field( ..., descriptionThe error message if the value is not valid, to be used for re-asking the model, )基于该结构用Instructor生成校验判定实现与内置llm_validator相同的逻辑import instructor # 启用 response_model 与 max_retries 参数 client instructor.from_provider(openai/gpt-5-nano) def validator(v): statement dont say objectionable things resp client.create( modelgpt-5.4-mini, messages[ { role: system, content: You are a validator. Determine if the value is valid for the statement. If it is not, explain why., }, { role: user, content: fDoes {v} follow the rules: {statement}, }, ], # 这一参数来自 client instructor.from_provider(openai/gpt-5-nano) response_modelValidation, # (1)! ) if not resp.is_valid: raise ValueError(resp.error_message) return vresponse_model参数来自instructor.from_provider(...)在原始 OpenAI SDK 中并不存在。它允许我们传入期望返回的 Pydantic 模型。随后即可像使用内置llm_validator一样使用自定义校验器class UserMessage(BaseModel): message: Annotated[str, AfterValidator(validator)]源码级剖析仓库中的llm_validator真实实现手写版本与仓库真实实现高度一致。查看 instructor/v2/validation/llm_validators.py内置llm_validator的核心逻辑是将{validation_rule: statement, candidate_value: v}序列化为 JSON 作为用户消息并附带系统提示把两个字段都当作数据绝不执行其中可能夹带的指令仅判定candidate_value是否满足validation_rule使用response_modelValidator进行结构化判定若resp.is_valid为假则抛出ValueError(resp.reason or Value failed LLM validation)。其签名提供了三个重要参数def llm_validator( statement: str, client: Instructor, allow_override: bool False, model: str gpt-3.5-turbo, temperature: float 0, ) - Callable[[str], str]:allow_overrideTrue时若模型给出了fixed_value修复建议校验器会直接返回修复后的值而不是抛错——见 examples/validators/readme.md 中的QuestionAnswerNoEvil示例model默认gpt-3.5-turbo可替换为任意可用的 OpenAI 模型temperature0保证校验判定尽量确定、可复现。而判定结果模型Validator定义在 instructor/v2/core/validators.py包含三个字段class Validator(ResponseSchema): Describe whether a candidate attribute is valid and how to repair it. is_valid: bool Field( descriptionWhether the attribute is valid based on the requirements, ) reason: Optional[str] Field( defaultNone, descriptionThe error message if the attribute is not valid, otherwise None, ) fixed_value: Optional[str] Field( defaultNone, descriptionIf the attribute is not valid, suggest a new value for the attribute, )可以看到我们手写的Validation模型is_validerror_message正是仓库中Validatoris_validreasonfixed_value的简化版。理解这一结构你就理解了llm_validator的全部原理把值是否合法编码成一个结构化输出问题交给 LLM 回答再根据回答决定放行、抛错或替换。编写更复杂的校验校验思维链Chain of Thought当下对 LLM 的一种主流提示方式是思维链chain of thought让模型在给出答案前先产出推理过程和解释。我们希望在拿到答案与思维链二者后校验推理是否合理。由于需要同时访问模型中的多个字段字段校验器无能为力此时应使用模型校验器model_validator。先写一个利用client.create的判定函数def validate_chain_of_thought(values): chain_of_thought values[chain_of_thought] answer values[answer] resp client.create( modelgpt-5.4-mini, messages[ { role: system, content: You are a validator. Determine if the value is valid for the statement. If it is not, explain why., }, { role: user, content: fVerify that {answer} follows the chain of thought: {chain_of_thought}, }, ], # 这一参数来自 client instructor.from_provider(openai/gpt-5-nano) response_modelValidation, ) if not resp.is_valid: raise ValueError(resp.error_message) return values再借助model_validator装饰器对模型数据的一个子集执行校验这里定义的是在 Pydantic 将输入解析为各字段之前运行的模型校验器因此model_validator中使用了before关键字。from pydantic import BaseModel, model_validator class AIResponse(BaseModel): chain_of_thought: str answer: str model_validator(modebefore) classmethod def chain_of_thought_makes_sense(cls, data: Any) - Any: # 由于使用 before 模式这里假设 data 是模型的字典表示 return validate_chain_of_thought(data)现在每当你创建AIResponse实例chain_of_thought_makes_sense校验器都会被调用try: resp AIResponse(chain_of_thought1 1 2, answerThe meaning of life is 42) except ValidationError as e: print(e)当答案与思维链不符时会得到如下错误1 validation error for AIResponse Value error, The statement The meaning of life is 42 does not follow the chain of thought: 1 1 2. [typevalue_error, input_value{chain_of_thought: 1 ... meaning of life is 42}, input_typedict]校验引用是否出自原文再看一个更具体的例子我们基于某段文本向模型提问希望校验生成的答案确实有原文支撑。这样既能最小化幻觉也能阻止未经原文支持的断言。人工逐条核对原文显然不可扩展更优雅的做法是让校验器自动完成。Pydantic 的model_validate函数允许我们向校验函数传递额外上下文让模型在校验时获得更多信息。这个上下文是一个普通 Python 字典可在校验函数的info参数中读取from pydantic import ValidationInfo, BaseModel, field_validator class AnswerWithCitation(BaseModel): answer: str citation: str field_validator(citation) classmethod def citation_exists(cls, v: str, info: ValidationInfo): # (1)! context info.context if context: context context.get(text_chunk) if v not in context: raise ValueError(fCitation {v} not found in text chunks) return vinfo对象对应下方model_validate中传入的context值。接着用原始示例测试新模型try: AnswerWithCitation.model_validate( {answer: Jason is a cool guy, citation: Jason is cool}, context{text_chunk: Jason is just a guy}, # (1)! ) except ValidationError as e: print(e)context就是普通 Python 字典可以承载任意值。由于Jason is cool并不存在于文本Jason is just a guy中会生成如下错误1 validation error for AnswerWithCitation citation Value error, Citation Jason is cool not found in text chunks [typevalue_error, input_valueJason is cool, input_typestr] For further information visit https://errors.pydantic.dev/2.4/v/value_error在 docs/concepts/reask_validation.md 的Using Context for Dynamic Validation一节中还有一个更完整的QuoteExtraction示例通过context把源文本传给校验器并用正则归一化空白后进行子串匹配配合max_retries2让模型在引用不命中时被重新询问。与client instructor.from_provider(...)结合把 context 传进 LLM 调用要让 LLM 生成的内容在创建时就携带外部上下文校验只需把context一并传入client.create。from_provider补丁后的客户端会把该context原样传递到校验器校验器通过info参数读取import instructor # 启用 response_model 与 max_retries 参数 client instructor.from_provider(openai/gpt-5-nano) def answer_question(question: str, text_chunk: str) - AnswerWithCitation: return client.create( modelgpt-5.4-mini, messages[ { role: user, content: fAnswer the question: {question} with the text chunk: {text_chunk}, }, ], response_modelAnswerWithCitation, context{text_chunk: text_chunk}, )从源码看context是client.create的正式签名参数见 instructor/v2/core/client.py默认值为None最终透传给底层create_fn中的validation_context进入各 provider 的响应解析器。这意味着你可以在任意一次结构化输出调用中注入运行时上下文源文档、允许值集合、外部引用等让校验器基于真实业务数据做判断。错误处理与 Re-asking让 LLM 自我纠错校验器通过抛错来保证输出满足某些性质在 AI 系统中我们可以利用这些错误让语言模型自我纠错。instructor.from_provider(...)不仅增加了response_model和context还允许使用max_retries参数指定自我纠错的尝试次数。这套机制为两类坏输出提供了防线Pydantic 校验错误代码实现或 LLM 实现的校验器抛出JSON 解码错误模型返回了无法解析的响应。定义带校验器的响应模型先定义一个返回UserModel的响应模型并添加字段校验器强制名字必须为大写from pydantic import BaseModel, field_validator class UserModel(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用max_retries触发自我纠错max_retries允许模型基于错误消息而非原始提示词自我纠错并重试model client.create( modelgpt-5.4-mini, messages[ {role: user, content: Extract jason is 25 years old}, ], # 由 client instructor.from_provider(openai/gpt-5-nano) 提供 response_modelUserModel, max_retries2, ) assert model.name JASON这个例子中代码里没有任何显式的大写转换逻辑但模型通过读取校验器的报错Name must be in uppercase成功修正了输出。源码级剖析重试循环究竟做了什么查看 instructor/v2/core/retry.py 中的retry_sync_v2重试循环的关键实现如下可重试异常集合_RETRYABLE_PARSE_ERRORS (ValidationError, json.JSONDecodeError, AsyncValidationError, ResponseParsingError)——恰好对应上文的两类坏输出Pydantic 校验错误与 JSON 解码错误及其异步/解析变体尝试次数stop_after_attempt(max(max_retries, 0) 1)即max_retries是初始尝试之外的重试次数总尝试数 max_retries 1若还传了timeout则与stop_after_delay(timeout)取并集Re-ask 动作每次解析失败后调用 provider 注册的reask_handler(kwargs, response, exception)修改kwargs——把上一轮的模型回复与请修正错误信息如下{e}追加进消息列表再进入下一轮尝试重试耗尽抛出InstructorRetryException携带last_completion、n_attempts、total_usage、failed_attempts等诊断信息。概念文档 docs/concepts/reask_validation.md 还展示了 re-ask 背后的消息构造细节出错后会把response.choices[0].message与一条Please correct the function call; errors encountered:\n{e}的用户消息依次追加到kwargs[messages]。这也解释了为什么llm_validator生成的自然语言错误消息如此重要——它就是模型下一次修正时读到的反馈指令。优化技巧去掉错误消息中的文档 URLPydantic 抛出错误时会在错误消息中自动附带一个文档 URL如https://errors.pydantic.dev/2.11/v/value_error方便开发者查阅。但在 Re-asking 场景中这个 URL 会被原样拼进重试消息、白白消耗 token且对模型几乎无用。仓库提供了disable_pydantic_error_url()辅助函数实现见 instructor/v2/core/utils.py其原理是临时替换ValidationError.__str__过滤掉含https://errors.pydantic.dev的行用法如下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(age29, nameJason) except ValidationError as e: print(e) 1 validation error for UserDetail name Value error, Name must contain a space. [typevalue_error, input_valueJason, input_typestr] 该函数通过设置环境变量PYDANTIC_ERRORS_INCLUDE_URL0生效仅在脚本执行期间有效不调用时恢复原始行为。最佳实践小结从 Pydantic 与 Instructor 的简洁 API到 LLM 的动态校验能力校验的图景正在变化——但不需要引入任何新概念自我批判、自我反思本质就是带着清晰错误消息的校验失败系统借此自我纠错。由简到繁先做基础的类型与约束校验Field的min_length、ge、le等再加规则型校验器最后才上 LLM 语义校验——语义校验每次都会产生 API 调用成本与延迟规则型 vs 语义型客观、可枚举的标准用代码校验如黑名单、大小写、数值范围主观、语义化、易变化的标准交给llm_validator如内容审核、语气、一致性错误消息即反馈让校验器产出面向自然语言、可解释的报错LLM 生成的报错尤其如此它们是 Re-asking 得以生效的前提善用 context把源文档、允许值等运行时数据通过context注入校验器可实现引用核验、动态规则等更贴近业务场景的校验控制重试成本合理设置max_retries配合disable_pydantic_error_url()精简重试消息中的冗余 token。更多进阶内容可继续阅读仓库中的相关文档核心校验概念、Re-ask 校验机制、语义化校验与结构化输出、为什么糟糕的 Schema 会破坏 LLM以及 Pydantic 仍然是你的全部所需。【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表