《筆記》7-7 RAG Context(文本)切割

▌課程重點

  • 為何需要文本切割(Chunking)
  • 「智慧/邏輯切割」與「簡單/固定大小切割」兩種方法
  • 處理不同資料類型(文章、書籍、圖片、簡報等)的具體策略
  • 用於文本清理和 JSON 輸出的 Prompt 範例
  • Python 的程式碼範例
  • 使用 LLM as a Judge 進行評估的想法

▌為何需要文本切割 (Chunking)?

在建構 RAG 系統時,我們常需處理篇幅很長的資料,例如學術論文、書籍章節或會議逐字稿。 如果每次處理使用者查詢時,都將整份長文件放入大型語言模型 (LLM) 的提示 (Prompt) 中,會導致幾個問題:

  • 成本高昂:LLM 的使用成本通常與輸入的文本長度(Token 數量)成正比。

  • 效率低下:處理過長的上下文會增加模型的計算時間。

  • 超出上下文長度限制:許多模型有其輸入長度的上限。

  • 資訊雜訊:一份長文件中,只有一小部分內容與特定查詢相關。將不相關的資訊一併提供,反而可能干擾模型生成精確的答案。

因此,文本切割 成為一個必要的預處理步驟。其目標是將一份大文件分解成多個更小、更易於管理的 「區塊 (Chunks)」。RAG 系統在收到查詢後,首先從這些區塊中檢索出最相關的幾個,然後僅將這些高度相關的區塊提供給 LLM,從而生成更精準、高效且經濟的回答。

▌文本處理與切割的兩種主要方法

課程中介紹了兩種主流的文本切割方法:智慧/邏輯切割和簡單/固定大小切割。

》方法一:智慧/邏輯切割 (Smart/Logical Chunking)

此方法利用 LLM 的強大語言理解能力,對文本進行有意義的分割。 這不僅是機械地切分,而是試圖保持每個區塊內的語義完整性和邏輯連貫性。執行流程通常包含以下兩個步驟:

  1. 文本清理與初步結構化

    • 目的:將原始、非結構化的文本(例如充滿贅詞和停頓的會議逐字稿)轉換為流暢、語法正確且可讀性高的文章。
    • 實作:可以透過一個精心設計的 Prompt 指示 LLM 扮演專業編輯的角色,完成這項任務。
    • Prompt 範例 (本文最下方有完整說明):

      你是一位專業的數據科學領域編輯。你的任務是將一段 podcast 逐字稿轉換為流暢易讀的文本,並盡可能保留原始資訊。請移除不必要的填充詞(如嗯、啊、然後、對)、重新組織語句使其通順,並將文本分割成多個簡短的段落。

  2. 命名區塊並輸出為結構化格式 (JSON)

    • 目的:在文本被清理後,進一步讓 LLM 識別出各個邏輯區塊,為其賦予標題,並以易於程式處理的格式(如 JSON)輸出。
    • 實作:將上一步產生的潔淨文本再次提供給 LLM,並給予新的指令。
    • Prompt 範例 (本文最下方有完整說明):

      你是一位專業的數據科學領域編輯。我會提供一段已按邏輯切分的文本(區塊間以 -~~~- 分隔)。你的任務是為每個區塊命名一個標題,並在必要時重新排列或合併區塊。請嚴格按照指定的 JSON 格式輸出,不要包含任何額外格式。

    • 輸出範例
      {"blocks": [
        {"title": "Welcome and Introduction",
         "sentences": ["Hi everyone, welcome to our office hours! It's been a while since we had these sessions, so it's nice to have you here.", "I can't see you, but I see one person has joined so far. Great to have you!"]},
        {"title": "Project Requirements and Overview",
         "sentences": ["Let's start with this file.", "Remember, to get a certificate of completion for this course, you must complete a project.", "The key requirement for passing the course and getting a certificate is completing a project that demonstrates your ability to apply what you've learned."]}
      ]}
      

》方法二:簡單/固定大小切割 (Simple/Fixed-Size Chunking)

