news 2026/10/6 17:32:35

RAG数据导入实战:txt与Markdown解析切块避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RAG数据导入实战:txt与Markdown解析切块避坑指南

RAG 系统里最不起眼、但最容易翻车的环节,不是向量检索,也不是大模型选型,而是数据导入与解析。我做过不下十个知识库项目,几乎每一个在 demo 阶段跑得飞起,一上真实数据就出问题——PDF 里的表格变成一坨乱码、Markdown 的标题层级全丢、txt 文件里混着各种编码。检索效果差,十有八九不是 embedding 模型不行,而是喂进去的"料"本身就是碎的。

这篇主要聊 RAG 数据导入链路里最基础的一段:纯文本(txt)和结构化文本(Markdown)怎么解析、怎么切、怎么保住结构信息。别看这两种格式简单,它们恰恰是很多知识库的主力数据源——技术文档、产品手册、会议纪要、小说语料,大量都是 txt 或 Markdown。把这两种吃透,后面处理 PDF、Word、HTML 就有底子了。适合正在搭 RAG 知识库、被数据清洗折磨过的同学,也适合刚接触 RAG、想知道"数据到底怎么进库"的新手。

1. 为什么 txt 和 Markdown 值得单独拎出来讲

1.1 它们看着简单,坑却最隐蔽

很多人觉得 txt 就是纯文本,读进来直接切块就完事了。真上手才发现,一个 txt 文件可能藏着 GBK、UTF-8、UTF-8 BOM 三种编码,甚至同一个文件里中英文段落编码还不一致。你按 UTF-8 硬读,中文全变问号,切出来的 chunk 全是乱码,向量化之后检索命中率直接归零。

Markdown 更微妙。它表面是纯文本,实际上携带了层级结构——#是一级标题、##是二级、列表项、代码块、表格、引用块。这些结构信息对 RAG 极其宝贵:标题能告诉你这段内容属于哪个主题,代码块不该被从中间切断,表格拆散就失去意义。如果你把 Markdown 当普通 txt 一刀切,等于把一份带目录的书撕成等长的纸条,检索时根本拼不回上下文。

1.2 结构信息是 RAG 检索质量的隐形杠杆

我做过一个对比实验,同一份技术文档,两种处理方式:

处理方式切块策略检索命中率(Top3)
粗暴切分按 500 字符硬切约 52%
结构感知按标题层级切,保留标题路径约 81%

差距接近 30 个百分点。原因很简单:结构感知切分让每个 chunk 自带"上下文标签"。比如一个 chunk 开头带着产品手册 > 安装指南 > 环境要求,检索时即使正文没出现"环境"这个词,标题路径也能帮上忙。这就是为什么 Markdown 的解析不能只做文本提取,必须把结构一起抽出来。

1.3 这一篇在整个导入链路里的位置

RAG 数据导入完整链路大致是:数据采集 → 格式解析 → 清洗 → 切块 → 元数据标注 → 向量化 → 入库。这篇聚焦"格式解析 + 切块"这两步,针对 txt 和 Markdown。后面还会涉及 PDF、Word、HTML 等复杂格式,但那些格式的解析思路,本质上都是从这两种基础格式延伸出去的——PDF 解析完往往也要转成 Markdown 再处理,所以先把地基打牢。

2. txt 解析:编码、清洗与切块的三道关

2.1 编码识别:别信"默认 UTF-8"

txt 文件最大的坑就是编码。Windows 上很多老文件是 GBK 或 GB2312,Mac 和 Linux 默认 UTF-8,还有些文件带 BOM 头。你如果无脑用open(file, 'r')读,Python 在部分平台会按系统默认编码走,结果就是乱码。

我的做法是先探测再读取,用chardet或charset-normalizer库自动识别:

from charset_normalizer import from_path def read_txt_safely(path): result = from_path(path).best() if result is None: raise ValueError(f"无法识别编码: {path}") text = str(result) # 去掉 BOM 头 if text.startswith('\ufeff'): text = text[1:] return text

