Chapter 7: 教學文件組合 (Tutorial Combination) (value in Traditional chinese)

恭喜您來到 PocketFlow-Tutorial-Codebase-Knowledge 教學系列的最後一章!在上一章 大型語言模型互動 (LLM Interaction) 中,我們探索了專案如何利用大型語言模型(LLM)的智慧來分析程式碼和生成內容。我們已經逐步收集了所有必要的組件:專案摘要、核心概念之間的關係圖、以及為每個概念精心撰寫的獨立教學章節。

現在,萬事俱備,只欠東風。這些寶貴的資訊和內容仍然是分散的片段。我們如何將它們組織起來,變成一份結構完整、易於導覽、可供學習者直接使用的教學文件呢?這就是「教學文件組合 (Tutorial Combination)」的任務所在。

什麼是教學文件組合?書籍的排版與印刷!

想像一下,您寫完了一本書的所有章節,也畫好了插圖,還有了書籍的簡介。接下來,您需要將這些材料交給出版社進行最後的排版、設計封面、印刷和裝訂,最終才能得到一本可以發行的書籍。

教學文件組合 (Tutorial Combination) 的核心概念: 這是教學生成的最後一道工序,好比是書籍的排版和印刷。它會收集所有先前生成的獨立章節內容(Markdown 格式)、專案摘要以及抽象概念之間的關係圖,然後將它們組合成一個結構完整、帶有導覽的教學網站(實際上是一系列相互連結的 Markdown 文件,存放在一個特定資料夾中)。

這個步驟確保了我們辛勤工作的成果能夠以一種專業、有條理且方便使用者閱讀的形式呈現出來。如果沒有這個步驟,我們就只有一堆零散的文字檔案,學習者將難以系統地學習。

主要使用案例: 我們已經透過前面的流程,在共享狀態 (Shared State) 中儲存了:

「教學文件組合」會利用這些資料,在指定的輸出目錄下建立一個以專案名稱命名的資料夾。資料夾內會有一個 index.md 首頁,包含專案摘要、關係圖和指向各個章節的連結。同時,每個獨立的章節內容也會被存成各自的 .md 檔案。

教學文件組合的關鍵任務

負責這項最終組裝任務的節點 (Node)CombineTutorial。它的關鍵職責包括:

  1. 建立輸出目錄:根據使用者指定的路徑和專案名稱,建立一個存放最終教學文件的資料夾。
  2. 生成關係圖:利用共享狀態 (Shared State)中儲存的抽象概念及其關係資料(概念名稱和關係標籤可能已翻譯),動態生成一個 Mermaid 格式的流程圖,視覺化地展示概念間的互動。
  3. 撰寫首頁 (index.md)
    • 包含專案的高層次摘要(可能已翻譯)。
    • 嵌入先前生成的 Mermaid 關係圖。
    • 提供一個指向原始程式碼庫的連結(如果有的話)。
    • 列出所有教學章節的標題(可能已翻譯)和指向對應 Markdown 檔案的連結。
  4. 儲存獨立章節檔案:將共享狀態 (Shared State)中儲存的每一篇章節內容(Markdown 字串,可能已翻譯)寫入到各自的 .md 檔案中。檔案名稱會根據章節順序和章節標題(可能已翻譯,並經過無害化處理)來命名,例如 01_流程編排.md (實際檔名會將中文轉為底線或拼音)。
  5. 添加固定文本:在 index.md 和每個章節檔案的末尾,添加固定的英文文本,例如 "Chapters", "Source Repository" (在 index.md 中) 以及專案的署名連結 (attribution footer)。根據設計,這些固定文本保持英文。

CombineTutorial 節點如何運作?最後的組裝大師!

CombineTutorial 是我們流程編排 (Flow Orchestration)中的最後一個節點 (Node)。它負責將所有成果匯集起來。

輸入 (從共享狀態讀取): 在其 prep(準備)階段,CombineTutorial 節點會從共享狀態 (Shared State)中讀取以下關鍵資訊:

輸出 (寫入共享狀態): 在其 post(收尾)階段,CombineTutorial 節點會將最終教學文件存放的資料夾路徑寫入共享狀態 (Shared State)

運作流程示意圖:

