llm
Obsidian + LLMs
My experiments (with obsidian-copilot) have yet to yield a satisfying intersection between LLMs and Obsidian. I generally find OpenAI’s models write too much like breathless clickbait instead of an interesting human. I don’t want a summary or rewrite of my notes generated in that style. For querying/searching/discovery, OpenAI’s notebook-esque web interface is fine.
I’m still hoping to come across something that uses indexing and embedding to help me organize and connect notes I wrote, by my hand, in intriguing and novel ways. 🤞🏻
That said, if you’re already running an LLM locally via llama, obsidian-ollama looks like an excellent way to integrate it into your note-writing/knowledge management scheme. The code of this plugin is easy to follow and nicely structured, making it a good one to look at when the temptation to write Obsidian plugins comes. (Which, for me, arrives as a potential distraction more often than I’d like!) Disclaimer: I haven’t tried this one yet!
Build with language models via llm
llm
(previously) is a tool Simon Willison is working on for interacting with large language models, running via API or locally.
I set out to use llm
as the glue for prototyping tools to generate embeddings from one of my journals so that I could experiment with search and clustering on my writings. Approximately, what I’m building is an ETL workflow: extract/export data from my journals, transform/index as searchable vectors, load/query for “what docs are similar to or match this query”.
Extract and transform, approximately
Given a JSON export from DayOne, this turned out to be a matter of shell pipelines. After some iterations with prompting (via Raycast’s GPT3.5 integration), I came up with a simple script for extracting entries and loading them into a SQLite database containing embedding vectors:
#!/bin/sh
# extract-entries.sh
# $ ./extract-entries Journals.json
file=$1
cat $file |
jq '[.entries[] | {id: .uuid, content: .text}]' |
llm embed-multi journals - \ # [1]
--format json \
--model sentence-transformers/all-MiniLM-L6-v2 \ # [2]
--database journals.db \
--store
A couple things to note here:
- The placement of the
-
parameter matters here. I’m used to placing it at the end of the parameter list, but that didn’t work. Thellm embed-multi
docs suggest that--input
is equivalent, but I think that’s a docs bug (the parameter doesn’t seem to exist in the released code). - I’m using locally-run model to generate the embeddings. This is very cool!
In particular, llm embed-multi
takes one JSON doc per line, expecting id/content
keys, and “indexes” those into a database of document/embedding rows. (If you’re thinking “hey, it’s SQLite, that has full search, why not both: yes, me too, that’s what I’m hoping to accomplish next!)
I probably could have just built this by iterating on shell commands, but I like editing with a full-blown editor and don’t particularly want to practice at using the zsh builtin editor. 🤷🏻♂️
Load, of a sort
Once that script finishes (it takes a few moments to generate all the embeddings), querying for documents similar to a query text is also straightforward:
# Query the embeddings and pretty display the results
# query.sh
# ./query.sh "What is good in life?"
query=$1
llm similar journals \
--number 3 \
--content "$query" |
jq -r -c '.content' | # [1]
mdcat # [2]
Of note, two things that probably should have been more obvious to me:
- I don’t need to write a for-loop in shell to handle the output of
llm similar
;jq
basically has an option for that - Pretty-printing Markdown to a terminal is trivial after
brew install mdcat
I didn’t go too far into clustering, which also boils down to one command: llm cluster journals 10
. I hit a hiccup wherein I couldn’t run a model like LLaMa2 or an even smaller one because of issues with my installation.
Things I learned!
jq
is very good on its own!- and has been for years, probably!
- using a copilot to help me take the first step with syntax using my own data is the epiphany here
llm
is quite good, doubly so with its growing ecosystem of plugins- if I were happier with using shells, I could have done all of this in a couple relatively simple commands
- it provides an adapter layer that makes it possible to start experimenting/developing against usage-priced APIs and switch to running models/APIs locally when you get serious
- it’s feasible to do some kinds of LLM work on your own computer
- in particular, if you don’t mind trading your own time getting your installation right to gain independence from API vendors and usage-based pricing
Mission complete: I have a queryable index of document vectors I can experiment with for searching, clustering, and building applications on top of my journals.
Read papers, work tutorials, the learning will happen
(Previously: Building a language model from scratch, from a tutorial)
I started to get a little impatient in transcribing linear algebra code from the tutorial into my version. In part, it’s tedious typing. More interesting, GitHub Copilot trying to autocomplete the code was sometimes right and sometimes the wrong idiom and actively deciding which was which compounded the tedium. This is a big lesson for our “humans-in-the-loop supervise generative LLMs” near-future. 😬
OTOH, when I got to the part where an attention head was implemented, it made way more sense having read Attention is All You Need previously. That feels like a big level-up: reading papers and working through implementations thereof brings it all together in a big learning moment. Success! 📈