charset-normalizer比chardet更新、维护更活跃,识别准确率也更高,尤其是中英混排的文件。实测下来,对 GBK、UTF-8、UTF-16 的识别基本没出过错。

注意:编码识别不是 100% 可靠,尤其是短文件或纯 ASCII 文件。建议在识别后做一次"可读性校验"——统计一下非 ASCII 字符里有多少是常见汉字,如果比例异常低,说明可能识别错了,需要人工介入或尝试备选编码。

2.2 清洗:哪些字符必须干掉

txt 文件里常见的脏数据包括:零宽字符(\u200b)、不间断空格(\xa0)、各种控制字符、连续空行、行尾多余空格。这些字符肉眼看不见,但会污染 embedding,让语义向量偏移。

我一般做这几步清洗:

import re def clean_text(text): # 统一换行符 text = text.replace('\r\n', '\n').replace('\r', '\n') # 去掉零宽字符和 BOM text = re.sub(r'[\u200b\u200c\u200d\ufeff]', '', text) # 不间断空格转普通空格 text = text.replace('\xa0', ' ') # 去掉控制字符(保留换行和制表) text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', text) # 连续空行压缩成一个 text = re.sub(r'\n{3,}', '\n\n', text) # 行尾空格去掉 text = re.sub(r'[ \t]+\n', '\n', text) return text.strip()

这里有个经验:不要过度清洗。有些同学喜欢把标点符号也统一了,把全角转半角,结果把中文的语义节奏破坏了。清洗的目标是去掉"机器噪声",不是改写内容。标点、语气词、口语化表达都是语义的一部分,保留它们反而有助于检索。

2.3 切块:固定长度 vs 语义边界

txt 没有天然结构,切块只能靠策略。最常用的是递归字符切分(RecursiveCharacterTextSplitter),按优先级依次尝试分隔符:段落 → 换行 → 句号 → 逗号 → 空格。这样能尽量在语义边界处断开。

from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", ".", " ", ""], length_function=len, ) chunks = splitter.split_text(clean_text)

参数怎么定?chunk_size=500是个经验值,对应中文大约 250-350 字,能容纳一个完整段落或几个短句。chunk_overlap=80是为了防止关键信息正好卡在切分点上被切断,重叠部分让相邻 chunk 有上下文衔接。

但固定长度切分有个硬伤:它不理解语义。一段讲"安装步骤"的文字,可能被从中间切开,前半段在 chunk A,后半段在 chunk B,检索时只命中一个,答案就不完整。对质量要求高的场景,我会用语义切分——先按句子切,再用 embedding 计算相邻句子的相似度,相似度骤降的地方就是切分点。

from langchain_experimental.text_splitter import SemanticChunker from langchain_openai import OpenAIEmbeddings semantic_splitter = SemanticChunker( OpenAIEmbeddings(), breakpoint_threshold_type="percentile", breakpoint_threshold_amount=95, ) chunks = semantic_splitter.split_text(clean_text)

语义切分效果好,但慢、费 token。我的建议是:长文档、结构松散的用语义切分;短文档、结构清晰的用递归切分。别一上来就全用语义切分,成本扛不住。

3. Markdown 解析:把标题层级变成检索的导航图

3.1 为什么不能把 Markdown 当 txt 处理

Markdown 的价值全在结构里。#到######六级标题构成了一棵文档树,列表、代码块、表格、引用块是叶子节点。如果你把它当 txt 硬切,会丢掉三类关键信息:

  • 标题路径:这段内容属于哪个章节,检索时能提供额外语义线索
  • 块类型:这是代码还是正文,代码块不该被切碎,表格拆开就废了
  • 层级关系:子章节和父章节的从属关系,决定了 chunk 的归属

我见过最典型的翻车案例:一份 API 文档,每个接口用##标题,参数说明用表格。粗暴切分后,表格被切成两半,检索"某接口的参数"时,命中的 chunk 只有半张表,模型只能瞎编。

3.2 用 MarkdownHeaderTextSplitter 保住标题路径

