导航菜单

  • 1.langchain.intro
  • 2.langchain.chat_models
  • 3.langchain.prompts
  • 4.langchain.example_selectors
  • 5.output_parsers
  • 6.Runnable
  • 7.memory
  • 8.document_loaders
  • 9.text_splitters
  • 10.embeddings
  • 11.tool
  • 12.retrievers
  • 13.optimize
  • 14.项目介绍
  • 15.启动HTTP
  • 16.数据与模型
  • 17.权限管理
  • 18.知识库管理
  • 19.设置
  • 20.文档管理
  • 21.聊天
  • 22.API文档
  • 23.RAG优化
  • 24.索引时优化
  • 25.检索前优化
  • 26.检索后优化
  • 27.系统优化
  • 28.GraphRAG
  • 29.图
  • 30.为什么选择图数据库
  • 31.什么是 Neo4j
  • 32.安装和连接 Neo4j
  • 33.Neo4j核心概念
  • 34.Cypher基础
  • 35.模式匹配
  • 36.数据CRUD操作
  • 37.GraphRAG
  • 38.查询和过滤
  • 39.结果处理和聚合
  • 40.语句组合
  • 41.子查询
  • 42.模式和约束
  • 43.日期时间处理
  • 44.Cypher内置函数
  • 45.Python操作Neo4j
  • 46.neo4j
  • 47.py2neo
  • 48.Streamlit
  • 49.Pandas
  • 50.graphRAG
  • 51.deepdoc
  • 52.deepdoc
  • 53.deepdoc
  • 55.deepdoc
  • 54.deepdoc
  • Pillow
  • 1. Markdown格式简介
    • 1.1 什么是Markdown
    • 1.2 Markdown基本语法
      • 1.2.1 标题
      • 1.2.2 文本样式
      • 1.2.3 列表
      • 1.2.4 表格
      • 1.2.5 代码块
      • 1.2.6 其他元素
    • 1.3 为什么需要了解Markdown语法
  • 2. RAGFlow Markdown解析基础
    • 2.1 什么是Markdown解析器
    • 2.2 基础使用示例
    • 2.3 重要注意事项
      • 2.3.1 层级关系未充分利用
      • 2.3.2 表格处理
  • 3. 自定义Markdown解析器
    • 3.1 为什么需要自定义解析器
    • 3.2 自定义解析器的实现思路
    • 3.3 完整实现示例
    • 3.4 关键功能说明
      • 3.4.1 表格提取
      • 3.4.2 层级分块
      • 3.4.3 递归分割
  • 4. 完整使用示例
  • 5. MarkdownHeaderTextSplitter详解
    • 5.1 什么是MarkdownHeaderTextSplitter
    • 5.2 使用示例
    • 5.3 参数说明
  • 6. 总结与最佳实践
    • 6.1 Markdown解析 vs 其他格式解析
    • 6.2 常见问题处理
    • 6.3 分块策略建议
    • 6.4 性能优化建议

1. Markdown格式简介 #

1.1 什么是Markdown #

Markdown是一种轻量级标记语言(Markup Language),由John Gruber于2004年创建。它允许用户使用简单的语法编写结构化文档,而无需依赖复杂的排版工具。

