在上一章 流程編排 (Flow Orchestration) 中,我們學習到整個自動化教學生成的過程,就像一部精心編導的電影,由一系列定義好的步驟依照特定順序執行。現在,我們要深入探討構成這部電影的每一個獨立「場景」——也就是「節點 (Node)」。
想像一條複雜的汽車生產線。這條生產線並不是由一個工人從頭到尾完成所有事情,而是被拆分成許多獨立的「工作站」。有的工作站負責安裝引擎,有的負責裝配輪胎,有的負責噴漆。每一個工作站都專注於一項特定的任務。
在我們的 PocketFlow-Tutorial-Codebase-Knowledge
專案中,「節點 (Node)」就扮演著類似生產線上工作站的角色。
節點 (Node) 的核心概念: 節點是流程中的獨立工作單元,就像生產線上的不同工作站。每個節點負責一項特定任務,例如「抓取程式碼」、「識別核心概念」或「撰寫章節」。它們接收來自共享狀態 (Shared State)的資訊,執行其任務,然後將結果更新回共享狀態 (Shared State),供後續節點使用。
為什麼我們需要節點?
將一個龐大複雜的任務(例如「從程式碼庫生成完整教學」)拆解成許多小而專一的節點,帶來了許多好處:
在我們的專案中,流程圖裡的每一個方塊都是一個節點:
例如:
FetchRepo
節點的任務就是「抓取程式碼」。IdentifyAbstractions
節點的任務是「從程式碼中識別核心概念」。WriteChapters
節點的任務則是「為每個核心概念撰寫教學章節」。每個節點都像一個辛勤工作的員工,專注於自己的職責,為整個專案的成功貢獻力量。
那麼,一個節點具體是如何完成它的任務呢?在 PocketFlow
框架中,每個節點的運作通常可以被比喻為一位大廚準備一道菜的三個主要階段:prep
(準備階段)、exec
(執行階段)和 post
(收尾階段)。
讓我們用一個序列圖來視覺化這個過程:
prep
(準備階段 - 備料):
# 檔案: nodes.py (某個節點的 prep 方法 - 簡化示意)
class MyNode(Node):
def prep(self, shared):
# 從 shared (共享狀態) 獲取 'input_data'
data_needed = shared.get("input_data")
# 可能還需要獲取其他配置
config = shared.get("node_config")
print(f"節點 {self.name}: 準備階段完成,拿到資料: {data_needed}")
# 將準備好的材料打包,供 exec 使用
return {"ingredients": data_needed, "recipe": config}
這段程式碼展示了 prep
方法如何從 shared
(一個共享的字典,我們會在下一章詳細介紹)中讀取所需的資訊。exec
(執行階段 - 炒菜):
prep
階段準備好的資料和配置來完成工作。# 檔案: nodes.py (某個節點的 exec 方法 - 簡化示意)
class MyNode(Node):
# ... prep 方法 ...
def exec(self, prep_res):
# 從 prep_res (prep 的回傳結果) 中取得材料和食譜
ingredients = prep_res["ingredients"]
recipe = prep_res["recipe"]
print(f"節點 {self.name}: 執行階段開始,使用材料: {ingredients}")
# 執行特定任務,例如處理資料 (簡化)
processed_result = f"處理過的 {ingredients} (依照 {recipe})"
# 回傳處理結果
return processed_result
exec
方法接收 prep
方法的回傳值 (prep_res
),並利用這些資訊來執行實際的運算或操作。post
(收尾階段 - 上菜):
# 檔案: nodes.py (某個節點的 post 方法 - 簡化示意)
class MyNode(Node):
# ... prep 和 exec 方法 ...
def post(self, shared, prep_res, exec_res):
# exec_res 是 exec 方法的回傳結果 (做好的菜)
output_data = exec_res
# 將結果存回 shared (共享狀態) 的 'output_key'
shared["output_key"] = output_data
print(f"節點 {self.name}: 收尾階段完成,結果 '{output_data}' 已存入共享狀態。")
post
方法將 exec
方法的成果 (exec_res
) 寫回到 shared
中,通常會使用一個新的鍵名(例如 "output_key"
)來儲存。在我們的專案 nodes.py
檔案中,你會看到如 FetchRepo
、IdentifyAbstractions
等類別,它們都繼承自 pocketflow.Node
,並且大多都實現了這 prep
、exec
、post
三個方法來定義它們各自的行為。
例如,FetchRepo
節點:
prep
方法中,它會從共享狀態讀取程式碼庫的 URL 或本地路徑、專案名稱等資訊。exec
方法中,它會實際執行抓取程式碼的動作,並回傳抓取到的檔案列表。post
方法中,它會將這個檔案列表存回共享狀態,鍵名為 "files"
。除了上述標準的 Node
之外,PocketFlow
還提供了一種特殊的節點叫做 BatchNode
(批次處理節點)。
想像一下,如果我們有很多相似的小任務要處理,例如:我們識別出了 5 個核心概念,現在需要為這 5 個概念分別撰寫教學章節。如果為每個概念都建立一個獨立的 WriteChapterForConceptA
、WriteChapterForConceptB
... 這樣會很繁瑣。
這時候 BatchNode
就派上用場了!
BatchNode
的作用:它允許你定義一個通用的處理邏輯(例如「撰寫一個章節」),然後將這個邏輯應用到一個列表中的每一項資料上(例如「核心概念列表」中的每一個概念)。在我們的專案中,WriteChapters
節點就是一個 BatchNode
。
prep
階段會從共享狀態拿到一個「待撰寫章節的核心概念列表」(由 OrderChapters
節點產生)。exec
方法會被 PocketFlow
框架為列表中的 每一個 核心概念呼叫一次。在每次呼叫時,exec
方法會專注於為當前的那個核心概念撰寫章節內容。post
階段,所有單獨生成的章節內容會被收集起來,形成一個完整的章節列表,並存回共享狀態。nodes.py
中的 WriteChapters
類別定義如下:
# 檔案: nodes.py (WriteChapters 節點的定義)
# ... 其他匯入 ...
from pocketflow import BatchNode # 注意這裡匯入的是 BatchNode
class WriteChapters(BatchNode): # 繼承自 BatchNode
def prep(self, shared):
# ... 從 shared 準備所有需要撰寫章節的核心概念列表 ...
# 回傳一個包含多個「項目」的列表,每個項目代表一個章節的撰寫任務
items_to_process = [
{"concept_info": concept1, "context": "..."},
{"concept_info": concept2, "context": "..."},
# ...
]
print(f"準備為 {len(items_to_process)} 個概念批次撰寫章節。")
return items_to_process
def exec(self, item): # 注意:這裡的 item 是 prep 回傳列表中一個「項目」
# ... 針對 item["concept_info"] 撰寫一個章節 ...
# (這裡會呼叫大型語言模型來生成文字)
chapter_content = f"這是關於 {item['concept_info']['name']} 的章節內容..."
print(f"已為概念 '{item['concept_info']['name']}' 撰寫章節。")
return chapter_content # 回傳單個章節的內容
def post(self, shared, prep_res, exec_res_list): # 注意:exec_res_list 是所有 exec 回傳結果的列表
# ... 將 exec_res_list (所有章節內容的列表) 存回 shared ...
shared["chapters"] = exec_res_list
print(f"所有 {len(exec_res_list)} 個章節已完成撰寫並存入共享狀態。")
BatchNode
使得處理一系列相似任務變得非常高效和簡潔。
在本章中,我們深入了解了「節點 (Node)」——構成我們自動化流程的基本工作單元。
prep
(準備)、exec
(執行)、post
(收尾)的生命週期,與共享狀態 (Shared State)互動以獲取輸入並儲存輸出。BatchNode
(例如 WriteChapters
) 能夠有效地處理一系列相似的任務。理解了節點的定義和運作方式後,我們可能會好奇:這些節點之間是如何共享資訊的呢?它們從哪裡拿到「食材」,又把「做好的菜」放到哪裡呢?這就引導我們進入下一章的主題:第 3 章:共享狀態 (Shared State)。