LangChain 提供了MarkdownHeaderTextSplitter,专门按标题层级切分,并把标题路径写进每个 chunk 的 metadata:

from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split_on = [ ("#", "h1"), ("##", "h2"), ("###", "h3"), ("####", "h4"), ] md_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=headers_to_split_on, strip_headers=False, # 保留标题在正文里 ) md_chunks = md_splitter.split_text(markdown_content)

切出来的每个 chunk 都带 metadata,比如:

{ "h1": "产品手册", "h2": "安装指南", "h3": "环境要求", "content": "### 环境要求\n\n- Python 3.9+\n- 内存 8GB 以上\n..." }

这个 metadata 太有用了。检索时你可以把它拼进 chunk 的文本前缀,比如[产品手册 > 安装指南 > 环境要求] 环境要求:Python 3.9+...,让 embedding 把标题路径也编码进去。实测这个技巧能把检索命中率再拉高 10 个点左右。

3.3 代码块和表格:必须特殊保护

MarkdownHeaderTextSplitter只按标题切,不处理代码块和表格。如果某个章节特别长,标题切分后还是一个大 chunk,就需要二次切分。这时候要小心:代码块和表格不能被从中间切断。

我的做法是先识别出代码块和表格,把它们当作"原子块",切分时整体保留:

import re def protect_blocks(text): # 提取代码块,用占位符替换 code_blocks = [] def replace_code(m): code_blocks.append(m.group(0)) return f"__CODE_BLOCK_{len(code_blocks)-1}__" text = re.sub(r'```[\s\S]*?```', replace_code, text) # 提取表格(连续的 | 开头行) tables = [] def replace_table(m): tables.append(m.group(0)) return f"__TABLE_{len(tables)-1}__" text = re.sub(r'(\|.*\|\n)+', replace_table, text) return text, code_blocks, tables def restore_blocks(text, code_blocks, tables): for i, block in enumerate(code_blocks): text = text.replace(f"__CODE_BLOCK_{i}__", block) for i, table in enumerate(tables): text = text.replace(f"__TABLE_{i}__", table) return text

切分完再还原。这样代码块和表格始终是完整的,不会被切碎。代价是可能出现超长 chunk,但比起信息丢失,我宁愿 chunk 大一点。

提示:如果代码块或表格本身超过 chunk_size,那就只能单独成块,并在 metadata 里标记block_type: code或block_type: table,检索时可以针对性处理。

3.4 标题路径拼接:让每个 chunk 自带上下文

前面提到把标题路径拼进 chunk 文本,这里展开说下具体做法。切分后,每个 chunk 的 metadata 里有 h1、h2、h3 等字段,按层级拼成路径:

def build_context_prefix(metadata): parts = [] for key in ["h1", "h2", "h3", "h4"]: if key in metadata and metadata[key]: parts.append(metadata[key]) return " > ".join(parts) for chunk in md_chunks: prefix = build_context_prefix(chunk.metadata) chunk.page_content = f"[{prefix}]\n{chunk.page_content}"

这样每个 chunk 开头都带着完整路径。检索时,即使正文没出现关键词,路径里的词也能帮上忙。比如用户问"环境要求是什么",chunk 正文可能只写了"Python 3.9+",但前缀[产品手册 > 安装指南 > 环境要求]里的"环境要求"直接命中。

4. 从 txt 到 Markdown:格式转换的取舍

4.1 什么时候该转,什么时候不该转

很多教程一上来就说"把所有格式统一转成 Markdown 再处理"。这话对了一半。转 Markdown 的好处是统一了结构表达,后续切分逻辑只用写一套。但转换本身会丢信息,尤其是从 PDF、Word 转过来的时候,表格和排版经常变形。

我的判断标准:

原始格式是否转 Markdown理由
txt(无结构)不转转了也没结构,白费功夫
txt(有伪结构,如用===分隔)转可以转成#标题,获得结构
HTML转HTML 标签转 Markdown 成熟且信息损失小
Word视情况简单文档转,复杂排版保留原格式解析
PDF谨慎表格和公式转换损失大,建议先解析再决定