简单理解:

  • Markdown是纯文本文件(.md或.markdown扩展名)
  • 使用特殊符号(如#、*、-等)来表示格式
  • 可以转换为HTML、PDF等格式
  • 广泛用于文档编写、README文件、博客等

1.2 Markdown基本语法 #

下面介绍Markdown的常用语法,这些知识有助于理解解析过程。

1.2.1 标题 #

Markdown使用#符号表示标题,#的数量表示标题级别:

# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题

重要特点:

  • 标题有明显的层级关系(一级 > 二级 > 三级...)
  • 层级关系对于文档分块非常重要
  • 相同层级的标题通常属于同一主题

1.2.2 文本样式 #

**加粗文本**  
*斜体文本*  
***加粗+斜体***  
~~删除线~~  
`行内代码`

1.2.3 列表 #

无序列表(使用-、*或+):

- 项目 1
- 项目 2
  - 子项目 2.1
  - 子项目 2.2

有序列表(使用数字):

1. 第一步
2. 第二步
   1. 子步骤 2.1
   2. 子步骤 2.2

1.2.4 表格 #

Markdown表格使用|分隔列,-分隔表头和数据:

| 列1   | 列2   | 列3   |
|----|----|----|
| 数据1 | 数据2 | 数据3 |
| 数据4 | 数据5 | 数据6 |

表格特点:

  • 表格是Markdown中的重要结构化数据
  • 需要单独提取和处理
  • 可以转换为HTML格式

1.2.5 代码块 #

行内代码:使用单个反引号

这是`行内代码`示例

代码块:使用三个反引号

def hello():
    print("Hello, Markdown!")

1.2.6 其他元素 #

  • 链接:[文本](URL)
  • 图片:![alt文本](图片URL)
  • 引用:使用>符号
  • 分割线:使用、***或___

1.3 为什么需要了解Markdown语法 #

了解Markdown语法有助于:

  1. 理解解析过程:知道解析器在识别什么
  2. 调试问题:遇到解析问题时知道可能的原因
  3. 优化分块:利用层级关系进行更好的分块

2. RAGFlow Markdown解析基础 #

2.1 什么是Markdown解析器 #

RAGFlow的Markdown解析器使用markdown库来解析Markdown文件。它可以将Markdown文档转换为结构化的数据,提取文本、表格等元素。

主要功能:

  • 读取Markdown文件内容
  • 提取表格数据
  • 分离文本和表格
  • 支持分块处理

2.2 基础使用示例 #

下面是一个完整的使用示例:

# 导入必要的库
import os
import sys

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入RAGFlow的Markdown解析器
from rag.app.naive import Markdown, chunk

# 定义回调函数(用于显示进度,可选)
def dummy(prog=None, msg=""):
    if msg:
        print(f"进度: {msg}")

# 定义Markdown文件路径(请替换为你的Markdown文件路径)
md_file = './data/markdown_ai.md'

# 方法一:直接解析(不进行分块)
print("方法一:直接解析")
md_parser = Markdown()

# 解析Markdown文件
# 返回格式:(sections, tables)
# sections: 段落列表,每个元素是(文本, 样式)的元组
# tables: 表格列表
sections, tbls = md_parser(md_file)

# 处理段落内容
print("\n========== 段落内容 ==========")
texts = []
for sec in sections:
    if sec[0]:  # 只处理非空段落
        texts.append(sec[0])

# 打印所有文本段落
for idx, text in enumerate(texts, 1):
    print(f"\n 段落 {idx} ")
    print(text[:200] + "..." if len(text) > 200 else text)  # 只显示前200个字符

# 处理表格内容
print("\n========== 表格内容 ==========")
print(f"共找到 {len(tbls)} 个表格")
for idx, tbl in enumerate(tbls, 1):
    print(f"\n 表格 {idx} ")
    # tbl格式:((None, HTML表格), "")
    if tbl[0] and tbl[0][1]:
        print(tbl[0][1][:300] + "..." if len(tbl[0][1]) > 300 else tbl[0][1])

# 方法二:解析并自动分块
print("\n方法二:解析并分块")
parser_config = {
    "chunk_token_num": 256,      # 每个chunk的token数量
    "delimiter": "\n!?。;!?"  # 分块的分隔符
}

chunks = chunk(md_file, callback=dummy, parser_config=parser_config)

# 查看分块结果
print(f"\n共生成 {len(chunks)} 个chunk")
for idx, data in enumerate(chunks[:5], 1):  # 只显示前5个
    print(f"\n Chunk {idx} ")
    print(data['content_with_weight'][:200] + "...")  # 只显示前200个字符

代码说明:

  1. 直接解析:使用Markdown类直接解析,返回段落和表格
  2. 解析并分块:使用chunk函数,自动进行分块处理
  3. 结果格式:
    • sections:段落列表
    • tbls:表格列表(HTML格式)

2.3 重要注意事项 #

2.3.1 层级关系未充分利用 #

RAGFlow的基础Markdown解析器在文本分块时没有充分利用Markdown的层级关系。这意味着:

  • 不同层级的标题内容可能被分到同一个chunk
  • 相同层级的标题内容可能被分开
  • 可能破坏文档的逻辑结构

解决方案:使用自定义解析器,按标题层级进行分块(见后面章节)。

2.3.2 表格处理 #

基础解析器会将表格转换为HTML格式,但表格和文本是分开处理的,可能缺失上下文。

3. 自定义Markdown解析器 #

3.1 为什么需要自定义解析器 #

虽然RAGFlow提供了基础Markdown解析器,但在实际应用中,你可能需要:

  1. 利用层级关系:按标题层级进行分块,保持文档结构
  2. 表格总结:使用LLM对表格进行总结,支持分析型问题
  3. 表格明细:将表格的每一行作为独立的chunk,支持精确检索
  4. 智能分块:先按层级分块,如果块太大再递归分割

3.2 自定义解析器的实现思路 #

我们的自定义解析器将实现以下功能:

  1. 提取表格:从Markdown文本中提取表格
  2. 分离文本和表格:将文本和表格分开处理
  3. 表格处理:对表格进行总结和按行分块
  4. 层级分块:使用MarkdownHeaderTextSplitter按标题层级分块
  5. 递归分割:如果某个层级块太大,使用RecursiveCharacterTextSplitter进一步分割

3.3 完整实现示例 #

下面是一个完整的自定义Markdown解析器实现:

# 导入必要的库
import os
import sys

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入RAGFlow的基础解析器
from deepdoc.parser import MarkdownParser

# 导入Markdown处理相关
from markdown import markdown

# 导入LangChain相关
from langchain_core.documents import Document as LDocument
from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter

# 导入工具函数(需要自己实现或从utils导入)
from utils import table_to_summary
from rag.utils import num_tokens_from_string
from rag.nlp import find_codec


class MyMarkDown(MarkdownParser):
    """
    自定义Markdown解析器

    功能:
    1. 提取表格并总结
    2. 按标题层级分块
    3. 智能递归分割
    """

    def __call__(self, filename, binary=None):
        """
        解析Markdown文件,提取文本和表格

        参数:
            filename: Markdown文件路径
            binary: Markdown二进制数据(可选)

        返回:
            tuple: (段落列表, 表格列表)
        """
        # 步骤1: 读取Markdown文件内容
        txt = ""
        if binary:
            # 如果是二进制数据,需要先检测编码
            encoding = find_codec(binary)
            txt = binary.decode(encoding, errors="ignore")
        else:
            # 从文件读取
            with open(filename, "r", encoding="utf-8") as f:
                txt = f.read()

        # 步骤2: 提取表格和剩余文本
        # extract_tables_and_remainder方法会:
        # - 识别Markdown表格(标准格式和HTML格式)
        # - 从文本中移除表格
        # - 返回剩余文本和表格列表
        remainder, tables = self.extract_tables_and_remainder(f'{txt}\n')

        # 步骤3: 处理剩余文本,按行分割
        sections = []
        for sec in remainder.split("\n"):
            # 如果某行文本太长(超过10倍chunk大小),进行分割
            if num_tokens_from_string(sec) > 10 * self.chunk_token_num:
                # 将长文本分成两半
                sections.append((sec[:int(len(sec)/2)], ""))
                sections.append((sec[int(len(sec)/2):], ""))
            else:
                # 正常长度的文本直接添加
                sections.append((sec, ""))

        # 步骤4: 处理表格,转换为HTML格式
        tbls = []
        for table in tables:
            # 使用markdown库将Markdown表格转换为HTML
            # extensions=['markdown.extensions.tables']启用表格扩展
            html_table = markdown(table, extensions=['markdown.extensions.tables'])
            # 格式:((None, HTML表格), "")
            tbls.append(((None, html_table), ""))

        return sections, tbls

    def chunk(self, filename, binary=None, llm=None,
              chunk_size=512, chunk_overlap=30):
        """
        解析Markdown文件并生成Document列表

        参数:
            filename: Markdown文件路径
            binary: Markdown二进制数据
            llm: 语言模型实例(用于表格总结)
            chunk_size: 文本chunk大小
            chunk_overlap: chunk重叠大小

        返回:
            list: LangChain Document列表
        """
        # 步骤1: 解析Markdown文件,获取段落和表格
        sections, tbls = self(filename, binary)

        # 步骤2: 提取文本段落
        txt_chunks = []
        for sec in sections:
            if sec[0]:  # 只处理非空段落
                txt_chunks.append(sec[0])

        # 步骤3: 提取表格内容
        table_chunks = []
        for sec in tbls:
            # sec格式:((None, HTML表格), "")
            if sec[0] and sec[0][1]:
                table_chunks.append(sec[0][1])

        # 如果没有提供LLM,创建一个(需要从model导入)
        if llm is None:
            from model import RagLLM
            llm = RagLLM()

        docs = []

        # 步骤4: 处理表格(总结和按行分块)
        print("正在处理表格...")
        for table_info in table_chunks:
            # 使用LLM对表格进行总结
            table_summary = table_to_summary(table_info, llm)

            # 创建表格总结Document
            # page_content是总结,metadata中保存原始表格内容
            doc = LDocument(
                page_content=table_summary,
                metadata={
                    "source": "table",
                    "content": table_info  # 原始表格内容保存在metadata中
                }
            )
            docs.append(doc)

        # 步骤5: 处理文本(按标题层级分块)
        print("正在处理文本...")

        # 步骤5.1: 定义要分割的标题层级
        # headers_to_split_on是一个列表,每个元素是(标题符号, 标题名称)的元组
        # 这里定义了一级、二级、三级标题
        headers_to_split_on = [
            ("#", "Header 1"),      # 一级标题
            ("##", "Header 2"),     # 二级标题
            ("###", "Header 3"),    # 三级标题
        ]

        # 步骤5.2: 合并所有文本段落
        all_texts = "\n".join(txt_chunks)

        # 步骤5.3: 使用MarkdownHeaderTextSplitter按标题层级分块
        # strip_headers=False表示保留标题在chunk中
        r_spliter = MarkdownHeaderTextSplitter(
            headers_to_split_on, 
            strip_headers=False
        )

        # 按标题层级分割文本
        # 返回的每个Document会包含:
        # - page_content: 该标题下的内容
        # - metadata: 包含标题信息(Header 1, Header 2等)
        md_header_splits = r_spliter.split_text(all_texts)

        # 步骤5.4: 如果某个层级块太大,进行递归分割
        # 使用RecursiveCharacterTextSplitter进行进一步分割
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=chunk_size,      # 每个chunk的最大大小
            chunk_overlap=chunk_overlap  # chunk之间的重叠大小
        )

        # 对每个层级块进行分割
        # 如果块小于chunk_size,则保持不变
        # 如果块大于chunk_size,则进一步分割
        chunks = text_splitter.split_documents(md_header_splits)

        # 步骤5.5: 为每个chunk添加元数据
        for chunk in chunks:
            chunk.metadata['source'] = 'text'
            chunk.metadata['content'] = ''
            docs.append(chunk)

        print(f"处理完成!共生成 {len(docs)} 个Document")
        return docs


