利用 LLM 在你的 Obsidian 笔记中进行操作
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 也是一个流行的选择。第二行表示我们将持久化我们索引的所有内容。接下来,我获取文件的路径并初始化一个读取器。然后我读取文件。Llama Index 了解 Markdown,因此它会以适当的方式读取并索引它。它还了解 PDF、文本文件和 Notion 文档等等。它不仅存储单词,还理解单词的含义以及它们与其他文本中的单词的关系。
await VectorStoreIndex.fromDocuments(thedoc, { storageContext: storage_context, serviceContext: service_context });
现在,这部分使用来自 OpenAI 的服务,但它与 ChatGPT 分开,不同的模型,不同的产品,并且 Langchain 中有替代方案可以在本地执行此操作,但速度会慢一些。Ollama 也有一个嵌入函数。你也可以在超快速的自托管云实例上使用这些服务,然后在索引完成后关闭它。
现在让我们搜索我们的笔记
现在我们有了这个文件的索引。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 行将获取提示并找到所有相关的笔记块,并将文本返回给我们。我们说过要使用前 5 个匹配项。所以我会从我们的笔记中获得 5 个文本块。有了这些原始信息,我们可以生成一个系统提示,帮助我们的模型知道在提出问题时该怎么做。
const ollama = new Ollama();
ollama.setModel("llama2");
ollama.setSystemPrompt(systemPrompt);
const genout = await ollama.generate(prompt);
现在我们可以使用模型了。我几天前创建了一个库,它在 npm 上。我可以将模型设置为使用 llama2,该模型已经使用 ollama pull llama2 命令下载到我的机器上。你可以尝试不同的模型来找到最适合你的模型。
为了快速获得答案,你希望坚持使用一个小模型。但你也需要一个输入上下文大小足以接受我们所有文本块的模型。我最多有 5 个文本块,每个文本块有 256 个 token。我将模型设置为使用包含我们文本块的系统提示。只需提出问题,它将在几秒钟内给你答案。
太棒了。现在,我们的 obsidian 插件会以适当的方式显示该答案。
我们还能做什么?
你还可以考虑总结文本或找到与你的文本匹配的最佳关键字,并将它们添加到正文(front matter),以便你可以更好地连接笔记。我一直在尝试制作 10 个好的问题和答案,并将其发送到 Anki。你将希望尝试不同的模型和提示来完成这些不同的事情。更改提示甚至模型权重以使其更适合任务非常容易。
我希望这篇文章能给你一些关于如何为 Obsidian 或任何其他笔记工具构建下一个伟大插件的想法。使用最新的本地 AI 工具,例如你可以在 ollama.com 上找到的工具,这种力量轻而易举,我希望你能向我展示你正在进行什么。