news 2026/9/15 15:41:13

Instructor 文档智能分段实战指南:用 Cohere command-a 将长文档切成语义完整的 Section

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Instructor 文档智能分段实战指南:用 Cohere command-a 将长文档切成语义完整的 Section

Instructor 文档智能分段实战指南:用 Cohere command-a 将长文档切成语义完整的 Section

【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor

本文是一份基于 Instructor 结构化输出能力的文档分段(Document Segmentation)实战指南。文章以Section+StructuredDocument两个 Pydantic 模型为骨架,讲解如何让长上下文 LLM(以 Cohere command-a 为例)把任意长文档切成一个个围绕单一核心概念的分段,并在分段后无损还原原始文本。读完本文,你将掌握一套不依赖正则规则、可复用在论文、教程、代码文档等任意场景的分段流水线。

为什么需要 LLM 驱动的文档分段

很多时候,我们需要把一份长文档拆分成"有意义的"段落,且每个段落都围绕一个核心概念展开——例如把一篇技术教程切分成可以按课时讲授的若干主题块。

传统的基于长度或规则的文本分割器(text-splitter)并不可靠:它们通常依赖空行、标题等表面特征,一旦文档中出现代码片段或数学公式,这些内容既不能简单地在'\n\n'处切开(会破坏语法完整性),也难以为每种文档类型编写大量专门规则。幸运的是,具备足够长上下文窗口的 LLM 天然适合完成这项任务:它能够理解语义边界,判断哪里是一个概念的结束、另一个概念的开始。

这正是 Instructor 的用武之地:我们用 Pydantic 模型声明"分段结果"的 schema,让 LLM 以结构化输出的形式返回分段边界,从而把不可靠的文本切分升级为可校验、可复现的语义切分。

定义分段的数据结构

分段任务的输出模型非常简单,只有两个类:

  • Section:描述文档中的一段。包含该段主题标题,以及该段在文档中的起止行号。
  • StructuredDocument:封装一份文档全部分段的容器。
from pydantic import BaseModel, Field from typing import List class Section(BaseModel): title: str = Field(description="main topic of this section of the document") start_index: int = Field(description="line number where the section begins") end_index: int = Field(description="line number where the section ends") class StructuredDocument(BaseModel): """obtains meaningful sections, each centered around a single concept/topic""" sections: List[Section] = Field(description="a list of sections of the document")

这里有一个非常关键的设计:LLM 只返回分段的起止行号,而不是分段正文Field(description=...)中给每个字段补充了语义描述,帮助 LLM 准确理解start_index/end_index的含义。这样做的直接好处是:

  1. 避免 LLM 重新生成原文——模型不参与内容创作,自然不会有改写、遗漏或幻觉文本混入;
  2. 输出极其轻量——即使输入文档有几千行,结构化输出也只是若干个小整数区间;
  3. 无损还原——分段正文完全从原始文档按行号切取,保证与原文逐字一致。

文档预处理:给每一行编号

为了让 LLM 能够用行号引用文档位置,我们需要在送入模型前对文档做一次预处理:把每一行前面加上行号标记(如[0][1][2]),同时维护一张行号 -> 原文的映射表,供后续还原使用。

def doc_with_lines(document): document_lines = document.split("\n") document_with_line_numbers = "" line2text = {} for i, line in enumerate(document_lines): document_with_line_numbers += f"[{i}] {line}\n" line2text[i] = line return document_with_line_numbers, line2text

这个函数返回两个东西:

  • document_with_line_numbers:带行号标记的文档文本,直接作为用户消息发送给 LLM;
  • line2text:字典{行号: 原文},在分段完成后用于把start_indexend_index区间映射回真实文本。

行号从 0 开始连续编号,LLM 在 system prompt 中会被告知方括号里的数字即行号。

使用 Instructor + Cohere 提取分段

接下来是核心环节:创建 Instructor 客户端,让 LLM 从带行号的文档中提取StructuredDocument