# 使用示例
if __name__ == "__main__":
    # 定义Markdown文件路径
    md_file = './data/markdown_ai.md'

    # 创建自定义解析器实例
    md_parser = MyMarkDown()

    # 解析并分块
    from model import RagLLM
    llm = RagLLM()

    print("开始解析Markdown文件...")
    documents = md_parser.chunk(
        md_file,
        llm=llm,
        chunk_size=512,
        chunk_overlap=30
    )

    # 查看结果统计
    text_count = sum(1 for doc in documents if doc.metadata['source'] == 'text')
    table_count = sum(1 for doc in documents if doc.metadata['source'] == 'table')

    print(f"\n解析完成!")
    print(f"文本chunks: {text_count} 个")
    print(f"表格chunks: {table_count} 个")
    print(f"总计: {len(documents)} 个Document")

    # 查看示例结果
    print("\n========== 示例结果 ==========")
    for idx, doc in enumerate(documents[:5], 1):
        print(f"\n Document {idx} ({doc.metadata['source']}) ")
        # 显示标题信息(如果有)
        if 'Header 1' in doc.metadata:
            print(f"一级标题: {doc.metadata.get('Header 1', '')}")
        if 'Header 2' in doc.metadata:
            print(f"二级标题: {doc.metadata.get('Header 2', '')}")
        if 'Header 3' in doc.metadata:
            print(f"三级标题: {doc.metadata.get('Header 3', '')}")

        content_preview = doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content
        print(f"内容: {content_preview}")

