基于 FastAPI + Qdrant 构建可解释、可约束的教育 Agent(三):知识清洗与结构感知切片
本文内容:系列第三篇,承接文档解析,介绍 RAG 入库前的两个环节——知识清洗与结构感知切片。清洗阶段逐行剔除重复、样板、纯链接与孤立符号,并以可复算的启发式规则计算质量分;切片阶段按块类型区别处理,标题不单独成块,表格与图片整块保留,正文按句子边界切分并保留重叠。
技术栈:Python 3.11、正则规则、结构化 dataclass;默认参数 target_size=1100、hard_max=1800、overlap=160。
文章目录
- 基于 FastAPI + Qdrant 构建可解释、可约束的教育 Agent(三):知识清洗与结构感知切片
- 1. 清洗与切片在 RAG 中的位置
- 2. 知识清洗管线
- 3. 质量分计算
- 4. 结构感知切片
- 5. 正文切分与 overlap
- 6. 参数与工程处理
- 7. 系列规划
- 结语
1. 清洗与切片在 RAG 中的位置
文档解析产出结构化的 ParsedBlock 后,若直接按固定长度切分入库,会引入两类问题:
- 解析结果中仍包含每页重复的页眉、版权声明、纯页码、乱码符号等低信息内容;
- 固定长度切分会在句子中间、表格内部断开,破坏语义与表格结构。
项目将处理顺序固定为:解析 → 清洗(过滤 + 质量打分)→ 结构感知切片。在切块前剔除脏内容、在完整块上计算质量分,成本与准确性都更优。
2. 知识清洗管线
KnowledgeCleaningService.clean逐块、逐行处理,整体管线如下:
逐行过滤规则包括:
- 重复行:长度在 4–80 之间、全篇出现不少于 3 次的行;
- 样板行:页码、纯数字、分隔线、版权声明、机密标识等;
- 纯链接行:整行匹配
http(s)://; - 孤立符号:长度 ≤2 且不含中英文。
样板行通过预编译正则匹配:
BOILERPLATE_RE=re.compile(r"^(?:第\s*\d+\s*页|page\s*\d+|\d+|[-–—]+|课程资料\s*版权所有|版权所有|copyright|confidential|机密)$",re.I,)逐行过滤实现:
def_clean_block_text(self,text,repeated):lines=[]forrawinstr(textor"").splitlines():line=_normalize_text(raw)ifnotline:continueiflineinrepeated:continueifself.BOILERPLATE_RE.match(line):continueifre.fullmatch(r"https?://\S+",line,re.I):continueiflen(line)<=2andnotre.search(r"[\u4e00-\u9fffA-Za-z]",line):continuelines.append(line)return_normalize_text("\n".join(lines))重复行统计限定长度 4–80,避免短词在正常内容中重复出现被误删:
def_repeated_lines(self,blocks):counts=defaultdict(int)forblockinblocks:forrawinblock.text.splitlines():line=_normalize_text(raw)if4<=len(line)<=80:counts[line]+=1return{lineforline,countincounts.items()ifcount>=3}3. 质量分计算
质量分不使用模型,而采用可复算的启发式规则,定义在knowledge_quality.py:
defscore_knowledge_text(text,block_type="text"):content=str(textor"").strip()ifnotcontent:return0.0ifblock_typein{"heading","table","visual_summary"}:return0.9signal=len(re.findall(r"[\u4e00-\u9fffA-Za-z0-9]",content))noise=len(re.findall(r"�|□|■|▯|[^\s\u4e00-\u9fffA-Za-z0-9,。;:、,.!?;:()()\[\]\-+*/|#]",content))density=signal/max(1,len(content))length_score=min(1.0,signal/80)noise_penalty=min(0.6,noise/max(1,len(content)))returnround(max(0.0,density*0.55+length_score*0.45-noise_penalty),3)清洗时,文本为空或质量分低于 0.2 的块被丢弃并计入 low_quality;保留块将分数写入 metadata。文档质量分取保留块分数的均值,报告记录 total_blocks、kept_blocks、dropped_blocks、low_quality_blocks。
4. 结构感知切片
StructureAwareChunker.chunk按块类型区别处理:
关键规则:
- heading 块跳过,不单独成块,标题信息通过 section_path 保留;
- block_type 归一为 table、image(含 image、visual_summary)、text;
- table / image 在长度不超过 hard_max 时整块保留,避免 Markdown 表格被切断;
- text 块进入句子级切分;
- 检索文本由章节路径与正文拼接,补充上下文。
forblockindoc.blocks:block_quality=float(block.metadata.get("quality_score",doc.quality_scoreor0.6))ifblock.block_typein{"heading"}:continueblock_type="table"ifblock.block_type=="table"else"image"ifblock.block_typein{"image","visual_summary"}else"text"texts=[block.text]ifblock_typein{"table","image"}andlen(block.text)<=self.hard_maxelseself._split_block(block.text)forpartintexts:section=block.section_pathorchapterordoc.document_name retrieval_text=_normalize_text("\n".join([section,part]))# 组装 StructuredChunk 与 metadata每个 chunk 的 metadata 包含 document_id、chunk_id、chapter、section_path、chunk_index、chunk_type、quality_score、page、file_type、resource_type、retrieval_text,用于引用溯源、类型过滤与质量降权。
5. 正文切分与 overlap
正文块先按句末标点或换行切出句子单元,再贪心装箱:
def_split_block(self,text):units=re.split(r"(?<=[。!?;.!?])\s*|\n+",_normalize_text(text))units=[u.strip()foruinunitsifu.strip()]ifnotunits:units=[text]chunks,buf=[],""forunitinunits:iflen(buf)+len(unit)<=self.target_size:buf=f"{buf}\n{unit}".strip()ifbufelseunitcontinueifbuf:chunks.append(buf)prefix=buf[-self.overlap:]ifself.overlapandbufelse""buf=f"{prefix}\n{unit}".strip()ifprefixelseunitwhilelen(buf)>self.hard_max:chunks.append(buf[:self.hard_max])buf=buf[self.hard_max-self.overlap:]ifself.overlap<self.hard_maxelsebuf[self.hard_max:]ifbuf:chunks.append(buf)returnchunks相邻块保留 overlap,避免完整答案横跨切块边界时无法被完整召回;hard_max 用于处理缺少标点的超长文本。
6. 参数与工程处理
默认切片参数如下:
| 参数 | 默认值 | 含义 |
|---|---|---|
| target_size | 1100 | 单块目标长度 |
| hard_max | 1800 | 单块硬上限 |
| overlap | 160 | 相邻块重叠长度 |
工程处理要点:
- 清洗先于切片,避免脏内容在切块后扩散;
- 质量分规则确定、可复算,不引入额外推理依赖;
- 表格与图片整块保留,正文只在句子边界断开;
- 章节路径前置到检索文本,提升短文本与表格行的召回语义。
7. 系列规划
知识工程部分后续将介绍查询分析与多路召回、多路结果融合、精排与动态 TopK、证据包构建。下一篇聚焦查询分析与多路召回。
结语
本文给出了知识清洗与结构感知切片的实现思路,核心是在入库前去除低信息内容、保留文档结构,并让每个 chunk 携带足够的上下文与元数据。相关实现仍在持续迭代,欢迎在评论区讨论改进方案。