import instructor # Apply the patch to the cohere client # enables response_model keyword client = instructor.from_provider("cohere/command-r-plus") system_prompt = f"""\ You are a world class educator working on organizing your lecture notes. Read the document below and extract a StructuredDocument object from it where each section of the document is centered around a single concept/topic that can be taught in one lesson. Each line of the document is marked with its line number in square brackets (e.g. [1], [2], [3], etc). Use the line numbers to indicate section start and end. """ def get_structured_document(document_with_line_numbers) -> StructuredDocument: return client.create( model="command-a-03-2025", response_model=StructuredDocument, messages=[ { "role": "system", "content": system_prompt, }, { "role": "user", "content": document_with_line_numbers, }, ], ) # type: ignore

这段代码有四个要点:

  1. 客户端初始化instructor.from_provider(...)是 Instructor 提供的统一工厂方法,传入形如"cohere/command-a-03-2025"provider/model字符串即可自动完成 provider 识别、客户端创建与补丁(patch)注入,从而让client.create支持response_model关键字。从当前仓库源码看,该方法实现在 instructor/v2/auto_client.py,要求模型字符串必须是"provider/model-name"格式,并支持async_clientcachemode等扩展参数。

  2. System prompt 设计:这里把 LLM 塑造成"整理讲义的世界级教育家",明确要求"每个 section 围绕一个可在一节课内讲完的单一概念",并强调方括号内的数字是行号,必须用行号标注起止。Prompt 的角色设定与输出约束直接决定了分段质量。

  3. 模型选择command-a-03-2025是 Cohere 的 256k 长上下文模型,足以容纳一篇完整教程;command-r-plus则为相对轻量的备选。两者的选择取决于你的文档长度与预算。

  4. 返回类型标注-> StructuredDocument让 IDE 与类型检查器能感知返回值结构,response_model=StructuredDocument则让 Instructor 在运行时完成校验与反序列化。

根据起止行号还原分段正文

拿到StructuredDocument后,借助预处理阶段的line2text映射,把每个分段的起止行号区间还原成真实文本:

def get_sections_text(structured_doc, line2text): segments = [] for s in structured_doc.sections: contents = [] for line_id in range(s.start_index, s.end_index): contents.append(line2text.get(line_id, '')) segments.append( { "title": s.title, "content": "\n".join(contents), "start": s.start_index, "end": s.end_index, } ) return segments

注意两点实现细节:

  • 循环使用range(s.start_index, s.end_index),即左闭右开区间,第end_index行本身不包含在本段内,分段之间不会重叠;
  • 使用line2text.get(line_id, '')兜底,即便 LLM 偶尔给出越界行号也不会抛异常,只会得到空行,增强了健壮性。

每个返回的 segment 是一个包含titlecontentstartend的字典,既便于阅读,也方便后续接知识图谱、向量化索引等下游任务。

完整示例:切分一篇 Transformer 教程

下面把上述类与函数串起来,演示如何分段 Sebastian Raschka 的《Self-Attention from Scratch》教程。我们使用trafilatura包抓取并抽取网页正文:

from trafilatura import fetch_url, extract import instructor from pydantic import BaseModel, Field from typing import List def doc_with_lines(document): document_lines = document.split("\n") document_with_line_numbers = "" line2text = {} for i, line in enumerate(document_lines): document_with_line_numbers += f"[{i}] {line}\n" line2text[i] = line return document_with_line_numbers, line2text client = instructor.from_provider("cohere/command-r-plus") system_prompt = f"""\ You are a world class educator working on organizing your lecture notes. Read the document below and extract a StructuredDocument object from it where each section of the document is centered around a single concept/topic that can be taught in one lesson. Each line of the document is marked with its line number in square brackets (e.g. [1], [2], [3], etc). Use the line numbers to indicate section start and end. """ class Section(BaseModel): title: str = Field(description="main topic of this section of the document") start_index: int = Field(description="line number where the section begins") end_index: int = Field(description="line number where the section ends") class StructuredDocument(BaseModel): """obtains meaningful sections, each centered around a single concept/topic""" sections: List[Section] = Field(description="a list of sections of the document") def get_structured_document(document_with_line_numbers) -> StructuredDocument: return client.create( model="command-a-03-2025", response_model=StructuredDocument, messages=[ { "role": "system", "content": system_prompt, }, { "role": "user", "content": document_with_line_numbers, }, ], ) # type: ignore def get_sections_text(structured_doc, line2text): segments = [] for s in structured_doc.sections: contents = [] for line_id in range(s.start_index, s.end_index): contents.append(line2text.get(line_id, '')) segments.append( { "title": s.title, "content": "\n".join(contents), "start": s.start_index, "end": s.end_index, } ) return segments url = 'https://sebastianraschka.com/blog/2023/self-attention-from-scratch.html' downloaded = fetch_url(url) document = extract(downloaded) document_with_line_numbers, line2text = doc_with_lines(document) structured_doc = get_structured_document(document_with_line_numbers) segments = get_sections_text(structured_doc, line2text)