对 txt 来说,如果它本身有伪结构——比如用连续等号、短横线做分隔,或者用"第一章""1.1"这种编号——可以写规则转成 Markdown 标题,这样就能复用 Markdown 的解析逻辑。

4.2 伪结构识别:用正则把 txt 变成 Markdown

常见的 txt 伪结构有几种:

  • 用====或----下划线标记标题
  • 用第X章、第X节标记章节
  • 用1.、1.1、1.1.1数字编号
  • 用全大写行标记标题

写一组正则把它们转成 Markdown 标题:

import re def txt_to_markdown(text): lines = text.split('\n') result = [] for i, line in enumerate(lines): stripped = line.strip() # 下划线标题:下一行是 === 或 --- if i + 1 < len(lines): next_line = lines[i+1].strip() if next_line and set(next_line) <= {'='} and len(next_line) >= 3: result.append(f"# {stripped}") continue if next_line and set(next_line) <= {'-'} and len(next_line) >= 3: result.append(f"## {stripped}") continue # 跳过下划线行本身 if stripped and set(stripped) <= {'=', '-'} and len(stripped) >= 3: continue # 章节编号 if re.match(r'^第[一二三四五六七八九十百]+[章节]', stripped): result.append(f"## {stripped}") continue # 数字编号 1.1.1 m = re.match(r'^(\d+(?:\.\d+)*)\s+(.+)', stripped) if m: level = m.group(1).count('.') + 1 level = min(level, 6) result.append(f"{'#' * level} {stripped}") continue result.append(line) return '\n'.join(result)

这套规则不是万能的,但能覆盖大部分技术文档和书籍 txt。转完之后,就能用 Markdown 的解析逻辑处理了。

4.3 转换后的校验:别让规则误伤正文

正则转换最大的风险是误伤。比如正文里出现"1. 首先打开设置",这其实是个列表项,不是标题,但会被数字编号规则误判成# 1. 首先打开设置。

我的做法是加一层校验:转换后统计标题数量,如果标题占比超过全文行数的 20%,说明规则太激进,需要收紧。另外,标题一般不会以标点结尾,可以加个判断:

def is_likely_heading(text): if len(text) > 80: # 标题不会太长 return False if text.endswith(('。', ',', ';', ':', '.', ',', ';')): # 标题不以标点结尾 return False return True

在转换前用这个函数过滤一遍,能挡掉大部分误判。转换完最好人工抽查几个文件,确认标题层级合理,再批量跑。

5. 切块参数的调优:没有万能值,只有场景值

5.1 chunk_size 到底怎么定

这是被问得最多的问题。我的答案永远是:看你的检索场景和模型上下文窗口。

  • 如果检索的是事实型问答("XX 的参数是多少"),chunk 要小,200-400 字符,保证精准命中
  • 如果检索的是总结型问答("这段讲了什么"),chunk 要大,800-1500 字符,保证信息完整
  • 如果模型上下文窗口大(如 128k),可以适当放大 chunk,减少 chunk 数量,降低检索开销

我一般从 500 起步,跑一批测试问题,看命中率和答案完整度,再上下调整。别迷信某个"最佳值",不同数据集差异很大。

5.2 overlap 的作用与代价

overlap 是为了防止信息在切分点丢失。但 overlap 太大会导致 chunk 冗余,检索时返回一堆重复内容,浪费上下文窗口。

我的经验值:overlap 取 chunk_size 的 10%-20%。500 的 chunk 配 50-100 的 overlap 比较合适。如果文档结构清晰(如 Markdown 按标题切),overlap 可以设小甚至为 0,因为标题切分本身就是语义边界。

5.3 中文的特殊处理

中文没有空格分词,按字符数切分时要注意:别在词语中间切断。比如"人工智能"被切成"人工"和"智能",两个 chunk 都失去了完整语义。

解决办法是优先在标点处切分。RecursiveCharacterTextSplitter的 separators 里把中文标点放前面:

separators=["\n\n", "\n", "。", "!", "?", ";", ",", "、", " ", ""]