3.4 关键功能说明 #

3.4.1 表格提取 #

Markdown解析器使用正则表达式提取表格:

  1. 标准Markdown表格:识别|列1|列2|格式的表格
  2. HTML表格:识别<table>...</table>格式的表格
  3. 提取后移除:从文本中移除表格,避免重复处理

3.4.2 层级分块 #

MarkdownHeaderTextSplitter的工作原理:

  1. 识别标题:根据定义的标题符号(#、##等)识别标题
  2. 按层级分割:在每个标题处分割文本
  3. 保留标题:将标题信息保存在metadata中
  4. 层级关系:相同层级的标题内容会被分在一起

示例:

# 第一章
这是第一章的内容

## 1.1 节
这是1.1节的内容

## 1.2 节
这是1.2节的内容

# 第二章
这是第二章的内容

分块结果:

  • Chunk 1: "第一章" + "这是第一章的内容" + "1.1 节" + "这是1.1节的内容" + "1.2 节" + "这是1.2节的内容"
  • Chunk 2: "第二章" + "这是第二章的内容"

3.4.3 递归分割 #

如果某个层级块太大(超过chunk_size),RecursiveCharacterTextSplitter会进一步分割:

  1. 优先在分隔符处分割:如\n\n、\n、.等
  2. 保持重叠:相邻chunk之间有chunk_overlap大小的重叠
  3. 保持语义:尽量在语义边界处分割

4. 完整使用示例 #

下面是一个完整的示例,演示如何使用自定义解析器:

# 导入必要的库
import os
import sys

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.abspath('.'))