运行之后,segments就是一个按语义切分好的分段列表。例如第 6 个分段的标题与内容如下:

print(segments[5]['title']) """ Introduction to Multi-Head Attention """ print(segments[5]['content']) """ Multi-Head Attention In the very first figure, at the top of this article, we saw that transformers use a module called multi-head attention. How does that relate to the self-attention mechanism (scaled-dot product attention) we walked through above? In the scaled dot-product attention, the input sequence was transformed using three matrices representing the query, key, and value. These three matrices can be considered as a single attention head in the context of multi-head attention. The figure below summarizes this single attention head we covered previously: As its name implies, multi-head attention involves multiple such heads, each consisting of query, key, and value matrices. This concept is similar to the use of multiple kernels in convolutional neural networks. To illustrate this in code, suppose we have 3 attention heads, so we now extend the \(d' \times d\) dimensional weight matrices so \(3 \times d' \times d\): In: h = 3 multihead_W_query = torch.nn.Parameter(torch.rand(h, d_q, d)) multihead_W_key = torch.nn.Parameter(torch.rand(h, d_k, d)) multihead_W_value = torch.nn.Parameter(torch.rand(h, d_v, d)) Consequently, each query element is now \(3 \times d_q\) dimensional, where \(d_q=24\) (here, let's keep the focus on the 3rd element corresponding to index position 2): In: multihead_query_2 = multihead_W_query.matmul(x_2) print(multihead_query_2.shape) Out: torch.Size([3, 24]) """

可以看到:含数学公式与 PyTorch 代码块的整块内容被完整保留在一个分段里,标题也准确概括为 "Introduction to Multi-Head Attention"——这正是基于语义的分段相对朴素文本切分的核心价值。同样的方法可以迁移到任意需要把复杂长文档拆分成语义块的其他领域。

源码级的实现原理:from_provider 与 from_cohere

为了让你在排查问题或扩展功能时心里有底,这里结合当前仓库源码说明这条流水线背后的实现机制。

统一入口instructor.from_provider:其实现在 instructor/v2/auto_client.py 中,逻辑是:按"/"拆分模型字符串得到 provider 名与模型名,再从 provider 注册表(instructor/v2/core/provider_specs.py,Cohere 的别名注册为"cohere")查找对应的from_cohere工厂函数,最终返回一个 Instructor 实例。它支持async_client=True返回异步客户端、cache注入缓存适配器、mode覆盖 provider 默认模式。

Cohere 适配层instructor.providers.cohere.client是一个兼容门面,真实实现在 instructor/v2/providers/cohere/client.py 的from_cohere。从源码可以看到它同时兼容 Cohere 的 V1(cohere.Client/AsyncClient)与 V2(cohere.ClientV2/AsyncClientV2)SDK,并做两件事:

  1. 模式归一化:将 Cohere 特有的工具调用模式归一化为通用的Mode.TOOLS等模式,并校验该模式已在 Cohere 的注册表中注册(未注册则抛出ModeError);
  2. 补丁注入:通过patch_v2包装client.chat/client.chat_stream,使create方法获得response_model结构化输出能力,同时根据客户端版本自动切换消息格式——V2 使用 OpenAI 兼容的messages格式,V1 使用message+chat_history格式。

仓库内的可运行参考实现见 examples/cohere/cohere.py,它演示了from_cohere的完整用法(含temperature=0以提升确定性);更详细的 Cohere 集成说明(安装pip install "instructor[cohere]"、导出CO_API_KEY等)见 docs/integrations/cohere.md。

