1. Greatest notes format
平常我就有作筆記的習慣,其中大部份的筆記都是非結構化的文字檔,部份是 markdown 格式,這樣在任何平台只要有文字編輯器,就能很方便檢視及編輯,而不必依賴特定的應用程式。
久了就慢慢累積了不少資料,有日記、工作日誌、筆記、雜記等等。為了方便檢視,我也作了一個小 app,可以讓我在一個畫面內快速檢視我的筆記列表,再點入去作進一步的檢視各別資料或統計資訊等。
雖說這些筆記幾乎是非結構化的,但其實還是有少量的規則,這樣我的程式才能作簡單的解析。例如日記或工作日誌基於日期的資料,至少有個日期標題和不定長度的內文,或是筆記類的資料至少也有個標題和不定長度的內文,等等。
漸漸的我盡量把類似性質的筆記的格式統一起來,最後我發現其實可以用一個很簡單的格式全部統一。它就是 Greatest notes 格式。
Greatest notes 文件是文字檔。內容由不定數量的項目組成,每個項目由一個標題和不定長度的內文組成。每一個項目由每一行開頭的">>> "開始,直到下一個">>> "或者是檔尾,就是一個項目。
例如:
>>> 這是一則筆記
內文
內文
內文
...
>>> 另一則筆記
內文
內文
...格式很簡單,要寫一個解析器也很簡單。因為只需要 re 以 ">>> " 對整個檔案切割,再從每個項目的第一行取出標題,其餘為內容。
例如下面就是一段可以解析 Greatest notes 文件的 javascript 程式。
// javascript
let notes = raw.split(/^>>> /gm).map(note => {
let i = note.indexOf('\n');
let title = note.substring(0, i);
let content = note.substring(i + 1);
return {
title: title !== '' ? title : '未命名筆記',
content: content
};
}).filter(note => note.content !== ''); // Remove empty notes.因為這是一個極簡的格式,只以 ">>> " 對檔案內容作區隔。缺少巢狀結構和 metadata 等。但實際應用時,能在標題帶入某種簡單的格式作為 metadata 的額外補充。例如日記或日誌等資料,可以在標題中統一以一個日期開始等。
有了這個簡單的格式,要建立筆記就方便了。在標題加上少量的自定義 metadata 就能進一步增強文件的結構,有利於進一步整理及資料分析。
2. Greatest notes compiler
近幾年 LLM 崛起,我也和大家一樣利用 AI 輔助探索了許多感興趣或遭遇到的問題。
OpenAI ChatGPT 有個對我很方便的地方是,可以容易的把整個對話資料匯出備份。匯出的資料全部被打包成一個 zip,下載後解壓。裡面有一個對話資料有關的 json 檔案。
後來我利用 AI 輔助,寫了一個讀取並解析此檔的工具程式,可以在電腦上的 browser 檢視。之所以會另外作一個工具檢視,而不是只使用官方的 app。是因為我常常會回頭檢視歷史紀錄,而當對話很長時,檢視已經變得很不方便。我作的小工具主要是為了提供一個大綱模式,讓我可以快速跳至指定段落,方便閱讀。
因為前面我已經發展出 Greatest notes 格式和手機檢視小工具,所以很自然的,我也把 ChatGPT 的 json 格式的對話紀錄轉成 Greatest notes 格式,並匯入我的小工具,在手機上方便隨時檢閱。
但是長對話的問題還是存在。超長的文件不管用什麼方式檢視都是個問題。就算我把那些問答式的對話,或是些無關緊要或價值不大的對話刪除,轉換成 Greatest notes 後,許多對話都有幾萬字,整個文件將近一百萬字。
基於這個原因,我才發展出 Greatest notes compiler,可以把 Greatest notes 文件內容壓縮,提取摘要和重點,過濾掉不是重點的內容。最後一樣輸出另一個 Greatest notes,這樣我可以互相比對參考。
2.1 Greatest notes compiler pipeline
Greatest notes 編譯器是一個 pipeline 流水線,類似一般的程式語言的編譯和連結流程。
load_greatest_notes
↓ 載入 Greatest notes
do_compile
↓ 對個別 note 編譯,輸出個別 json
merge
↓ 合併所有 json,輸出最終 Greatest notes
完成2.1.1 編譯流程
編譯流程首先載入 Greatest notes 後,分別對個別的 note 執行編譯。每個 note 編譯完成後會個別輸出一個 temp 的 json 檔案,類似編譯 c/c++ 程式時,會產生中間的 obj 檔案。分成幾個步驟如下:
compile_note
↓ 對單一則 note 編譯
chunk_text
↓ 對 note 以固定長度作切割
summarize_chunk
↓ 對 chunk 內容作摘要提取
extract_claims
↓ 對 chunk 內容作要點提取
完成2.1.1.1 chunk_text
chunk_text 主要目的是對輸入的 note 內文作切割,避免單一 note 內文過大,造成後續編譯步驟出問題。
切割的方式很簡單很暴力,就是以固定長度對輸入文字切割。目前以 chunk_size = 2000 作區段大小處理。雖然用這麼簡單又暴力的方式切割文字,但實測結果並沒有什麼明顯的問題,所以也就暫時都這樣作了。
2.1.1.2 summarize_chunk
summarize_chunk 利用 LLM 的語言能力對輸入文字作摘要提取。目前使用的模型是 gemma3:12b-it-q4_K_M,測試結果對我而言很好。
提示詞如下:
Summarize the following conversation chunk.
只用繁體中文回答.
Return JSON:
{{
"summary": "...",
"main_points": []
}}
Text:
{text}2.1.1.3 extract_claims
extract_claims 一樣利用 gemma3:12b-it-q4_K_M 作為輸入文字的重點提取引擎。
提取出來的重點除了文字以外還有一個 type,這是跟未來替編譯流程加上下一步驟有關:建立重點之間的關聯以建立知識圖譜。
提示詞如下:
Extract atomic claims from the text.
Constraints:
- Each claim must represent a DISTINCT semantic proposition.
- Do not produce multiple claims that can be merged.
- If two sentences imply the same normative rule, merge them.
- Prefer canonical, abstract, policy-level phrasing.
- Maximum 10 claims per chunk.
- Remove rhetorical emphasis.
- No restatements.
- 只用繁體中文回答.
If the text repeats an idea, output it only once.
Return JSON:
{{
"claims": [
{{
"text": "...",
"type": "normative | descriptive | inference"
}}
]
}}
Text:
{text}2.1.2 連結流程
連結流程比較簡單,也是類似 c/c++ 程式最後的 link 動作將所有 objs/libs 等連結成一個 exe 或 dll 等。而 Greatest notes compiler 的連結動作則是把所有個別 note 編譯成功後輸出的 json 檔,全部合拼再輸出成一個 Greatest notes 文件。
下面是編譯後的一篇 note 的範例。



留言
張貼留言