在你的 Obsidian 笔记中利用 LLM

2023年9月21日

今天我在 Hacker News 上看到了关于 Obsidian 的另一个插件的帖子,该插件与 ChatGPT 集成。有很多这样的工具,我喜欢看到用它们与 Obsidian 结合的不同方式。建立连接,让你在笔记上走得更远。一些评论者认为这做了你需要自己做的工作,但我认为它以新的和令人难以置信的方式增强了你的能力。

与你的笔记交谈

你可能想要做的第一件也是最明显的事情是能够与你的笔记进行对话。提出问题以获得更深入的见解。如果你可以直接将模型指向你的笔记并完成它,那就方便了。但是大多数模型无法一次接受所有这些内容。

当你提出问题时,并非所有笔记都相关。所以你需要找到相关的部分,并将其交给模型。 Obsidian 有一个搜索功能,但它只是搜索确切的单词和短语,我们需要搜索概念。这就是嵌入发挥作用的地方。我们必须创建一个索引。事实证明这很容易做到。

让我们构建索引器

当你创建一个 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 个令牌。 我将模型设置为使用包含我们文本块的系统提示。 只需提出问题,它会在几秒钟内给你答案。

太棒了。 现在,我们的 obsidian 插件将适当地显示该答案。

我们还能做什么?

你也可以考虑总结文本或找到与你的文本匹配的最佳关键词,并将它们添加到 front matter 中,这样你就可以在笔记之间建立更好的连接。 我已经尝试制作 10 个好的问题和答案发送给 Anki。 您需要尝试不同的模型和提示来完成这些不同的事情。 更改提示甚至模型权重以使其更适合任务非常容易。

我希望这篇文章为你提供了一些关于如何为 Obsidian 或任何其他笔记工具构建下一个伟大插件的想法。 使用最新的本地 AI 工具,例如你可以在 ollama.com 上找到的那些工具,这种能力轻而易举,我希望你能向我展示你在做什么。