# 导入自定义解析器(假设已经保存为my_markdown_parser.py)
from my_markdown_parser import MyMarkDown
from model import RagLLM

# 定义Markdown文件路径
md_file = './data/markdown_ai.md'

# 创建LLM实例(用于表格总结)
llm = RagLLM()

# 创建自定义解析器实例
md_parser = MyMarkDown()

# 解析Markdown文件并生成Document列表
print("开始解析Markdown文件...")
documents = md_parser.chunk(
    md_file,
    llm=llm,
    chunk_size=512,
    chunk_overlap=30
)

# 查看结果统计
text_count = sum(1 for doc in documents if doc.metadata['source'] == 'text')
table_count = sum(1 for doc in documents if doc.metadata['source'] == 'table')

print(f"\n解析完成!")
print(f"文本chunks: {text_count} 个")
print(f"表格chunks: {table_count} 个")
print(f"总计: {len(documents)} 个Document")

# 查看示例结果
print("\n========== 示例结果 ==========")
for idx, doc in enumerate(documents[:5], 1):
    print(f"\n Document {idx} ({doc.metadata['source']}) ")

    # 显示标题层级信息
    if 'Header 1' in doc.metadata:
        print(f"一级标题: {doc.metadata.get('Header 1', '')}")
    if 'Header 2' in doc.metadata:
        print(f"二级标题: {doc.metadata.get('Header 2', '')}")
    if 'Header 3' in doc.metadata:
        print(f"三级标题: {doc.metadata.get('Header 3', '')}")

    content_preview = doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content
    print(f"内容: {content_preview}")

    if doc.metadata.get('content'):
        print(f"原始内容: {doc.metadata['content'][:100]}...")

# 可以保存到向量数据库
# from langchain_chroma import Chroma
# from model import RagEmbedding
# 
# embedding_model = RagEmbedding()
# vector_db = Chroma.from_documents(
#     documents,
#     embedding_model.get_embedding_fun(),
#     collection_name="my_markdown_collection"
# )