sequenceDiagram participant PocketFlow框架 participant CombineTutorial節點 participant 共享狀態 PocketFlow框架->>CombineTutorial節點: 請執行 prep(共享狀態) activate CombineTutorial節點 CombineTutorial節點->>共享狀態: (prep) 讀取 project_name, relationships, chapters 等所有成果 共享狀態-->>CombineTutorial節點: 回傳資料 Note right of CombineTutorial節點: (prep) 準備 index.md 內容 (含Mermaid圖)
準備各章節檔案名稱與內容
定義輸出資料夾路徑 CombineTutorial節點-->>PocketFlow框架: (prep) 材料準備完成 (回傳 prep_res) deactivate CombineTutorial節點 PocketFlow框架->>CombineTutorial節點: 請執行 exec(prep_res) activate CombineTutorial節點 Note right of CombineTutorial節點: (exec) 建立輸出資料夾
寫入 index.md
寫入所有章節 .md 檔案 CombineTutorial節點-->>PocketFlow框架: (exec) 所有檔案寫入完成 (回傳輸出路徑 exec_res) deactivate CombineTutorial節點 PocketFlow框架->>CombineTutorial節點: 請執行 post(共享狀態, prep_res, exec_res) activate CombineTutorial節點 CombineTutorial節點->>共享狀態: (post) 將最終輸出路徑 (exec_res) 存入 shared["final_output_dir"] 共享狀態-->>CombineTutorial節點: 儲存成功 CombineTutorial節點-->>PocketFlow框架: 所有工作完成,教學文件已生成! deactivate CombineTutorial節點

內部實作探秘:CombineTutorialprep, exec, post

讓我們更深入地了解 CombineTutorial 節點是如何透過其三個主要方法來完成組裝工作的。

prep 方法:準備所有組裝材料

CombineTutorialprep 方法是組裝工作的藍圖規劃階段。它不實際寫入檔案,而是將所有要寫入的內容和檔案結構準備好。

# 檔案: nodes.py (CombineTutorial 節點的 prep 方法 - 簡化示意)
class CombineTutorial(Node):
    def prep(self, shared):
        project_name = shared["project_name"]
        output_base_dir = shared.get("output_dir", "output")
        output_path = os.path.join(output_base_dir, project_name) # 最終輸出資料夾路徑

        relationships_data = shared["relationships"] # 包含已翻譯的摘要和關係標籤
        chapter_order = shared["chapter_order"]
        abstractions = shared["abstractions"] # 包含已翻譯的概念名稱
        chapters_content = shared["chapters"] # 包含已翻譯的章節內容
        repo_url = shared.get("repo_url")

        # --- 產生 Mermaid 圖表 ---
        mermaid_lines = ["flowchart TD"]
        for i, abstr in enumerate(abstractions):
            node_id = f"A{i}"
            # 使用已翻譯的概念名稱,並做簡單的無害化處理
            sanitized_name = abstr["name"].replace('"', "") 
            mermaid_lines.append(f'    {node_id}["{sanitized_name}"]') # 節點標籤使用已翻譯名稱
        
        for rel in relationships_data["details"]:
            from_node_id = f"A{rel['from']}"
            to_node_id = f"A{rel['to']}"
            # 使用已翻譯的關係標籤,並做無害化處理和長度限制
            edge_label = rel["label"].replace('"', "").replace("\n", " ") 
            # ... (省略標籤長度修剪邏輯) ...
            mermaid_lines.append(f'    {from_node_id} -- "{edge_label}" --> {to_node_id}') # 邊標籤使用已翻譯標籤
        mermaid_diagram = "\n".join(mermaid_lines)

        # --- 準備 index.md 內容 ---
        index_content = f"# Tutorial: {project_name}\n\n"
        index_content += f"{relationships_data['summary']}\n\n" # 使用已翻譯的摘要
        if repo_url: # 固定的 "Source Repository" 標籤 (英文)
            index_content += f"**Source Repository:** [{repo_url}]({repo_url})\n\n"
        
        index_content += "```mermaid\n" # 嵌入 Mermaid 圖
        index_content += mermaid_diagram + "\n"
        index_content += "```\n\n"
        
        index_content += f"## Chapters\n\n" # 固定的 "Chapters" 標題 (英文)

        chapter_files_to_write = [] # 儲存要寫入的章節檔案資訊
        for i, abstraction_index in enumerate(chapter_order):
            abstraction_name = abstractions[abstraction_index]["name"] # 已翻譯的概念名稱
            # 根據已翻譯的概念名稱產生無害化檔名
            safe_name = "".join(c if c.isalnum() else "_" for c in abstraction_name).lower()
            # 如果 safe_name 因中文而變成空或只有底線,可能需要備用策略,但目前程式碼是這樣
            # 例如 "流程編排" 會變成 "__" 或類似,取決於isalnum對中文的處理
            filename = f"{i+1:02d}_{safe_name if safe_name else f'chapter_{i+1}'}.md" # 確保檔名不為空

            index_content += f"{i+1}. [{abstraction_name}]({filename})\n" # 連結文字使用已翻譯名稱

            chapter_md_content = chapters_content[i] # 已翻譯的章節內容
            # 添加固定的英文署名
            chapter_md_content += f"\n\n---\n\n"
            chapter_files_to_write.append({"filename": filename, "content": chapter_md_content})
        
        # 為 index.md 添加固定的英文署名
        index_content += f"\n\n---\n\n"

        return {
            "output_path": output_path,
            "index_content": index_content,
            "chapter_files": chapter_files_to_write,
        }

