资讯动态

使用 Instructor 与 Pydantic 规范化 LLM 时间戳输出:让 HH:MM:SS 与 MM:SS 格式不再引发解析事故

发布时间:2026/9/14 22:20:08 来源:尧图企业网站定制
使用 Instructor 与 Pydantic 规范化 LLM 时间戳输出让 HH:MM:SS 与 MM:SS 格式不再引发解析事故【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor导读在视频内容处理场景中语言模型生成的时间戳往往格式不一短视频倾向于输出MM:SS长视频则更常输出HH:MM:SS甚至会出现2:00这类歧义表达。本指南基于 instructor 仓库中 timestamp 博客原文 与配套示例 examples/timestamps/run.py讲解如何用 Pydantic 的数据验证能力配合自定义解析逻辑将任意格式的时间戳统一规范化为HH:MM:SS读完即可在真实视频/音频剪辑工作流中落地可复用的时间戳处理方案。问题背景语言模型输出的时间戳为什么不可信语言模型完全有能力理解时间戳但它们在输出时并不总是保持一致。以视频分段segment抽取为例一段 30 秒的短视频模型大概率给出00:30这样的MM:SS格式而一部 90 分钟的长片模型又倾向于给出01:30:00这样的HH:MM:SS格式。两种格式混在一起直接喂给下游解析器轻则报错重则把时间计算得完全错误。仓库中的朴素示例清晰地展示了这种困境见 examples/timestamps/run.py 的注释class Segment(BaseModel): title: str Field(..., descriptionThe title of the segment) timestamp: str Field(..., descriptionThe timestamp of the event as HH:MM:SS)这个模型的问题在于提示词与模型行为的偏差即使你在字段描述里写明必须是 HH:MM:SS短视频场景下模型仍倾向于输出MM:SS。正如示例注释中所言语言模型并不擅长输出00:MM:SS这种带前导零的完整形式。歧义无法消解2:00到底该解释为 2 分钟还是 2 小时仅凭一个字符串字段无法判断模型没有义务替你消除这个歧义。格式约束形同虚设1:30:00明明是一个合法的时间却不符合预设的HH:MM:SS描述导致合法数据被判为非法。解决方案显式声明格式 自定义解析器 统一规范化针对上述问题原文给出的解法分三步显式声明时间格式让模型在返回时间戳的同时声明它使用的是哪一种格式HH:MM:SS还是MM:SS把歧义从猜测变成声明使用自定义验证器解析用 Pydantic 的model_validator在模型实例化后立即根据声明的格式做解析统一规范化输出无论输入是哪种格式最终都归一化为HH:MM:SS保证下游系统拿到的是唯一确定的格式。改进后的实现与 examples/timestamps/run.py 完全一致from pydantic import BaseModel, Field, model_validator from typing import Literal class SegmentWithTimestamp(BaseModel): title: str Field(..., descriptionThe title of the segment) time_format: Literal[HH:MM:SS, MM:SS] Field( ..., descriptionThe format of the timestamp ) timestamp: str Field( ..., descriptionThe timestamp of the event as either HH:MM:SS or MM:SS ) model_validator(modeafter) def parse_timestamp(self): if self.time_format HH:MM:SS: hours, minutes, seconds map(int, self.timestamp.split(:)) elif self.time_format MM:SS: hours, minutes, seconds 0, *map(int, self.timestamp.split(:)) else: raise ValueError(Invalid time format, must be HH:MM:SS or MM:SS) # Normalize seconds and minutes total_seconds hours * 3600 minutes * 60 seconds hours, remainder divmod(total_seconds, 3600) minutes, seconds divmod(remainder, 60) if hours 0: self.timestamp f{hours:02d}:{minutes:02d}:{seconds:02d} else: self.timestamp f00:{minutes:02d}:{seconds:02d} return self关键设计点拆解time_format: Literal[HH:MM:SS, MM:SS]这是一个枚举约束字段。Literal让 Pydantic 在验证阶段就能拒绝既不是HH:MM:SS也不是MM:SS的取值更重要的是它在发给模型的结构化输出 schema 中变成了一个明确的选择项迫使模型在生成时间戳之前先想清楚自己用哪种格式。这也是 Instructor 借助response_model把 schema 传给 LLM 时天然支持的约束能力相关机制可参考 docs/concepts/validation.md 中关于类型约束与字段约束的说明。model_validator(modeafter)after模式表示在字段全部完成基本验证之后运行此时可以安全地读取self.time_format与self.timestamp做跨字段的联动处理。这与 docs/learning/validation/field_level_validation.md 中当某个字段的有效性依赖于其他字段时使用model_validator的最佳实践完全对应。秒级归一化先把两个分支都换算成total_seconds再通过divmod反向拆出hours、minutes、seconds最后用:02d补零输出。这带来两个额外收益其一65:00MM:SS 形式下分钟数超过 59会被正确归一化为01:05:00而不是报错其二无论输入是0:30、00:30还是00:00:30最终都收敛为统一的00:00:30。非法格式抛错else分支抛出ValueError这实际上是给 Instructor 的重试机制max_retries对应 docs/concepts/reask_validation.md提供触发信号——验证失败的错误信息会被拼回对话历史让模型在下一次生成时自我纠正而不是让错误静默流入下游。为什么比约束采样或 JSON Schema 更好你可能会问为什么不直接用约束采样constrained sampling或 JSON Schema 一步到位原文给出了清晰的理由时间戳解析是上下文相关的处理超出简单模式匹配的能力范围。约束采样可以强制模型输出某个固定格式但它解决不了不同视频时长导致格式漂移的问题——它无法在MM:SS与HH:MM:SS之间做转换更谈不上归一化。你必须在生成之前就替模型选死一种格式而这恰恰是我们无法预先确定的。JSON Schema只能验证数据结构是否符合预期比如字段类型、枚举取值却无法执行把 65 分钟进位成 1 小时 5 分这种需要计算逻辑的解析与规范化。我们的方案把两者的长处拼在了一起用 Pydantic 的 schema 验证保证结构正确、用Literal把格式选择显式化再用自定义model_validator承担模式匹配之外的解析与换算工作。测试验证三个边界用例全部通过原文给出的测试用例覆盖了三种典型输入仓库示例 examples/timestamps/run.py 中可直接运行复现if __name__ __main__: # Test cases for SegmentWithTimestamp test_cases [ ( SegmentWithTimestamp( titleIntroduction, time_formatMM:SS, timestamp00:30 ), 00:00:30, ), ( SegmentWithTimestamp( titleMain Topic, time_formatHH:MM:SS, timestamp00:15:45 ), 00:15:45, ), ( SegmentWithTimestamp( titleConclusion, time_formatMM:SS, timestamp65:00 ), 01:05:00, ), ] for input_data, expected_output in test_cases: try: assert input_data.timestamp expected_output print(fTest passed: {input_data.timestamp} {expected_output}) except AssertionError: print(fTest failed: {input_data.timestamp} ! {expected_output}) # Output: # Test passed: 00:00:30 00:00:30 # Test passed: 00:15:45 00:15:45 # Test passed: 01:05:00 01:05:00三个用例分别验证了短格式补零00:30→00:00:30、标准长格式原样保持00:15:45→00:15:45、以及分钟进位65:00→01:05:00。其中第三个用例尤其关键——它证明了解析器不是简单的字符串格式检查而是真正在做时间计算把超出 59 的分钟数正确进位到小时位。与单一字段技巧的呼应从匹配语言到规范时间原文在开篇提到这个思路和 matching-language.md多语言摘要匹配一脉相承在模型中添加一个显式的元信息字段让 LLM 先声明某个属性再基于该属性做后续处理。matching-language 的做法是让模型先输出detected_language再据此保证摘要语言与原文一致本文则是让模型先声明time_format再据此解析时间戳。这背后是同一个设计哲学与其寄希望于模型恰好输出正确格式不如把格式的声明权交给模型、把格式的解释权留给代码。在 matching-language.md 中仅靠增加一个语言检测字段匹配正确率就从不稳定的状态提升到了全部通过与本例中靠time_format消除歧义的效果如出一辙。在实际 Instructor 工作流中的完整用法上面的模型定义只是验证层在实际项目中你会把这个模型作为response_model传给 Instructor 的客户端让 LLM 直接生成结构化输出import instructor import openai from pydantic import BaseModel, Field, model_validator from typing import Literal client instructor.from_openai(openai.OpenAI()) class SegmentWithTimestamp(BaseModel): title: str Field(..., descriptionThe title of the segment) time_format: Literal[HH:MM:SS, MM:SS] Field( ..., descriptionThe format of the timestamp ) timestamp: str Field( ..., descriptionThe timestamp of the event as either HH:MM:SS or MM:SS ) model_validator(modeafter) def parse_timestamp(self): if self.time_format HH:MM:SS: hours, minutes, seconds map(int, self.timestamp.split(:)) elif self.time_format MM:SS: hours, minutes, seconds 0, *map(int, self.timestamp.split(:)) else: raise ValueError(Invalid time format, must be HH:MM:SS or MM:SS) total_seconds hours * 3600 minutes * 60 seconds hours, remainder divmod(total_seconds, 3600) minutes, seconds divmod(remainder, 60) if hours 0: self.timestamp f{hours:02d}:{minutes:02d}:{seconds:02d} else: self.timestamp f00:{minutes:02d}:{seconds:02d} return self segments client.create( response_modellist[SegmentWithTimestamp], max_retries2, messages[ { role: user, content: 把这段视频分成 3 段标注每段的标题和起始时间戳, } ], ) for seg in segments: print(seg.title, seg.timestamp) # 所有 timestamp 均为 HH:MM:SS这里的max_retries2非常重要一旦 LLM 返回的timestamp无法解析例如声明了MM:SS却输出1:30:00或time_format取值非法Pydantic 验证失败后Instructor 会把错误信息追加到消息历史中重试底层逻辑见 docs/concepts/reask_validation.md 与重试相关实现 instructor/core/retry.py。也就是说model_validator里的ValueError不只是防御更是给重试机制的可读信号——这正是原文强调的这不是在强制语言模型而是在为下游系统构建有效输入。延伸结合流式输出与字幕时间轴如果你把时间戳与视频内容结合得更紧密仓库中还有现成的参考 examples/youtube-clips/run.py 演示了如何拉取 YouTube 字幕的start/end时间轴并用instructor.Partial做流式剪辑点提取。本文的时间戳规范化模型可以直接套用在类似的场景中——先让模型按段落声明time_format并给出时间戳再通过model_validator统一成HH:MM:SS后续无论是剪辑定位、字幕对齐还是时间计算都不必再为格式问题做防御式判断。结论语言模型输出中的时间戳格式不一致本质上是模型自说自话、下游各执一词的协议问题。解决它的关键不在于更花哨的提示词而在于把格式选择显式化用Literal字段让模型声明格式消除2:00这类歧义把解析归一化交给代码用model_validator做上下文相关的换算而不是依赖模式匹配让验证失败可重试通过 Instructor 的max_retries把ValueError转化为自我纠正的输入。这套声明 解析 归一化的组合拳不仅解决了时间戳问题也为所有格式多变、语义稳定的 NLP 任务提供了一套可复制的框架。面对 LLM 输出与其祈祷格式永远正确不如主动设计验证与归一化层来保证下游系统的输入一致性。【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价