歡迎來到 open-notebook
教學系列的第七章!在上一章 提示詞管理器 (Prompter) 中,我們學會了如何像使用信件模板一樣,有效地管理和生成給 AI 模型(LLM)的指令(提示詞)。Prompter
確保了我們能清晰地告訴 AI 我們希望它做什麼以及如何格式化輸出。
現在,想像一下,您剛剛用 open-notebook
添加了一篇很長的網路文章作為來源。您可能希望系統能自動幫您做一些常見的處理,例如:
如果每次添加新來源後,都要手動去聊天介面輸入指令來做這些事情,會很重複也很麻煩。我們希望能有一種方法,可以預先定義好這些常用的文字處理任務,然後在需要時輕鬆地套用它們。
這就是「轉換 (Transformations)」的設計目的!
我們希望 open-notebook
不僅能儲存資訊,還能主動地幫助我們處理和理解這些資訊。對於文字內容,有很多常見的 AI 處理需求,像是摘要、提取關鍵字、翻譯、改寫風格等等。
如果沒有一個統一的方式來定義和管理這些常見任務,我們可能會:
「轉換 (Transformations)」提供了一種標準化的方式來定義、管理和應用這些可重複使用的 AI 文字處理任務。
轉換 (Transformations) 是您可以定義的一系列特定的 AI 文字處理任務,可以應用於您的來源內容。您可以把它們想像成一套為您的文字內容準備的濾鏡:
每個「轉換」濾鏡本身儲存了實現特定效果所需的所有資訊:
name
: 一個簡短的內部識別名稱 (例如 summarize_short
)。title
: 一個顯示在使用者介面上的友善標題 (例如 簡短摘要
)。description
: 一段簡短的描述,解釋這個轉換會做什麼 (例如 產生一段簡短的摘要
)。prompt
: 最核心的部分!這是一個詳細的提示詞模板,指示 AI 模型如何處理輸入的文字。這個模板會透過我們在上一章學習的 提示詞管理器 (Prompter) 來使用。apply_default
: 一個開關 (布林值),決定當新增來源時,是否預設就建議套用這個轉換。重要的是,Transformation
物件本身也繼承自 物件模型 (ObjectModel)。這意味著每個轉換都可以像筆記本或筆記一樣,被儲存到資料庫、從資料庫讀取、更新或刪除。
# open_notebook/domain/transformation.py (簡化示意)
from typing import ClassVar
from open_notebook.domain.base import ObjectModel
class Transformation(ObjectModel): # 繼承自 ObjectModel
table_name: ClassVar[str] = "transformation" # 對應資料庫表格
name: str # 內部名稱
title: str # UI 顯示標題
description: str # 描述
prompt: str # 核心:給 AI 的提示詞模板
apply_default: bool # 是否預設套用
這樣一來,我們可以輕鬆地管理這些可重複使用的 AI 處理任務。
使用「轉換」主要涉及兩個方面:定義管理轉換,以及在流程中應用轉換。
open-notebook
提供了一個使用者介面(位於 pages/8_💱_Transformations.py
),讓您可以輕鬆地建立、編輯和刪除轉換。
在這個頁面上,您可以:
name
、title
、description
。prompt
欄位中,詳細撰寫您希望 AI 如何處理文字的指令(這就是提示詞模板)。您可以參考提供的範例或外部資源。apply_default
如果希望它成為預設選項。# pages/8_💱_Transformations.py (簡化 UI 邏輯示意)
import streamlit as st
from open_notebook.domain.transformation import Transformation
# ... (載入所有 transformations) ...
# 顯示建立新轉換的按鈕
if st.button("Create new Transformation"):
# 建立一個預設的 Transformation 物件
new_trans = Transformation(
name="New Name", title="New Title", prompt="", apply_default=False
)
# ... (將 new_trans 加入 session_state 列表以便顯示) ...
# 遍歷顯示每個 transformation 的編輯介面
# for transformation in st.session_state.transformations:
# with st.expander(transformation.name):
# # 使用 st.text_input, st.text_area 等讓使用者編輯屬性
# name = st.text_input("Name", transformation.name)
# prompt = st.text_area("Prompt", transformation.prompt, height=300)
# apply_default = st.checkbox("Suggest by default", ...)
# if st.button("Save"):
# # 更新 transformation 物件的屬性
# transformation.name = name
# transformation.prompt = prompt
# # ...
# transformation.save() # 使用 ObjectModel 的 save() 儲存
# st.toast("Saved!")
程式碼解釋:
Transformation
物件互動。Transformation
物件的屬性,然後呼叫從 物件模型 (ObjectModel) 繼承來的 save()
方法,將這些設定儲存到資料庫中。轉換通常是在處理新來源時自動應用的,或者可以在之後手動觸發。open-notebook
使用 LangGraph 狀態機 (Graph Workflows) 來編排這個過程,特別是在 open_notebook/graphs/source.py
中定義的 source_graph
。
當一個新來源(例如,一篇網路文章)被加入並透過 source_graph
處理時:
Source
物件被儲存到資料庫。source_graph
中的 trigger_transformations
節點會檢查需要應用哪些轉換(可能是預設勾選的,或是使用者特別選擇的)。trigger_transformations
會發送一個任務給 transform_content
節點。LangGraph 會並行處理這些任務。transform_content
節點會接收來源物件 (source
) 和單個轉換物件 (transformation
)。然後,它會呼叫另一個更小的、專門執行單一轉換的 LangGraph 圖 (transform_graph
)。transform_graph
返回轉換後的文字(例如摘要)。transform_content
節點會將這個結果附加到 Source
物件上(通常作為一個「洞見」(insight))。# open_notebook/graphs/source.py (簡化 - 觸發和執行轉換的部分)
from open_notebook.domain.transformation import Transformation
from open_notebook.graphs.transformation import graph as transform_graph
from langgraph.types import Send
# 條件邊函式:決定要觸發哪些轉換任務
def trigger_transformations(state: SourceState, config: RunnableConfig) -> List[Send]:
# state["apply_transformations"] 包含要應用的 Transformation 物件列表
to_apply = state["apply_transformations"]
if not to_apply: return [] # 如果沒有要應用的,就返回空列表
# 為每個要應用的轉換建立一個 Send 指令
return [
Send( # 指示 LangGraph 呼叫 "transform_content" 節點
"transform_content",
{ # 傳遞給該節點的資料
"source": state["source"], # 來源物件
"transformation": t, # 當前這個 Transformation 物件
},
)
for t in to_apply # 遍歷所有要應用的轉換
]
# 執行單一轉換的節點函式
async def transform_content(state: TransformationState) -> Optional[dict]:
source = state["source"]
transformation: Transformation = state["transformation"]
content_to_transform = source.full_text # 取得來源的完整文字
# 呼叫專門的轉換圖 (transform_graph)
result = await transform_graph.ainvoke(
{ # 傳遞輸入文字和轉換物件給 transform_graph
"input_text": content_to_transform,
"transformation": transformation
}
)
# 將轉換結果 (例如摘要) 儲存回來源物件
source.add_insight(transformation.title, result["output"])
# ... (返回狀態更新) ...
return {"transformation": [{"output": result["output"], ...}]}
# 在 SourceStateGraph 中定義邊
# workflow.add_conditional_edges("save_source", trigger_transformations, ["transform_content"])
# workflow.add_edge("transform_content", END)
程式碼解釋:
trigger_transformations
:這個函式根據狀態中 apply_transformations
列表的內容,決定需要執行多少個轉換任務。它為每個任務產生一個 Send
指令,告訴 LangGraph 去呼叫 transform_content
節點,並傳遞必要的資料(source
和 transformation
)。transform_content
:這個節點接收來源和單個轉換物件。它不直接執行 AI 呼叫,而是將任務委派給另一個更專注的圖——transform_graph
。它呼叫 transform_graph.ainvoke()
並傳遞輸入文字和轉換物件。source.add_insight(...)
:當 transform_graph
完成並返回結果後,transform_content
將這個結果(例如摘要)儲存回 Source
物件中。這個流程展示了如何將預先定義好的「轉換」無縫整合到自動化的內容處理工作流程中。
那麼,那個被呼叫的 transform_graph
(定義在 open_notebook/graphs/transformation.py
)內部又是如何運作的呢?這個圖相對簡單,基本上只有一個核心節點 run_transformation
。
transform_graph.ainvoke
)當 transform_content
節點呼叫 transform_graph.ainvoke({"input_text": ..., "transformation": ...})
時:
transform_graph
接收到包含 input_text
(原始文字)和 transformation
(要應用的 Transformation
物件)的狀態。run_transformation
): 圖中唯一的節點 run_transformation
開始執行。transformation
物件中取得核心的提示詞模板 (transformation.prompt
)。DefaultPrompts
物件讀取一些通用的前置指令,加在特定提示詞模板的前面。input_text
等資料傳遞給 Prompter
,讓 Jinja2 引擎將這些資料填入提示詞模板。provision_langchain_model
這個輔助函式(內部會用到 模型管理器 (ModelManager))來獲取一個設定好的語言模型 (LLM)。input_text
(通常作為 HumanMessage) 一起發送給 LLM。run_transformation
節點將 LLM 返回的結果放入狀態的 output
欄位。END
,transform_graph
將包含 output
的最終狀態返回給呼叫者(即 source_graph
中的 transform_content
節點)。以下是一個簡化的序列圖,展示了 transform_graph
的執行過程:
transform_graph
的核心程式碼片段讓我們看看 open_notebook/graphs/transformation.py
中定義這個簡單圖形的關鍵程式碼:
# open_notebook/graphs/transformation.py (簡化)
from langgraph.graph import END, START, StateGraph
from typing_extensions import TypedDict
from open_notebook.domain.transformation import Transformation # 匯入 Transformation 模型
from open_notebook.prompter import Prompter # 匯入 Prompter
# 匯入模型配置和訊息相關工具
from open_notebook.graphs.utils import provision_langchain_model
from langchain_core.messages import HumanMessage, SystemMessage
# 定義轉換圖的狀態
class TransformationState(TypedDict):
input_text: str # 輸入的原始文字
transformation: Transformation # 要應用的轉換物件
output: str # AI 輸出的結果
# 執行轉換的核心節點函式
def run_transformation(state: dict, config: RunnableConfig) -> dict:
# 從狀態中獲取輸入文字和轉換物件
content = state.get("input_text")
transformation: Transformation = state["transformation"]
# 獲取轉換的核心提示詞模板
transformation_prompt_text = transformation.prompt
# ... (這裡可能加入 DefaultPrompts 的通用指令) ...
# 使用 Prompter 準備最終的系統提示詞
# Prompter 會自動處理模板渲染
system_prompt = Prompter(prompt_text=transformation_prompt_text).render(data=state)
# 準備發送給 LLM 的訊息 (包含系統提示詞和原始輸入)
payload = [
SystemMessage(content=system_prompt),
HumanMessage(content=content)
]
# 獲取並設定 LLM (使用輔助函式,內部可能用到 ModelManager)
chain = provision_langchain_model(
str(payload), # 可能用於選擇模型
config.get("configurable", {}).get("model_id"), # 允許外部傳入模型 ID
"transformation", # 任務類型標籤
# ... 其他模型設定 ...
)
# 呼叫 LLM 執行轉換
response = chain.invoke(payload)
# 返回包含輸出結果的字典,更新狀態
return {
"output": response.content,
}
# 建立 LangGraph 狀態圖
agent_state = StateGraph(TransformationState)
# 新增唯一的節點 "agent"
agent_state.add_node("agent", run_transformation)
# 設定流程:從 START -> agent -> END
agent_state.add_edge(START, "agent")
agent_state.add_edge("agent", END)
# 編譯圖
graph = agent_state.compile()
程式碼解釋:
TransformationState
: 定義了在這個簡單圖中流動的狀態,包含輸入文字、轉換物件和最終輸出。run_transformation
函式:state
。state["transformation"]
取得 Transformation
物件,並從中取出 prompt
。Prompter(prompt_text=...).render(data=state)
來生成最終的 system_prompt
。注意這裡直接用了 prompt_text
而不是 prompt_template
,展示了 Prompter
的靈活性。payload
,包含 SystemMessage
(AI 的角色和指令)和 HumanMessage
(要處理的原始文字)。provision_langchain_model
來獲取配置好的 LLM 鏈 (chain
)。chain.invoke(payload)
將任務發送給 LLM。response.content
包裝在字典中返回,以更新狀態的 output
欄位。StateGraph
建立了一個圖,只添加了 run_transformation
這個節點,並設定了從 START
到 agent
再到 END
的簡單流程,最後編譯成可執行的 graph
物件。在本章中,我們深入了解了「轉換 (Transformations)」,這是 open-notebook
中定義和重用 AI 文字處理任務的機制。
source_graph
)中被觸發和應用的,通常是在處理新來源內容之後。transform_graph
) 的內部運作,它如何使用 提示詞管理器 (Prompter) 準備指令,並呼叫 AI 模型來完成實際的文字處理。透過「轉換」,open-notebook
提供了一種強大且靈活的方式,讓使用者可以自訂和自動化各種 AI 文字處理任務,豐富筆記內容並從中提取更多價值。
到目前為止,我們已經探討了 open-notebook
的許多核心概念:資料結構 (物件模型 (ObjectModel))、AI 模型管理 (模型管理器 (ModelManager))、使用者互動 (使用者介面 (Streamlit UI))、自動化流程 (內容處理流程, LangGraph 狀態機)、AI 指令生成 (提示詞管理器 (Prompter)) 以及可重用的 AI 任務 (轉換)。
所有這些元件都需要一個可靠的底層機制來儲存和讀取資料——筆記本、筆記、來源、模型設定、轉換定義等等。這就需要一個與資料庫溝通的橋樑。
在下一章,也是我們教學系列的最後一章,我們將深入了解 資料庫儲存庫 (Database Repository),看看 open-notebook
是如何與資料庫(特別是 SurrealDB)互動,實現資料的持久化儲存和檢索。