5. MarkdownHeaderTextSplitter详解 #

5.1 什么是MarkdownHeaderTextSplitter #

MarkdownHeaderTextSplitter是LangChain提供的专门用于按Markdown标题层级分块的工具。

工作原理:

  1. 识别Markdown文档中的标题(#、##等)
  2. 在每个标题处分割文档
  3. 将标题信息保存在Document的metadata中
  4. 保持标题层级关系

5.2 使用示例 #

# 导入必要的库
from langchain_text_splitters import MarkdownHeaderTextSplitter

# 定义要分割的标题层级
# 格式:(标题符号, 标题名称)
headers_to_split_on = [
    ("#", "Header 1"),      # 一级标题
    ("##", "Header 2"),    # 二级标题
    ("###", "Header 3"),   # 三级标题
]

# 创建分割器
# strip_headers=False表示保留标题在chunk中
splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on,
    strip_headers=False  # 如果设为True,会移除标题,只保留内容
)

# 要分割的Markdown文本
markdown_text = """
# 第一章 介绍

这是第一章的内容。

## 1.1 背景

这是背景部分的内容。

## 1.2 目标

这是目标部分的内容。

# 第二章 方法

这是第二章的内容。
"""

# 执行分割
documents = splitter.split_text(markdown_text)

# 查看结果
for idx, doc in enumerate(documents, 1):
    print(f"\n Document {idx} ")
    print(f"标题信息: {doc.metadata}")
    print(f"内容: {doc.page_content}")

输出示例:

 Document 1 
标题信息: {'Header 1': '第一章 介绍'}
内容: # 第一章 介绍

这是第一章的内容。

## 1.1 背景

这是背景部分的内容。

## 1.2 目标

这是目标部分的内容。

 Document 2 
标题信息: {'Header 1': '第二章 方法'}
内容: # 第二章 方法

这是第二章的内容。

5.3 参数说明 #

  • headers_to_split_on:要分割的标题层级列表

    • 格式:[("#", "Header 1"), ("##", "Header 2"), ...]
    • 只会在定义的层级处分割
  • strip_headers:是否移除标题

    • False:保留标题在chunk中
    • True:移除标题,只保留内容

6. 总结与最佳实践 #

6.1 Markdown解析 vs 其他格式解析 #

| 特性 | Markdown解析 | PDF解析 | Word解析 | Excel解析 | ||-||-|--| | 数据格式 | 纯文本 | 渲染后的图片 | 结构化文档 | 结构化表格 | | OCR需求 | 不需要 | 需要 | 不需要 | 不需要 | | 层级关系 | 明确(标题) | 需要识别 | 需要识别 | 无 | | 表格处理 | 文本格式 | 需要识别 | 直接读取 | 直接读取 | | 分块策略 | 可按层级 | 按位置 | 按段落 | 按行 |

6.2 常见问题处理 #

问题1:层级关系未充分利用

  • 原因:基础解析器按行分割,不考虑层级
  • 解决方案:使用MarkdownHeaderTextSplitter按标题层级分块

问题2:表格缺失上下文

  • 原因:表格和文本分开处理
  • 解决方案:在自定义解析器中为表格添加前后文本作为上下文

问题3:代码块处理

  • 原因:代码块可能被当作普通文本处理
  • 解决方案:可以在解析时识别代码块,单独处理或跳过

6.3 分块策略建议 #

  1. 按标题层级分块:

    • 优点:保持文档逻辑结构
    • 适用:有明确层级结构的文档
  2. 递归分割:

    • 优点:处理大块内容
    • 适用:层级块太大的情况
  3. 表格单独处理:

    • 优点:支持总结和明细两种检索方式
    • 适用:包含表格的文档

6.4 性能优化建议 #

  1. 选择合适的层级:只分割需要的标题层级,避免过度分割
  2. 调整chunk_size:根据实际需求调整chunk大小
  3. 缓存结果:解析结果可以缓存,避免重复解析
← 上一节 54.deepdoc 下一节 Pillow →

访问验证

请输入访问令牌

Token不正确,请重新输入