這是一種更直接的方法,它使用一個 滑動視窗 (Sliding Window) 的概念,將文本切割成固定長度的區塊,並允許區塊之間有部分重疊。

  • 概念

    • 區塊大小 (Chunk Size):定義每個區塊包含多少單詞或字符。
    • 重疊大小 (Overlap Size):定義相鄰兩個區塊之間共享的單詞或字符數量。 適度的重疊有助於避免在區塊邊界切斷完整的句子或語義單元,從而更好地保留上下文。
  • Python 實作範例 (本文最下方有完整說明)::

    def chunk_text(text, chunk_size=240, overlap_size=20):
        """
        將給定文本切割成指定大小且帶有重疊的區塊。
    
        參數:
        text (str): 要切割的輸入文本。
        chunk_size (int): 每個區塊應包含的單詞數。
        overlap_size (int): 連續區塊之間應重疊的單詞數。
    
        返回:
        list: 一個包含文本區塊的列表。
        """
        words = text.split()
        chunks = []
        start = 0
        text_length = len(words)
    
        while start < text_length:
            # 確定區塊的結束索引
            end = start + chunk_size
            
            # 將區塊加入列表
            chunk = " ".join(words[start:end])
            chunks.append(chunk)
            
            # 移動起始索引,為下一個區塊做準備
            # 步長為 (chunk_size - overlap_size) 以確保重疊
            start += chunk_size - overlap_size
            
            if end >= text_length:
                break
    
        return chunks
    
    # 使用範例:
    # article_text = "你的長篇文章內容..."
    # chunks = chunk_text(article_text, chunk_size=240, overlap_size=20)
    # for i, chunk in enumerate(chunks):
    #     print(f"Chunk {i+1}:\n{chunk}\n")
    

▌不同資料類型的處理策略

根據資料的特性,我們可以採用不同的處理策略。

資料類型 處理策略
多篇文章 (Multiple Articles) 1.為每篇文章分配唯一的 doc_id
2. 對每篇文章進行切割 (chunking)。
3. 為每個切割出的區塊分配唯一的 chunk_id (例如 doc_id_chunk_number)。
4. 在索引資料庫(如 Elasticsearch)中儲存每個區塊,並同時記錄其 doc_idchunk_id
5. 評估檢索效果時,可同時評估 文件命中率(是否找到了正確的文章)和 區塊命中率(是否找到了最精確的那個區塊)。
單篇文章/逐字稿 流程與多篇文章類似,但規模較小。 可建立一個即時處理系統:使用者提供一個來源(如 YouTube 影片連結),系統自動抓取內容、切割、索引,並讓使用者立即開始對話。 由於資料量不大,可考慮使用記憶體內資料庫 (in-memory database)。
書籍/長篇內容 1.嘗試將內容在更高層次上進行劃分,例如將每個 章節 (Chapter)小節 (Section) 視為一份獨立文件。
2. 然後再對這些「文件」進行更細緻的切割。
3. 由於策略眾多,建議透過實驗和評估(如使用 LLM as a Judge)來決定哪種結構劃分最有效。
圖片 (Images) 1.文字描述:使用多模態模型(如 gpt-4o-mini)為每張圖片生成詳細的文字描述。
2. 向量嵌入:使用像 CLIP 這樣的模型將圖片直接轉換為向量嵌入。
3. 將每張圖片(及其生成的描述或嵌入)視為一個獨立文件進行索引。
簡報 (Slides) 1.將一份簡報檔案 (slide deck) 視為一份 文件 (document)
2. 將簡報中的每一頁投影片 (slide) 視為一個 區塊 (chunk)
3. 可以利用 LLM 為每頁投影片生成內容描述,然後對這些描述進行索引,這樣就回到了處理多篇文章的邏輯。

▌總結與評估

無論選擇哪種切割策略,評估 都是至關重要的一環。你需要一種方法來判斷調整(例如改變 chunk_size、更換切割方法)是否對 RAG 系統的最終表現產生了正面影響。
課程中提到的 LLM as a Judge 是一個強大的評估工具:我們可以讓一個獨立的 LLM 來判斷 RAG 系統生成的答案是否準確地回答了問題。透過比較不同策略下的評分,我們可以數據化地做出決策,找到最適合當前資料與應用的文本處理方案。


▌文本切割 prompt 範例

本節課程老師使用三個 prompt,逐步示範從原始的影片逐字稿,切割為適合 RAG 處理的資料。

以下是原文和中文翻譯供大家參考:

  1. 將影片逐字稿轉為流暢文章
  2. 將前述文章編輯並輸出為 JSON 格式
  3. 撰寫 Python 程式,切割文本

》Prompt 1:將影片逐字稿轉為流暢文章

這個 prompt 要求 GAI 將一份原始的逐字稿,透過多項編輯規則,轉換成一篇流暢、易讀且結構清晰的文章。

prompt 翻譯參考:

你是一位精通數據科學的專業編輯。 你的任務是將一份 podcast 逐字稿轉換為流暢易讀的文本,同時盡可能保留所有原始資訊。

指令:

  • 移除填充詞,如嗯、啊等語助詞。
  • 當 ‘so’, ‘right’, ‘like’ 等詞僅作為填充詞而無實際意義時,將其移除。
  • 為了更清晰地表達,重組語句。
  • 重新排列字詞,使其符合文法。
  • 如果句子以「and」開頭,請重新表述。
  • 如果句子以「right?」結尾,請將其改寫為一個正式的問句。
  • 盡可能使用原句中的字詞。
  • 當一個思緒在邏輯上結束時,透過換行開始一個新段落。
  • 為提升可讀性,請保持段落簡短(每段約 3-4 個句子或行)。
  • 為提升可讀性,將文本分割成邏輯區塊,並在區塊之間使用 -~~~- 分隔。
  • 請務必遵循提供的格式,不要添加任何額外格式,如標題、粗體等。
  • 僅使用提供的資訊,不要添加任何額外內容。