这样切分时会优先在句号、问号处断开,实在不行才在逗号处断,最后才按字符硬切。实测这个顺序对中文语义完整性帮助很大。

6. 元数据标注:让 chunk 可追溯、可过滤

6.1 必带的元数据字段

每个 chunk 除了文本内容,还应该带一组元数据,方便检索时过滤和展示来源。我一般会带这些:

字段说明示例
source源文件路径docs/install.md
file_type文件类型markdown / txt
chunk_index在文档中的序号12
heading_path标题路径产品手册 > 安装指南
block_type块类型text / code / table
char_count字符数486

这些字段在检索时能派上大用场。比如用户问"代码示例",可以过滤block_type=code;用户问某个章节的内容,可以按heading_path过滤。

6.2 用元数据做检索后过滤

向量检索返回 Top-K 后,可以用元数据做二次过滤。比如只保留来自特定文档的结果,或者排除代码块:

def filter_results(results, source_filter=None, block_type=None): filtered = [] for r in results: meta = r.metadata if source_filter and source_filter not in meta.get('source', ''): continue if block_type and meta.get('block_type') != block_type: continue filtered.append(r) return filtered

这个技巧在多知识库场景特别有用。比如一个 RAG 系统同时挂了产品文档和内部 Wiki,用户问产品问题时,可以按source过滤掉 Wiki 内容,避免串味。

6.3 元数据别塞太多

元数据不是越多越好。每个字段都会增加存储和检索开销,而且塞太多无关字段会稀释有效信息。我的原则是:只带检索和展示真正用得上的字段。像文件的创建时间、修改时间、作者这些,除非业务需要,否则不用带。

7. 实测中的几个坑与应对

7.1 编码识别失败的兜底方案

charset-normalizer也有失手的时候,尤其是短文件。我的兜底方案是多编码尝试 + 可读性打分:

def read_with_fallback(path): encodings = ['utf-8', 'gbk', 'gb18030', 'utf-16', 'latin-1'] best_text = None best_score = -1 for enc in encodings: try: with open(path, 'r', encoding=enc) as f: text = f.read() score = readability_score(text) if score > best_score: best_score = score best_text = text except (UnicodeDecodeError, LookupError): continue return best_text def readability_score(text): # 统计常见汉字和英文字母占比 if not text: return 0 common = sum(1 for c in text if '\u4e00' <= c <= '\u9fff' or c.isalnum() or c.isspace()) return common / len(text)

按可读性打分选最优结果,比单纯依赖编码识别库更稳。这个方案我用了两年多,基本没再遇到乱码问题。

7.2 Markdown 表格被切碎的修复

即使做了块保护,有时候表格还是会被切碎——比如表格特别长,超过 chunk_size。这时候我的处理是:把表格转成文本描述,而不是保留 Markdown 表格语法。

def table_to_text(md_table): lines = [l for l in md_table.strip().split('\n') if l.strip()] if len(lines) < 2: return md_table headers = [c.strip() for c in lines[0].strip('|').split('|')] rows = [] for line in lines[2:]: # 跳过表头分隔行 cells = [c.strip() for c in line.strip('|').split('|')] row_desc = ",".join(f"{h}是{c}" for h, c in zip(headers, cells)) rows.append(row_desc) return ";".join(rows)

比如一张参数表,转成"参数A是值1,参数B是值2;参数A是值3,参数B是值4"这样的文本。虽然丢了表格形式,但语义完整,embedding 能理解,检索时也能命中。

7.3 超长单行文本的处理

有些 txt 文件是一整行超长文本,没有换行。这时候按换行切分完全失效,只能按字符硬切。但硬切会破坏语义。

我的做法是先按标点插入换行,再走正常切分流程:

def insert_linebreaks(text, max_len=200): # 在句末标点后插入换行,如果该行已超长 result = [] current = [] current_len = 0 for char in text: current.append(char) current_len += 1 if char in '。!?;' and current_len > max_len: result.append(''.join(current)) current = [] current_len = 0 if current: result.append(''.join(current)) return '\n'.join(result)

