
使用 Instructor 与 Burr 构建 YouTube 闪卡生成应用从结构化输出到可观测的 LLM 工作流【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor本文基于 Instructor 项目的官方实战指南演示如何用instructor从 YouTube 字幕中可靠地提取结构化问答对flashcards再借助 Burr 将整个流程组织为易于理解和调试的 LLM 应用。读完本文你将掌握 Pydantic 响应模型的约束设计、create_iterable流式多对象提取以及用 Burr 的actions/transitions/State搭建带用户交互循环的完整应用并学会接入 Burr UI 实现遥测与数据标注。完整可运行代码见 examples/youtube-flashcards/run.py本文是对 docs/blog/posts/youtube-flashcards.md 的深度展开。为什么要用 Instructor Burr 的组合闪卡flashcards能帮我们把复杂主题拆解成可消化的小块无论是学生物、学外语还是背剧本台词都很有效。用 LLM 生成闪卡的难点有两个输出不可靠LLM 返回的是自由文本无法保证每次都能产出格式一致的题目、选项、答案索引和难度评分流程不可控从「用户输入 URL」到「获取字幕」再到「生成题目」多步骤串联后一旦出错难以定位。Instructor 解决前者——它用 Pydantic 模型约束 LLM 的输出结构Burr 解决后者——它用actions与transitions定义应用流程并自带 Burr UI 提供本地优先、免费开源的观测、标注与调试能力。本文是 Analyzing Youtube Transcripts with Instructor 的进阶篇上一篇展示了如何将字幕切分为章节本篇进一步把字幕转换为可用于自测的问答对并用 Burr 把脚本包装成真正的交互式应用。环境准备安装所需的依赖包pip install openai instructor pydantic youtube_transcript_api burr[start]其中burr[start]会一并安装 Burr 的可观测性相关组件。另外请确保已配置OPENAI_API_KEY环境变量或采用你所用 provider 对应的认证方式。1. 用 Instructor 生成闪卡1.1 定义 LLM 响应模型Instructor 的核心用法是定义 Pydantic 模型作为 LLM 填写的模板。没有默认值的属性将由 LLM 生成import uuid from pydantic import BaseModel, Field from pydantic.json_schema import SkipJsonSchema class QuestionAnswer(BaseModel): question: str Field(descriptionQuestion about the topic) options: list[str] Field( descriptionPotential answers to the question., min_items3, max_items5 ) answer_index: int Field( descriptionIndex of the correct answer options (starting from 0)., ge0, lt5 ) difficulty: int Field( descriptionDifficulty of this question from 1 to 5, 5 being the most difficult., gt0, le5, ) youtube_url: SkipJsonSchema[str | None] None id: uuid.UUID Field(descriptionUnique identifier, default_factoryuuid.uuid4)这个例子集中展示了 Instructor 的几项关键能力用default/default_factory阻止 LLM 幻觉id字段通过default_factoryuuid.uuid4在本地生成唯一标识不需要也不应该让模型凭空捏造一个 UUID。用SkipJsonSchema将字段排除在生成的 JSON Schema 之外youtube_url由应用程序在运行时回填见后文generate_question_and_answers中qna.youtube_url youtube_url我们不希望 LLM 猜测或虚构它。SkipJsonSchema在类型层面标注该字段不进入发给模型的 schema同时保留在最终 Pydantic 对象上。用Field约束生成内容min_items3, max_items5限制选项数量在 3~5 个之间ge0, lt5限制answer_index为合法的选项下标gt0, le5将难度限制在 1~5 区间。这些约束会被编码进发给 LLM 的 JSON Schema让模型在生成阶段就遵循规则而非事后靠校验兜底。补充说明约束校验在生成阶段就生效是因为 Instructor 会把 Pydantic 模型序列化为 schema 并作为工具参数或 JSON Schema传给模型即便个别模型偶尔违反约束Instructor 的校验层也能捕获并自动重试修正相关机制可参考 docs/concepts/retrying.md。1.2 获取 YouTube 字幕使用youtube-transcript-api拉取视频的官方字幕。从 URL 中解析出 video id 后直接取回逐条字幕片段并拼成整段文本from youtube_transcript_api import YouTubeTranscriptApi youtube_url https://www.youtube.com/watch?vhqutVJyd3TI _, _, video_id youtube_url.partition(?v) segments YouTubeTranscriptApi.get_transcript(video_id) transcript .join([s[text] for s in segments])partition(?v)会把 URL 在?v处切成三段中间的video_id即视频 ID。字幕片段可能带有时间戳等信息这里仅拼接text字段作为 LLM 的输入文本。1.3 用create_iterable生成问答对一次调用即可从整段字幕中产出多条QuestionAnswerimport instructor instructor_client instructor.from_provider(openai/gpt-5-nano) system_prompt Analyze the given YouTube transcript and generate question-answer pairs to help study and understand the topic better. Please rate all questions from 1 to 5 based on their difficulty. response instructor_client.create_iterable( modelgpt-4o-mini, response_modelQuestionAnswer, messages[ {role: system, content: system_prompt}, {role: user, content: transcript}, ], )关键点拆解创建 Instructor 客户端instructor.from_provider(openai/gpt-5-nano)采用provider/model的统一路由格式底层实现在 instructor/v2/auto_client.py它会按 provider 解析并包装对应厂商的 SDK如 OpenAI 的openai.OpenAI默认模式为Mode.TOOLS还支持async_clientTrue返回异步客户端、cache接入缓存等参数。也可以退而求其次使用instructor.from_openai(openai.OpenAI())显式包装examples/youtube-flashcards/run.py 中即采用这种写法。.create_iterable()一次生成多个对象该方法定义于 instructor/v2/core/client.py它会内部构造一个包含tasks列表的动态模型底层由 instructor/v2/dsl/iterable.py 的IterableModel生成把流式响应逐个切分成独立对象官方文档推荐优先用它而非手写Iterable[...] streamTrue详见 docs/concepts/iterable.md。指定response_modelQuestionAnswer保证每个输出都是校验过的QuestionAnswer实例。通过messages传递指令与输入system消息给出任务说明分析字幕、生成问答对、按 1~5 评分难度user消息放入整段字幕文本。返回值是一个生成器generator逐个迭代即可拿到QuestionAnswer对象print(Preview:\n) count 0 for qna in response: if count 2: break print(qna.question) print(qna.options) print() count 1 Preview: What is the primary purpose of the new OpenTelemetry instrumentation released with Burr? [To reduce code complexity, To provide full instrumentation without changing code, To couple the project with OpenAI, To enhance customer support] What do you need to install to use the OpenTelemetry instrumentation with Burr applications? [Only OpenAI package, Specific OpenTelemetry instrumentation module, All available packages, No installation needed] What advantage does OpenTelemetry provide in the context of instrumentation? [It is vendor agnostic, It requires complex integration, It relies on specific vendors, It makes applications slower] 注意create_iterable返回的是惰性生成器题目在迭代时才被逐个解析出来因此非常适合「先出几条、边看边用」的场景。2. 用 Burr 组装闪卡应用前文的脚本是线性的一旦要支持「用户反复输入 URL、逐轮生成」的交互就需要把流程组织成状态机。Burr 用actions应用能做的事和transitions动作间的流转定义应用同时保持流程图般的直观性方便理解与调试。2.1 定义actionsaction装饰器声明一个动作可读/可写哪些State字段被装饰的函数以State为第一参数返回更新后的State对象。这里定义三个动作它们只是对前文代码片段的轻量重构from burr.core import action, State action(reads[], writes[youtube_url]) def process_user_input(state: State, user_input: str) - State: Process user input and update the YouTube URL. youtube_url ( user_input # In practice, we would have more complex validation logic. ) return state.update(youtube_urlyoutube_url) action(reads[youtube_url], writes[transcript]) def get_youtube_transcript(state: State) - State: Get the official YouTube transcript for a video given its URL youtube_url state[youtube_url] _, _, video_id youtube_url.partition(?v) transcript YouTubeTranscriptApi.get_transcript(video_id) full_transcript .join([entry[text] for entry in transcript]) # store the transcript in state return state.update(transcriptfull_transcript, youtube_urlyoutube_url) action(reads[transcript, youtube_url], writes[question_answers]) def generate_question_and_answers(state: State) - State: Generate QuestionAnswer from a YouTube transcript using an LLM. # read the transcript from state transcript state[transcript] youtube_url state[youtube_url] # create the instructor client instructor_client instructor.from_provider(openai/gpt-5-nano) system_prompt ( Analyze the given YouTube transcript and generate question-answer pairs to help study and understand the topic better. Please rate all questions from 1 to 5 based on their difficulty. ) response instructor_client.create_iterable( modelgpt-4o-mini, response_modelQuestionAnswer, messages[ {role: system, content: system_prompt}, {role: user, content: transcript}, ], ) # iterate over QuestionAnswer, add the youtube_url, and append to state for qna in response: qna.youtube_url youtube_url # State is immutable, so .append() returns a new object with the appended value state state.append(question_answersqna) return state几个值得注意的细节reads/writes声明让 Burr 能静态推断每个动作的数据依赖这在可视化和调试时非常有用State是不可变对象state.update(...)与state.append(...)都返回新对象而非原地修改question_answers在循环中被逐条追加每条题目生成后立即写入 state天然支持「边生成边查看」。2.2 构建Application使用ApplicationBuilder组装应用。最小配置需要三步.with_actions()传入所有action装饰过的函数.with_transitions()用(from_action, to_action)元组定义动作间的流转.with_entrypoint()指定第一个执行的动作。from burr.core import ApplicationBuilder app ( ApplicationBuilder() .with_actions( process_user_input, get_youtube_transcript, generate_question_and_answers, ) .with_transitions( (process_user_input, get_youtube_transcript), (get_youtube_transcript, generate_question_and_answers), (generate_question_and_answers, process_user_input), ) .with_entrypoint(process_user_input) .build() ) app.visualize()注意最后一个 transition(generate_question_and_answers, process_user_input)它让应用在生成完一轮题目后回到入口等待下一次用户输入从而形成可持续交互的循环。app.visualize()可随时输出应用图来理解逻辑流向即文首那张流程图。2.3 运行应用并交互Application.run()会执行动作直到命中停止条件。这里在进入process_user_input之前暂停以便接收用户输入的 YouTube URL。run()返回三元组(action_name, result, state)本例只关心state中的生成结果action_name, result, state app.run( halt_before[process_user_input], inputs{user_input: https://www.youtube.com/watch?vhqutVJyd3TI}, ) print(state[question_answers][0])用while循环即可搭建一个简单的本地交互体验while True: user_input input(Enter a YouTube URL (q to quit): ) if user_input.lower() q: break action_name, result, state app.run( halt_before[process_user_input], inputs{user_input: user_input}, ) print(f{len(state[question_answers])} question-answer pairs generated)每轮输入一个视频 URL应用就会依次完成「记录 URL → 拉取字幕 → 生成问答对」并在下一次循环回到等待输入的状态。3. 进阶把应用生产化Burr 的Application本质是一个轻量 Python 对象可以跑在 notebook、脚本、Streamlit/Gradio 等 Web 前端甚至作为 Web 服务如 FastAPI对外提供。ApplicationBuilder提供了多个面向生产的特性状态持久化保存/恢复State例如存放对话历史、可观测性记录 LLM 调用、token 用量、错误与重试、流式与异步执行让 UI 更跟手。3.1 接入 Burr UI 遥测只需几行代码就能把遥测写入 Burr UI。关键顺序是先对 OpenAI 库插桩再创建 Instructor 客户端否则无法捕获底层调用随后在 builder 上调用.with_tracker()并指定项目名开启use_otel_tracingTrue启用 OpenTelemetry 追踪from burr.core import ApplicationBuilder from opentelemetry.instrumentation.openai import OpenAIApiInstrumentor # instrument before importing instructor or creating the OpenAI client OpenAIApiInstrumentor().instrument() app ( ApplicationBuilder() .with_actions( process_user_input, get_youtube_transcript, generate_question_and_answers, ) .with_transitions( (process_user_input, get_youtube_transcript), (get_youtube_transcript, generate_question_and_answers), (generate_question_and_answers, process_user_input), ) .with_tracker(projectyoutube-qna, use_otel_tracingTrue) .with_entrypoint(process_user_input) .build() )开启后Burr UI 中就能看到每次 OpenAI API 调用的完整信息——包括 prompt、响应模型以及响应内容方便逐轮核对 Instructor 的结构化输出是否符合预期。3.2 用标注工具沉淀评测数据Burr UI 内置标注工具可以对记录的运行数据用户输入、LLM 响应、RAG 检索到的内容等进行打标签、评分或评论。这对构建测试用例和评测数据集尤其有用——例如在多次运行后把效果好的问答对挑出来作为回归测试样本。4. 下一步构建更复杂的 Agent掌握「Instructor 保证可靠输出 Burr 组织应用结构」后可以按目标继续深入构建复杂 AgentInstructor 通过结构提升 LLM 的推理质量。嵌套模型并叠加约束可以在几行代码内实现 带引用的事实提取 或 知识图谱抽取重试机制 则让 LLM 能在校验失败时自我修正。Burr 这边可以在transitions上加Condition条件构建复杂却依然易于推理的工作流。融入你的产品把Application作为轻量组件嵌入 notebook、脚本或 Web 应用如 FastAPI 服务即可上线结合状态持久化、可观测性与流式/异步能力就能支撑真实用户场景。总结本文完整演示了一条可复用的技术路线用 Instructor 的 Pydantic 模型约束 LLM 生成结构化闪卡用youtube-transcript-api取回字幕作为输入用create_iterable一次产出多条问答对再用 Burr 的actions/transitions/State把它组装成可持续交互的应用最后通过 Burr UI 实现遥测观测与数据标注。完整可运行的参考实现就在 examples/youtube-flashcards/run.py现在就可以动手把它改造成自己的学习工具。【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考