自動逐字稿有時會出錯,你需要修正它們。 我們會提供上下文——即我們在活動前準備好的問題。 同時,也請運用你自己的判斷力和知識。

老師原始 prompt 1
You're a professional editor highly skilled in data science. Your task is to turn a podcast transcript into a readable text while preserving as much of the original information as possible

Instructions:

- remove filler words, uhms, mhms and so on
- remove "so", "right", "like" when they are not needed in the text and used only as fillers
- rephrase sentences for clarity
- rearrange words so the result is grammatically correct
- if a sentence starts with "and", rephase it 
- if a sentence ends with "right?", rephrase it - make it a question 
- use as many words from the original sentence as possible
- when a thought logically ends, start a new paragraph by simply adding a linebreak
- keep paragraphs short (3-4 sentences or lines each) to enhance readability
- split the texts into logical blocks to enhance readability, separate paragraphs between blocks by adding -~~~-
- always follow the provided format and don't add any extra formatting like headers, bold, etc
- use only the provided information, don't add anything 

Sometimes there are errors in the automatic transcription, and you will need to correct them. We will give you context - the questions we prepared in advance before the event. Also use your own judgement and knowledge.

Format:

Sentence 1 of paragraph 1. Sentence 2 of paragraph 1. ...  
Sentence 1 of paragraph 2. Sentence 2 of paragraph 2. ...  

-~~~-

Sentence 1 of paragraph 3. Sentence 2 of paragraph 3. ...  
Sentence 1 of paragraph 4. Sentence 2 of paragraph 4. ...  

Transcript:
以下是字幕逐字稿,在此省略。

》Prompt 2:將前述文章編輯並輸出為 JSON 格式

這個 prompt 指示 AI 扮演一個編輯角色,將已經初步分塊的文本進行整理、命名,並以結構化的 JSON 格式輸出。

prompt 翻譯參考:

你是一位精通數據科學的專業編輯。

我會提供一段已經按邏輯區塊劃分好的編輯後逐字稿,區塊之間使用 -~~~- 分隔。

你的任務是為每個區塊命名,並在可能的情況下重新排列區塊以達到更好的結構。 這也包含了將區塊進一步拆分,或將段落從一個區塊移動到另一個區塊。

請嚴格遵循格式要求,不要添加任何額外格式。

輸出格式必須是可解析的 JSON。請不要在輸出中包含程式碼區塊。

{"blocks": [
  {"title": "<標題>",
   "sentences": ["<句子1>", "<句子2>", ...]},
  {"title": "<標題>",
   "sentences": ["<句子1>", "<句子2>", ...]}
  ...
]}
老師原始 prompt 2
You're a professional editor highly skilled at data science. I give you an edited transcript already broken down by logical blocks, The blocks are separated using -~~~-

Your task is to give each block a name and re-arrange blocks if better arrangement is possible. This includes splitting the blocks further or moving a paragraph from one block to another
Follow the format exactly and don't add any extra formatting.

The output format should be a parsable JSON. Don't include codeblocks in the output.

{"blocks": [
  {"title": "<TITLE>",
   "sentences": ["<SENTENCE1>", "<SENTENCE2>", ...]},
  {"title": "<TITLE>",
   "sentences": ["<SENTENCE1>", "<SENTENCE2>", ...]}
  ...
]}

The text:
以下為步驟一所潤飾後的文章,省略。

》Prompt 3:撰寫用於 RAG 的文本切割 Python 函數

這個 prompt 請 GAI 撰寫 Python 程式碼,用來將長篇文章切割成帶有重疊部分的小區塊,以利於後續的 RAG 使用。

prompt 翻譯參考:

我有一篇文章,我想要將它切割成區塊(chunk),以便用於 RAG。 請為此撰寫一個 Python 函數。

我希望每個區塊的總長度為 240 個單詞(此數值可配置),並有約 20 個單詞的重疊(此數值也可配置)——也就是說,當前區塊應包含前一個區塊的 20 個單詞,以及下一個區塊的 20 個單詞。

老師原始 prompt 3
I have an article and I want to chunk the code so I can use it for RAG. Write a function in Python for doing it

I want the chunks to be each 240 words in total (configurable), and have overlap of ~20 words (also configurable) - i.e. the current chunk should contain 20 words from the previous chunk and 20 from the next.

▌參考資料