这样超长行会被拆成合理的段落,后续切分就正常了。

7.4 重复内容去重

知识库数据经常有重复——同一份文档多个版本、复制粘贴的段落。重复内容会导致检索时返回一堆相似结果,浪费上下文。

我一般用SimHash 或 MinHash做近似去重。简单点的话,可以对 chunk 做归一化(去空格、转小写)后算 MD5,完全相同的直接去重:

import hashlib def dedup_chunks(chunks): seen = set() result = [] for chunk in chunks: normalized = re.sub(r'\s+', '', chunk.page_content).lower() h = hashlib.md5(normalized.encode()).hexdigest() if h not in seen: seen.add(h) result.append(chunk) return result

近似去重更复杂,需要算相似度,但对质量提升明显。如果数据量大,建议上 MinHash LSH,效率高。

8. 一套可复用的解析流水线

把前面的东西串起来,我封装了一个通用的解析函数,输入 txt 或 Markdown,输出带元数据的 chunk 列表:

def parse_document(path): # 1. 读取(带编码兜底) if path.endswith('.md'): text = read_with_fallback(path) is_markdown = True else: text = read_with_fallback(path) # 尝试伪结构转 Markdown text = txt_to_markdown(text) is_markdown = True # 2. 清洗 text = clean_text(text) # 3. 保护代码块和表格 text, code_blocks, tables = protect_blocks(text) # 4. 按标题切分 md_splitter = MarkdownHeaderTextSplitter( headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")], strip_headers=False, ) chunks = md_splitter.split_text(text) # 5. 还原代码块和表格 for chunk in chunks: chunk.page_content = restore_blocks(chunk.page_content, code_blocks, tables) # 6. 二次切分(超长 chunk) final_chunks = [] for chunk in chunks: if len(chunk.page_content) > 1000: sub_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""], ) sub_texts = sub_splitter.split_text(chunk.page_content) for i, sub in enumerate(sub_texts): new_chunk = Document( page_content=sub, metadata={**chunk.metadata, 'sub_index': i} ) final_chunks.append(new_chunk) else: final_chunks.append(chunk) # 7. 加标题路径前缀 for chunk in final_chunks: prefix = build_context_prefix(chunk.metadata) if prefix: chunk.page_content = f"[{prefix}]\n{chunk.page_content}" chunk.metadata['source'] = path chunk.metadata['char_count'] = len(chunk.page_content) # 8. 去重 final_chunks = dedup_chunks(final_chunks) return final_chunks

这套流水线我用了很久,覆盖了 txt 和 Markdown 的绝大多数场景。你可以直接拿去改,也可以按自己的需求裁剪。

9. 几个容易被忽略的细节

9.1 文件读取的编码参数要显式指定

Python 的open()如果不指定 encoding,会按系统默认编码走。Windows 默认 GBK,Linux 默认 UTF-8,同一份代码在不同机器上结果不一样。永远显式指定 encoding,这是铁律。

9.2 Markdown 的 front matter 要单独处理

很多 Markdown 文件开头有 YAML front matter(---包裹的元数据)。这部分不该进正文,应该解析出来放进 metadata:

import yaml def extract_front_matter(text): if text.startswith('---'): parts = text.split('---', 2) if len(parts) >= 3: try: meta = yaml.safe_load(parts[1]) return meta, parts[2].strip() except yaml.YAMLError: pass return {}, text

front matter 里的 title、tags、date 都是很好的元数据,检索时能用来过滤。

9.3 列表项不要单独成块

Markdown 的列表项如果被单独切成一个 chunk,会失去上下文。比如:

- 支持 Python 3.9+ - 支持 Node 18+ - 支持 Java 17+

如果每个列表项单独成块,检索"支持哪些语言"时,可能只命中一个,答案不完整。我的做法是把整个列表作为一个块,除非列表特别长。

9.4 标题本身也要进 chunk