prep 方法中,節點精心準備了 index.md 的完整內容(包括標題、摘要、Mermaid 圖和章節連結),以及一個包含所有獨立章節檔案名稱和其對應內容的列表。檔案名稱是根據章節順序和無害化處理後的章節名稱(可能已翻譯)生成的。例如,如果一個章節的(已翻譯)名稱是「流程編排」,經過 safe_name 處理後,檔名可能會變成像 01____.md01_liucheng_bianpai.md(如果系統進行了拼音轉換,但目前程式碼只做簡單的非字母數字字元替換)。連結的顯示文字則會是原始的(已翻譯)章節名稱。

exec 方法:執行檔案寫入

exec 方法接收 prep 方法準備好的材料,並實際執行檔案系統操作——建立資料夾和寫入檔案。

# 檔案: nodes.py (CombineTutorial 節點的 exec 方法 - 簡化示意)
class CombineTutorial(Node):
    # ... prep 方法 ...
    def exec(self, prep_res):
        output_path = prep_res["output_path"]     # 例如 "output/MyProjectName"
        index_content = prep_res["index_content"] # 準備好的 index.md 內容
        chapter_files = prep_res["chapter_files"] # 包含 {"filename": ..., "content": ...} 的列表

        print(f"開始組合教學文件至資料夾: {output_path}")
        os.makedirs(output_path, exist_ok=True) # 建立輸出資料夾,如果已存在則不報錯

        # 寫入 index.md
        index_filepath = os.path.join(output_path, "index.md")
        with open(index_filepath, "w", encoding="utf-8") as f:
            f.write(index_content)
        print(f"  - 已寫入 {index_filepath}")

        # 寫入所有章節檔案
        for chapter_info in chapter_files:
            chapter_filepath = os.path.join(output_path, chapter_info["filename"])
            with open(chapter_filepath, "w", encoding="utf-8") as f:
                f.write(chapter_info["content"])
            print(f"  - 已寫入 {chapter_filepath}")

        return output_path # 回傳最終教學文件所在的資料夾路徑

這個方法的核心就是使用 Python 的檔案操作功能,將 prep 階段準備好的內容一一寫入到目標資料夾中。

post 方法:宣告任務完成

post 方法非常簡單,它只是將 exec 方法回傳的最終輸出路徑儲存到共享狀態 (Shared State)中,並打印一條完成訊息。

# 檔案: nodes.py (CombineTutorial 節點的 post 方法 - 簡化示意)
class CombineTutorial(Node):
    # ... prep 和 exec 方法 ...
    def post(self, shared, prep_res, exec_res):
        # exec_res 就是 exec 方法回傳的 output_path
        shared["final_output_dir"] = exec_res 
        print(f"\n教學文件生成完畢!檔案位於: {exec_res}")

至此,整個自動化教學生成流程就圓滿結束了!

最終成果:一份完整的教學文件

CombineTutorial 節點執行完畢後,您會在指定的輸出目錄下(例如 output/YourProjectName/)找到一個包含以下內容的資料夾:

這樣,學習者就可以從 index.md 開始,輕鬆地瀏覽整個教學內容了。

總結與展望

在本章中,我們深入了解了「教學文件組合 (Tutorial Combination)」這個畫龍點睛的步驟。它由 CombineTutorial 節點 (Node) 負責,將所有先前生成的元素——摘要、關係圖資料、獨立章節——巧妙地編織成一個結構化、可導覽的教學文件集。

至此,我們已經完成了 PocketFlow-Tutorial-Codebase-Knowledge 專案所有核心概念的學習之旅!從最初的流程編排 (Flow Orchestration),到節點 (Node)的定義、共享狀態 (Shared State)的資訊傳遞,再到具體的程式碼擷取 (Code Fetching)教學章節生成 (Chapter Generation)(其中涉及了大型語言模型互動 (LLM Interaction)),最後到本章的「教學文件組合」,我們一步步揭開了自動化程式碼庫知識教學生成的神秘面紗。

希望本系列教學能幫助您理解這個專案的運作原理,並激發您探索更多自動化和 AI 輔助開發的可能性!感謝您的學習!