使用要点与注意事项

  • 行号格式要与 prompt 约定一致:预处理阶段用什么格式编号([0]起始、每行一个编号),prompt 中就要如实说明,两者不一致会显著降低分段准确率。
  • 区间语义要统一start_index包含、end_index排他(左闭右开)的约定应在Field(description)中写清楚,并保证get_sections_textrange逻辑与之一致。
  • 长文档与大模型:分段质量依赖模型的上下文长度与指令遵循能力。若使用其他模型或更长文档,需评估上下文窗口是否足够;本文使用的 command-a 系列模型具备 256k 长上下文,是这类任务的合适选择。
  • 确定性优先:分段属于"定位"类任务而非"生成"类任务,在预算允许时建议将temperature设为 0(参考 examples/cohere/cohere.py 的做法),以获得稳定、可复现的边界结果。
  • 越界兜底line2text.get(line_id, '')保证了极端情况下流水线不会崩溃,但在生产环境建议额外校验start_index < end_index并过滤空分段。

延伸阅读

本文的分段结果可以直接作为下游任务的数据源,仓库中提供了相关配套指南:

  • 知识图谱构建 —— 从文档构建知识图谱
  • 实体解析 —— 识别并对齐实体
  • 列表抽取 —— 抽取多个对象
  • 嵌套结构 —— 复杂层级模型建模
  • from_provider 客户端配置 —— 更全面的客户端初始化方式

【免费下载链接】instructorstructured outputs for llms项目地址: https://gitcode.com/GitHub_Trending/in/instructor

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 15:40:47

基于QT与STM32的振动测量系统:ADC采样、串口通信与FFT频谱实现

简介&#xff1a;这是一份基于QT与STM32的振动测量系统完整工程资料&#xff0c;定位于本科毕业设计、课程作业及嵌入式初/中级开发者&#xff0c;覆盖从下位机数据采集到上位机界面展示的典型开发环节。压缩包共449个文件&#xff0c;约21.16MB&#xff0c;包含C/C源码、STM32…

作者头像 李华
网站建设 2026/9/15 15:38:23

Holt-Winters算法详解:时间序列预测的原理、实现与实战

1. 时间序列预测&#xff0c;为什么我最终选了Holt-Winters接手这个需求之前&#xff0c;我对时间序列预测的认知还停留在"拿历史数据画条线&#xff0c;然后用眼睛估摸一下未来走势"的阶段。直到业务方丢给我一份带有明显趋势和季节性波动的销售数据&#xff0c;让我…

作者头像 李华
网站建设 2026/9/15 15:37:08

HTML5语义标签实战指南:从结构混乱到可访问性提升

1. 为什么“语义标签”不是锦上添花&#xff0c;而是网页结构的底层地基你有没有遇到过这样的情况&#xff1a;用<div class"header">写完导航栏&#xff0c;再套三层<div class"nav-item">嵌套出菜单项&#xff0c;最后在调试响应式时发现屏幕…

作者头像 李华
网站建设 2026/9/15 15:37:00

ECShop仿京东整站源码解析:模板引擎、PHP二次开发与缓存排查实战

简介&#xff1a;这是一套基于ECShop二次开发的仿京东商城整站源码&#xff0c;定位为PHP后端项目实战包&#xff0c;适合毕业生或面向期末大作业、课程设计的开发者快速获取完整电商系统&#xff0c;无需从零搭建。压缩包共2859个文件&#xff0c;以928个PHP逻辑页为骨架&…

作者头像 李华
网站建设 2026/9/15 15:36:53

CloudQ WorkBuddy完全指南:从技能配置到自动化工作流的效率智能体实践

最近后台收到不少留言&#xff0c;都在问CloudQ WorkBuddy到底怎么用、和CodeBuddy有什么区别、装完之后启动慢怎么解决。作为一个从内测阶段就开始用WorkBuddy的老用户&#xff0c;今天就把我这大半年的使用经验完整梳理一遍&#xff0c;从基础概念到进阶玩法&#xff0c;从安…

作者头像 李华
网站建设 2026/9/15 15:36:39

Vue3特色美食网站模板:从路由到部署的完整实践指南

简介&#xff1a;一份基于VUE3开发的简洁特色美食网站源码&#xff0c;面向前端初学者、课程设计及毕业设计人群&#xff0c;可直接运行并用于快速搭建个人站点。网站实现世界特色美食、国内特色美食、美食图展、关于我们、登录注册与美食详情介绍等功能模块&#xff0c;内置九…

作者头像 李华