有些切分策略会把标题从正文里剥离,只留正文。这样 chunk 就失去了"这段讲什么"的直接线索。我建议strip_headers=False,让标题留在正文里,同时 metadata 里也存一份。

10. 关于效果验证的一点经验

解析和切分做完,怎么知道效果好不好?别靠感觉,要建测试集。

我的做法是:从文档里挑 20-30 个典型问题,人工标注正确答案所在的 chunk。然后跑检索,看 Top-K 命中率。如果命中率低于 70%,说明切分或解析有问题,回去调。

另外,看 bad case比看整体指标更有用。把没命中的问题拉出来,看命中的 chunk 是什么,为什么没命中。常见原因有:chunk 太大导致语义稀释、chunk 太小导致信息不全、标题路径没拼、编码问题导致乱码。对症下药,比盲目调参有效得多。

这套流程跑下来,txt 和 Markdown 的解析基本就稳了。后面处理 PDF、Word、HTML 时,思路是一样的——先保住结构,再谈切分。结构是 RAG 检索质量的地基,地基打不牢,上面堆再多优化都是白搭。

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

上位机界面开发框架怎么选?Qt/MFC/WinForm/WPF全面对比与选型指南

做上位机界面开发这些年&#xff0c;我经常在论坛和群里看到同一个问题&#xff1a;Qt、MFC、WinForm、WPF到底选哪个好&#xff1f;每次都能吵出几百条回复&#xff0c;有人力挺Qt说跨平台是真香&#xff0c;有人守着MFC说老代码根本动不了&#xff0c;还有人觉得WinForm简单够…

作者头像 李华
网站建设 2026/10/6 17:32:27

游戏引擎物理与动画系统架构设计与性能优化实战

1. 物理与动画系统在游戏引擎中的定位与整体设计1.1 为什么物理和动画是引擎架构里最难啃的两块骨头做引擎开发的人都有一个共识&#xff1a;渲染管线可以靠堆人力优化&#xff0c;脚本层可以靠热重载提升迭代速度&#xff0c;唯独物理和动画这两块&#xff0c;一旦架构设计出了…

作者头像 李华
网站建设 2026/10/6 17:32:08

微信小游戏斗地主联机实战:Node.js服务端与状态同步

简介&#xff1a;这是一套面向微信小游戏开发者与Node.js后端学习者的斗地主项目源码&#xff0c;适合想打通小游戏前后端、理解实时对战服务器架构的初中级开发者参考。压缩包共253个文件&#xff0c;约5.95MB&#xff0c;以162个js脚本为核心&#xff0c;涵盖服务器入口、游戏…

作者头像 李华
网站建设 2026/10/6 17:32:01

Python进阶:SOLID原则与23种设计模式实战落地

去年年初我被拉进一个维护了很久的Python订单系统&#xff0c;核心模块是一千多行的订单处理器&#xff0c;十几个if-elif分支轮番处理支付方式、优惠策略和通知渠道。加一个优惠类型要改三处代码&#xff0c;改一处发货逻辑会连带影响到支付回调和库存扣减。当时团队的结论很一…

作者头像 李华
网站建设 2026/10/6 17:31:36

15MB本地代理工具:一键切换Codex与Claude Code模型

1. 这个15MB小工具到底解决了什么问题第一次看到“一个15MB的小工具&#xff0c;让Codex和Claude Code随便换模型”这个标题&#xff0c;我脑子里蹦出来的第一个念头就是&#xff1a;终于有人把这件事做成独立工具了。但凡同时用过Codex和Claude Code的人都知道&#xff0c;这两…

作者头像 李华
网站建设 2026/10/6 17:31:36

Codex智能体自动化实战:从AGENTS.MD配置到多场景生产线搭建

1. 从“会用工具”到“造生产线”&#xff1a;Codex 多场景自动化到底在解决什么问题这两年“智能体”这个词被聊得太多&#xff0c;多到有点变味。很多人一提到智能体&#xff0c;脑子里浮现的还是对话框里那个你问一句它答一句的助手。但真正在一线干活的人会发现&#xff0c…

作者头像 李华