在 Obsidian 筆記中善用 LLM

2023 年 9 月 21 日

今天我在駭客新聞 (Hacker News) 上看到一篇關於另一個 Obsidian 外掛程式的文章,這個外掛程式整合了 ChatGPT。市面上有很多這類工具,我很高興看到將它們與 Obsidian 結合使用的各種不同方式。建立連結,讓你的筆記更上一層樓。有些評論者認為這是在做你應該自己做的工作,但我認為它以嶄新且令人難以置信的方式為你賦能。

與你的筆記對話

你可能想做的第一件也是最明顯的事情,就是能夠與你的筆記對話。向它提問以獲得更深入的見解。如果可以簡單地將模型指向你的筆記並完成操作,那就太方便了。但是大多數模型都無法一次接受所有這些內容。

當你提出問題時,並非所有筆記都相關。因此,你需要找到相關的部分並將其交給模型。Obsidian 有搜尋功能,但它只是搜尋確切的詞語和短語,而我們需要搜尋概念。這就是嵌入 (embeddings) 的用武之地。我們必須建立索引。事實證明這非常容易做到。

讓我們建立索引器

當你建立 Obsidian 外掛程式時,你可以讓它在外掛程式載入時執行某些操作,然後在你觸發命令或開啟筆記,或 Obsidian 中的其他活動時執行其他操作。因此,我們需要一些東西在外掛程式啟動時理解你的筆記,並且它應該儲存其進度,這樣它就不必再次重新產生索引。讓我們看一下程式碼範例,為我們的一個筆記建立索引。我將在這個範例中使用 Llama Index,但 LangChain 也是另一個很棒的選擇。

import { VectorStoreIndex, serviceContextFromDefaults, storageContextFromDefaults, MarkdownReader } from "llamaindex";

const service_context = serviceContextFromDefaults({ chunkSize: 256 })
const storage_context = await storageContextFromDefaults({ persistDir: "./storage" });

const mdpath = process.argv[2];
const mdreader = new MarkdownReader();
const thedoc = await mdreader.loadData(mdpath)

首先,我們需要初始化一個記憶體內資料儲存區。這是 Llama Index 附帶的記憶體內儲存區,但 Chroma DB 是另一個受歡迎的選擇。第二行表示我們將持久儲存我們索引的所有內容。接下來,我取得檔案的路徑並初始化一個讀取器 (reader)。然後我讀取檔案。Llama Index 了解 Markdown,因此它會適當地讀取並為其建立索引。它也了解 PDF、文字檔案和 Notion 文件等等。它不僅儲存詞語,還理解詞語的含義以及它們與本文中其他詞語的關係。

await VectorStoreIndex.fromDocuments(thedoc, { storageContext: storage_context, serviceContext: service_context });

現在,這部分正在使用 OpenAI 的一項服務,但它與 ChatGPT 是分開的,不同的模型,不同的產品,Langchain 中也有替代方案可以在本地執行此操作,但速度會稍慢一些。Ollama 也具有嵌入 (embed) 功能。你也可以在雲端超快速的自架伺服器上使用這些服務,然後在索引完成後關閉它。

現在讓我們搜尋我們的筆記

現在我們有了這個檔案的索引。Obsidian 可以為我們提供所有檔案的列表,因此我們可以一遍又一遍地執行它。而且我們正在持久儲存,所以這是一次性操作。現在,我們該如何提問?我們需要一些程式碼來找到筆記中的相關部分,將其交給模型,並使用該資訊來產生答案。

const storage_context = await storageContextFromDefaults({ persistDir: "./storage" });
const index = await VectorStoreIndex.init({ storageContext: storage_context });
const ret = index.asRetriever();
ret.similarityTopK = 5
const prompt = process.argv[2];
const response = await ret.retrieve(prompt);
const systemPrompt = `Use the following text to help come up with an answer to the prompt: ${response.map(r => r.node.toJSON().text).join(" - ")} `

因此,在這個程式碼範例中,我們正在使用我們已處理的內容初始化索引。Retriever.retrieve 行將取得提示 (prompt) 並找到所有相關的筆記區塊,並將文字回傳給我們。我們說在這裡使用前 5 個符合項。因此,我將從我們的筆記中取得 5 個文字區塊。有了這些原始資訊,我們可以產生一個系統提示 (system prompt),以幫助我們的模型了解在我們提出問題時該怎麼做。

const ollama = new Ollama();
ollama.setModel("llama2");
ollama.setSystemPrompt(systemPrompt);
const genout = await ollama.generate(prompt);

現在我們可以使用模型了。我正在使用幾天前建立的一個函式庫,它在 npm 上。我可以將模型設定為使用 llama2,它已經使用命令 ollama pull llama2 下載到我的機器上。你可以嘗試不同的模型,找到最適合你的模型。

為了快速獲得回覆,你會希望堅持使用小型模型。但你也希望模型具有足夠大的輸入上下文大小,以接受我們所有的文字區塊。我最多有 5 個區塊,每個區塊 256 個 token。我將模型設定為使用包含我們文字區塊的系統提示 (System Prompt)。只需提出問題,它就會在幾秒鐘內給你答案。

太棒了。現在,我們的 Obsidian 外掛程式將適當地顯示該答案。

我們還可以做什麼?

你也可以考慮摘要文字或找到與你的文字最匹配的關鍵字,並將它們新增到 front matter,這樣你就可以在筆記之間建立更好的連結。我嘗試過製作 10 個好的問題和答案發送到 Anki。你會想要嘗試不同的模型和提示 (prompts) 來完成這些不同的事情。更改提示 (prompts),甚至將模型權重更改為更適合任務的權重,都非常容易。

我希望這篇文章為你提供了一些關於如何為 Obsidian 或任何其他筆記工具建立下一個偉大外掛程式的想法。使用最新的本地 AI 工具,例如你可以在 ollama.com 找到的那些工具,這種力量輕而易舉,我希望你能展示你正在做的事情。