Instructor 高级验证模式基于 Pydantic 构建可靠的 LLM 结构化输出校验体系【免费下载链接】AI-Research-SKILLsComprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full horsepower. Maintained by Orchestra Research.项目地址: https://gitcode.com/gh_mirrors/ai/AI-Research-SKILLs本篇技术指南以 Instructor 技能库 中的 validation.md 为骨架系统讲解如何利用 Pydantic 为 LLM 结构化输出构建从字段级到模型级的完整校验体系。你将掌握内置约束、自定义校验器、跨字段校验、复杂嵌套模式、错误处理与优雅降级等实战方案并能将校验失败与 Instructor 的自动重试机制结合让 Agent 输出的数据达到生产级可靠性。Instructor 中的校验为什么结构化输出需要双重保险Instructor 的核心价值在于把 LLM 的自由文本输出转换为符合 Pydantic 模式response_model的强类型结构化数据。而**校验Validation**正是这条链路上的关键一环——模型生成的 JSON 先经过 Pydantic 模式解析再经过校验器逐字段把关任何不满足约束的字段都会触发错误反馈并驱动重试。从 技能路由表 可以看到结构化 LLM 输出Structured LLM output这类任务被路由到16-prompt-engineering/instructor/技能。在该技能的 SKILL.md 中校验流程被描述为五步闭环LLM 生成输出Pydantic 执行校验若校验失败把错误信息回传给 LLMLLM 带着错误反馈重新生成重复以上过程直到成功或达到max_retries默认 3 次。这意味着校验器抛出的每条ValueError信息最终都会被 LLM 看到并作为修正依据。因此校验器写得越具体、错误信息越有指导性重试的成功率就越高。这正是本文所有模式的核心设计思想。一、内置校验器零代码的约束声明Pydantic 的Field提供了丰富的内置约束可直接声明在字段上无需编写任何校验函数。这是最轻量、最常用的校验手段。数值约束Numeric Constraintsfrom pydantic import BaseModel, Field class Product(BaseModel): price: float Field(gt0, descriptionPrice must be positive) discount: float Field(ge0, le100, descriptionDiscount 0-100%) quantity: int Field(ge1, descriptionAt least 1 item) rating: float Field(ge0.0, le5.0, descriptionRating 0-5 stars) # If LLM provides invalid values, automatic retry with error feedback可用约束一览约束含义示例gt大于Greater thanField(gt0)要求值 0ge大于等于Field(ge1)要求值 ≥ 1lt小于Field(lt100)要求值 100le小于等于Field(le5.0)要求值 ≤ 5.0multiple_of必须是该数的倍数Field(multiple_of5)要求值为 5 的倍数实战要点description并非校验逻辑但它会被注入到 LLM 的提示/工具模式中指导模型生成符合语义的值因此建议始终为受约束字段补充 description。字符串约束String Constraintsclass User(BaseModel): username: str Field( min_length3, max_length20, patternr^[a-zA-Z0-9_]$, description3-20 alphanumeric characters ) bio: str Field(max_length500, descriptionBio up to 500 chars) status: str Field(patternr^(active|inactive|pending)$) # pattern validates against regexmin_length/max_length限定长度pattern用正则表达式约束字符集。当业务上有明确的枚举或格式要求时正则约束比单纯长度限制更能显著提升输出质量——例如用户名只允许字母、数字、下划线。邮箱与 URL 校验Email and URL Validationfrom pydantic import EmailStr, HttpUrl, AnyUrl class Contact(BaseModel): email: EmailStr # Validates email format website: HttpUrl # Validates HTTP/HTTPS URLs portfolio: AnyUrl # Any valid URL scheme contact client.messages.create( modelclaude-sonnet-4-5-20250929, max_tokens1024, messages[{ role: user, content: Extract: johnexample.com, https://example.com }], response_modelContact )三种类型按严格程度区分EmailStr校验邮箱格式HttpUrl只接受 http/https 协议的合法 URLAnyUrl接受任意 URL scheme如ftp://、file://。这里的client是通过instructor.from_anthropic(Anthropic())或instructor.from_openai(OpenAI())创建的 Instructor 客户端详见 providers.md。日期与时间校验Date and DateTime Validationfrom datetime import date, datetime from pydantic import Field, field_validator class Event(BaseModel): event_date: date # Validates date format created_at: datetime # Validates datetime format year: int Field(ge1900, le2100) field_validator(event_date) def future_date(cls, v): Ensure event is in the future. if v date.today(): raise ValueError(Event must be in the future) return vdate/datetime类型本身就会校验格式而field_validator允许在此基础上叠加业务规则例如活动日期必须在未来。这是从格式校验走向语义校验的第一步。列表与字典校验List and Dict Validationclass Document(BaseModel): tags: list[str] Field(min_length1, max_length10) keywords: list[str] Field(min_length3, descriptionAt least 3 keywords) metadata: dict[str, str] Field(descriptionString key-value pairs) field_validator(tags) def unique_tags(cls, v): Ensure tags are unique. if len(v) ! len(set(v)): raise ValueError(Tags must be unique) return v对list[str]min_length/max_length约束元素个数dict[str, str]则约束键值均为字符串。上面的unique_tags展示了内置约束 自定义校验器的组合用法内置约束管数量校验器管唯一性。二、自定义字段校验器把业务规则写进模式当内置约束不够用时用field_validator编写自定义校验逻辑。校验器返回的值会替换原值因此可以用来清洗、规范化数据抛出的ValueError则会进入 Instructor 的错误反馈回路。基础字段校验器from pydantic import field_validator class Person(BaseModel): name: str age: int field_validator(name) def name_must_not_be_empty(cls, v): Validate name is not empty or just whitespace. if not v or not v.strip(): raise ValueError(Name cannot be empty) return v.strip() field_validator(age) def age_must_be_reasonable(cls, v): Validate age is between 0 and 120. if v 0 or v 120: raise ValueError(Age must be between 0 and 120) return v注意name_must_not_be_empty返回v.strip()——校验的同时完成了去空白。校验器签名需接收cls和待校验值v。带字段上下文的校验器Validator with Field Infofrom pydantic import ValidationInfo class Article(BaseModel): title: str content: str field_validator(content) def content_length(cls, v, info: ValidationInfo): Validate content is longer than title. if title in info.data: title_len len(info.data[title]) if len(v) title_len * 2: raise ValueError(Content should be at least 2x title length) return v通过info: ValidationInfo参数可以访问info.data——即已通过校验的其他字段值。这使得单个字段的校验可以依赖其他字段实现字段间的条件约束如正文长度至少是标题的两倍。注意info.data只包含当前字段之前已完成校验的字段因此访问前要先用in判断键是否存在。多字段联合校验Multiple Fields Validationclass TimeRange(BaseModel): start_time: str end_time: str field_validator(start_time, end_time) def valid_time_format(cls, v): Validate both times are in HH:MM format. import re if not re.match(r^\d{2}:\d{2}$, v): raise ValueError(Time must be in HH:MM format) return v一个校验器可以同时绑定多个字段传入字段名字符串列表对每个字段独立执行同一套规则——适合为多个字段共享同一格式约束的场景。校验即转换Transform and Validateclass URL(BaseModel): url: str field_validator(url) def normalize_url(cls, v): Add https:// if missing. if not v.startswith((http://, https://)): v fhttps://{v} return v宽松接收、统一规范化是处理 LLM 输出的高频策略LLM 可能返回example.com或www.example.com校验器负责补全协议前缀让下游系统拿到统一格式。这类归一化校验器在真实项目中价值极高。三、模型级校验跨字段的业务一致性单字段校验器无法处理多个字段之间的逻辑关系如end_date必须晚于start_date。此时使用model_validator。跨字段校验Cross-Field Validationfrom pydantic import model_validator class DateRange(BaseModel): start_date: str end_date: str model_validator(modeafter) def check_dates(self): Ensure end_date is after start_date. from datetime import datetime start datetime.strptime(self.start_date, %Y-%m-%d) end datetime.strptime(self.end_date, %Y-%m-%d) if end start: raise ValueError(end_date must be after start_date) return self class PriceRange(BaseModel): min_price: float max_price: float model_validator(modeafter) def check_price_range(self): Ensure max min. if self.max_price self.min_price: raise ValueError(max_price must be greater than min_price) return selfmodeafter表示在所有字段完成解析和字段级校验之后再执行此时可通过self访问完整的字段值。务必return self否则模型实例会被替换为返回值。条件校验Conditional Validationclass Order(BaseModel): order_type: str # standard or express delivery_date: str delivery_time: Optional[str] None model_validator(modeafter) def check_delivery_time(self): Express orders need delivery time. if self.order_type express and not self.delivery_time: raise ValueError(Express orders require delivery_time) return self条件校验实现字段是否必填取决于另一字段的值当order_type express时delivery_time变为必填。注意本例使用Optional[str]需要先from typing import Optional。复杂业务逻辑Complex Business Logicclass Discount(BaseModel): code: str percentage: float Field(ge0, le100) min_purchase: float Field(ge0) max_discount: float Field(ge0) model_validator(modeafter) def validate_discount(self): Ensure discount logic is sound. # Max discount cant exceed percentage of min_purchase theoretical_max (self.percentage / 100) * self.min_purchase if self.max_discount theoretical_max: self.max_discount theoretical_max return self模型级校验器不仅拒绝错误数据还可以主动修正当max_discount超出理论上限时直接把它钳制到合法值。这种校验 修复的模式非常适合折扣、配额、预算等数值联动业务。四、复杂校验模式嵌套、列表与联合类型真实提取任务的数据结构往往不是扁平的Pydantic 对嵌套结构同样提供完整校验支持且嵌套校验自动递归执行。嵌套模型校验Nested Model Validationclass Address(BaseModel): street: str city: str country: str postal_code: str field_validator(postal_code) def validate_postal_code(cls, v, info: ValidationInfo): Validate postal code format based on country. if country in info.data: country info.data[country] if country USA: import re if not re.match(r^\d{5}(-\d{4})?$, v): raise ValueError(Invalid US postal code) elif country Canada: if not re.match(r^[A-Z]\d[A-Z] \d[A-Z]\d$, v): raise ValueError(Invalid Canadian postal code) return v class Person(BaseModel): name: str address: Address # Nested validation runs automatically子模型Address内部的校验器会在父模型Person解析时自动执行同时info.data允许引用同层已校验字段如country实现根据国家校验邮编格式的上下文敏感逻辑。模型列表校验List of Modelsclass Task(BaseModel): title: str Field(min_length1) priority: int Field(ge1, le5) class Project(BaseModel): name: str tasks: list[Task] Field(min_length1, descriptionAt least 1 task) field_validator(tasks) def at_least_one_high_priority(cls, v): Ensure at least one task has priority 4. if not any(task.priority 4 for task in v): raise ValueError(Project needs at least one high-priority task) return vlist[Task]会让每个元素都经历Task的字段校验父级校验器再对整个列表施加聚合约束如至少一个高优先级任务。注意field_validator处理的是整个列表对象可以直接对列表做any()/all()判断。联合类型校验Union Type Validationfrom typing import Union from pydantic import HttpUrl class TextBlock(BaseModel): type: str text content: str Field(min_length1) class ImageBlock(BaseModel): type: str image url: HttpUrl alt_text: str class Page(BaseModel): title: str blocks: list[Union[TextBlock, ImageBlock]] field_validator(blocks) def validate_block_types(cls, v): Ensure first block is TextBlock. if v and not isinstance(v[0], TextBlock): raise ValueError(First block must be text) return vUnion[TextBlock, ImageBlock]让 LLM 根据内容自主选择块类型文本块或图片块Pydantic 会尝试按顺序匹配。联合类型结合列表即可表达页面由多种类型的块混合组成这类灵活结构。同样地联合类型在 SKILL.md 的 Union Types 一节 中也有对应演示。依赖字段Dependent Fieldsclass Subscription(BaseModel): plan: str # free, pro, enterprise max_users: int features: list[str] model_validator(modeafter) def validate_plan_limits(self): Enforce plan-specific limits. limits { free: {max_users: 1, required_features: [basic]}, pro: {max_users: 10, required_features: [basic, advanced]}, enterprise: {max_users: 999, required_features: [basic, advanced, premium]} } if self.plan in limits: limit limits[self.plan] if self.max_users limit[max_users]: raise ValueError(f{self.plan} plan limited to {limit[max_users]} users) for feature in limit[required_features]: if feature not in self.features: raise ValueError(f{self.plan} plan requires {feature} feature) return self把业务规则表各套餐的用户上限、必备功能内嵌到校验器中模型级校验器一次性完成套餐-人数-功能三者的联动校验。这是把领域规则显式化的典型做法规则集中、可读性强、可维护。五、错误处理让校验失败变得可控校验失败是 LLM 结构化输出的常态关键是设计好降级路径。Instructor 会把校验错误回传给模型重试但重试耗尽后抛出的ValidationError需要应用层妥善处理。优雅降级Graceful Degradationclass OptionalExtraction(BaseModel): # Required fields title: str # Optional fields with defaults author: Optional[str] None date: Optional[str] None tags: list[str] Field(default_factorylist) # LLM can succeed even if it cant extract everything通过Optional和默认值把非核心字段标记为可缺失——LLM 即使无法提取作者、日期、标签也能成功返回title。这极大降低了整次提取失败的概率是容错的第一道防线。default_factorylist保证每次实例化都获得独立的空列表避免可变默认值陷阱。部分校验与回退Partial Validationfrom pydantic import ValidationError def extract_with_fallback(text: str): Try full extraction, fall back to partial. try: # Try full extraction return client.messages.create( modelclaude-sonnet-4-5-20250929, max_tokens1024, messages[{role: user, content: text}], response_modelFullModel ) except ValidationError: # Fall back to partial model return client.messages.create( modelclaude-sonnet-4-5-20250929, max_tokens1024, messages[{role: user, content: text}], response_modelPartialModel )两阶段模式先尝试字段齐全的FullModel失败后回退到字段宽松的PartialModel。这是完整优先、部分兜底的经典降级策略适合对完整性要求高、但又不能完全丢弃结果的场景。校验错误检视Validation Error Inspectionfrom pydantic import ValidationError try: result client.messages.create( modelclaude-sonnet-4-5-20250929, max_tokens1024, messages[...], response_modelMyModel, max_retries3 ) except ValidationError as e: # Inspect specific errors for error in e.errors(): field error[loc][0] message error[msg] print(fField {field} failed: {message}) # Custom handling per field if field email: # Handle email validation failure passmax_retries控制重试次数默认 3。重试耗尽后抛出的ValidationError可通过e.errors()逐条检视每条错误包含loc字段路径、msg错误消息等信息可按字段实施差异化处理如日志告警、字段补全、人工介入。相关错误处理范式同样见于 SKILL.md 的 Error Handling 一节。自定义错误消息Custom Error Messagesclass DetailedModel(BaseModel): name: str Field( min_length2, max_length100, descriptionName between 2-100 characters ) age: int Field( ge0, le120, descriptionAge between 0 and 120 years ) field_validator(name) def validate_name(cls, v): Provide helpful error message. if not v.strip(): raise ValueError( Name cannot be empty. Please provide a valid name from the text. ) return v # When validation fails, LLM sees these helpful messages错误消息会作为重试反馈喂给 LLM因此消息质量直接决定重试成功率。Name cannot be empty. Please provide a valid name from the text. 这种带指引的消息比笼统的 invalid value 有效得多。六、校验最佳实践1. 约束要具体Be Specific# ❌ Bad: Vague validation class Item(BaseModel): name: str # ✅ Good: Specific constraints class Item(BaseModel): name: str Field( min_length1, max_length200, descriptionItem name, 1-200 characters )泛泛的str对 LLM 几乎没有约束力给出长度边界和语义描述模型才能生成符合预期的值。2. 提供纠错上下文Provide Context# ✅ Good: Explain why validation failed field_validator(price) def validate_price(cls, v): if v 0: raise ValueError( Price must be positive. Extract numeric price from text without currency symbols. ) return v错误信息中说明失败原因 如何修正比如不要带货币符号模型重试时就能精准修正——这是 SKILL.md 中automatic retry with error feedback机制发挥效力的关键。3. 固定取值集合用枚举Use Enums for Fixed Sets# ❌ Bad: String validation status: str field_validator(status) def validate_status(cls, v): if v not in [active, inactive, pending]: raise ValueError(Invalid status) return v # ✅ Good: Enum class Status(str, Enum): ACTIVE active INACTIVE inactive PENDING pending status: Status # Validation automatic对于固定取值集合str枚举继承str的Enum比手写字符串校验更简洁可靠校验自动完成且枚举值会以合法选项的形式被注入到 LLM 提示中。SKILL.md 中的Sentiment枚举、分类模式均采用了这一做法。4. 严格与灵活要平衡Balance Strictness# Too strict: May fail unnecessarily class StrictModel(BaseModel): date: str Field(patternr^\d{4}-\d{2}-\d{2}$) # Fails if LLM uses 2024-1-5 instead of 2024-01-05 # Better: Normalize in validator class FlexibleModel(BaseModel): date: str field_validator(date) def normalize_date(cls, v): from datetime import datetime # Parse flexible formats for fmt in [%Y-%m-%d, %Y/%m/%d, %m/%d/%Y]: try: dt datetime.strptime(v, fmt) return dt.strftime(%Y-%m-%d) # Normalize except ValueError: continue raise ValueError(Invalid date format)过严的正则会让 LLM 在日期格式如2024-1-5vs2024-01-05上频繁失败。更优做法是宽松接收 校验器归一化用多个格式依次尝试解析统一输出YYYY-MM-DD。结合前面Transform and Validate的例子可以看到归一化是处理 LLM 输出变体的通用武器。5. 校验器要测试Test Validation# Test your validators with edge cases def test_validation(): # Should succeed valid MyModel(fieldvalid_value) # Should fail try: invalid MyModel(fieldinvalid) assert False, Should have raised ValidationError except ValidationError: pass # Expected # Run tests before using in productionPydantic 校验器是纯 Python 逻辑可以脱离 LLM 单独单元测试。把边界值空串、超长、非法格式固化成测试用例在上线前验证校验行为避免把缺陷带到生产环境。七、高级技巧条件必填、外部数据与渐进式校验条件必填字段Conditional Required Fieldsfrom typing import Optional class ConditionalModel(BaseModel): type: str detail_a: Optional[str] None detail_b: Optional[str] None model_validator(modeafter) def check_required_details(self): Require different fields based on type. if self.type type_a and not self.detail_a: raise ValueError(type_a requires detail_a) if self.type type_b and not self.detail_b: raise ValueError(type_b requires detail_b) return self通过Optional让字段表面可选再在模型级校验器中按type强制特定字段必填——实现字段是否必填取决于类型的动态规则。对接外部数据Validation with External Dataclass Product(BaseModel): sku: str name: str field_validator(sku) def validate_sku(cls, v): Check SKU exists in database. # Query database or API if not database.sku_exists(v): raise ValueError(fSKU {v} not found in catalog) return v校验器不限于纯逻辑还可以查询数据库、调用内部 API、比对词典。这意味着 LLM 提取出的实体如 SKU、用户 ID可以在进入下游系统前就完成存在性校验把无效引用拦截在源头。渐进式校验Progressive Validation# Start with loose validation class Stage1(BaseModel): data: str # Any string # Then strict validation class Stage2(BaseModel): data: str Field(patternr^[A-Z]{3}-\d{6}$) # Use Stage1 for initial extraction # Use Stage2 for final validation先松后严的流水线第一阶段用宽松模型完成初步提取保证成功率第二阶段用严格模型做最终校验保证规范性。这适合宁可有中间产物、不可有最终坏数据的管道式处理场景。八、与重试、Provider 及技能库的协同校验并非孤立环节它和 Instructor 的其余能力深度耦合重试闭环校验器抛出的每条ValueError都是重试的燃料。配合max_retries参数默认 3系统自动完成生成 → 校验 → 反馈 → 再生成循环参见 SKILL.md 的 Automatic Retrying 一节。Provider 适配校验器与模型无关但不同 Provider 的接入方式不同——Anthropic 用client.messages.createOpenAI 用client.chat.completions.create本地 Ollama 模型则需modeinstructor.Mode.JSON等回退模式。具体配置见 providers.md。实战组合examples.md 中的信息抽取、分类、多实体提取、批量处理、流式输出等模式均可无缝叠加本文的校验器。例如分类任务中confidence: float Field(ge0.0, le1.0)这类约束正是把校验思想融入日常模式的标准写法。技能定位在 AI 研究 Agent 的技能路由体系中结构化 LLM 输出被统一路由至本技能见 skill-routing.md。这意味着本文介绍的校验模式是 Agent 自动化研究中可靠提取这一环节的通用底座。一个完整的生产级校验设计应当是内置约束声明边界数值/长度/正则/类型→ 字段校验器做归一化与单字段语义检查 → 模型校验器做跨字段业务一致性检查 → 用清晰错误消息驱动自动重试 → 重试耗尽后用可选字段 回退模型优雅降级 → 上线前用单元测试覆盖边界场景。这样构建出的结构化输出管线才能既保证 LLM 的生成灵活性又守住下游系统对数据质量的底线。延伸阅读验证模式参考本文原始素材、实战示例、Provider 配置、Instructor 技能入口。【免费下载链接】AI-Research-SKILLsComprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full horsepower. Maintained by Orchestra Research.项目地址: https://gitcode.com/gh_mirrors/ai/AI-Research-SKILLs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
