<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jamesb</title>
    <description>The latest articles on DEV Community by Jamesb (@experilearning).</description>
    <link>https://dev.to/experilearning</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1226520%2Fb84f0d34-58e5-4b0a-8e74-38e85345626a.jpg</url>
      <title>DEV Community: Jamesb</title>
      <link>https://dev.to/experilearning</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/experilearning"/>
    <language>en</language>
    <item>
      <title>Unlocking Advanced RAG: Citations and Attributions</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Mon, 29 Jan 2024 17:22:41 +0000</pubDate>
      <link>https://dev.to/experilearning/unlocking-advanced-rag-citations-and-attributions-59lk</link>
      <guid>https://dev.to/experilearning/unlocking-advanced-rag-citations-and-attributions-59lk</guid>
      <description>&lt;p&gt;Often we want LLMs to cite exact quotes from source material we provide in our prompts. This is useful in academic contexts to cite snippets from papers, for law firms who need to cite sections of the legal code for a case and in business applications where knowing the exact source of a quote can save hours of scrolling through financial documents. But it's not as simple as asking the LLM to return exact quotes in the prompt. We can't trust that the LLM won't just hallucinate a quote or citation that doesn't really exist. So how can we get LLMs to provide exact quotes or citations and verify that they are correct?&lt;/p&gt;

&lt;p&gt;I ran into this problem while working on the &lt;a href="https://bjsi.github.io/remnote-flashcard-copilot.html" rel="noopener noreferrer"&gt;RemNote Flashcard Copilot&lt;/a&gt;. My goal was to allow new users to generate flashcards by highlighting a paragraph from their notes. I wanted to add citation pins linking the AI generated flashcards to each sentence in their notes so that new users could understand how the AI had used their notes to generate the flashcards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldeyqfpv0l44jhfl557x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldeyqfpv0l44jhfl557x.gif" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Naive substring checks don't work because LLMs will make small changes in punctuation and wording, correct spelling and grammar mistakes.&lt;/p&gt;

&lt;p&gt;Initially I attempted to split by sentence and generate flashcards per sentence. But this is not multilingual - different languages use different sentence delimiters. End of sentence delimiters can have different meanings in different contexts. Of course I could ask an LLM to chunk a paragraph into sentences, but this would add a bunch of additional waiting time for the user.&lt;/p&gt;

&lt;p&gt;Then I tried using using an LLM to verify that citations are correct. The issue with this is that this is still error-prone - you still run into situations where the checker LLM says that the cited sentence is correct and present but when you go to search for it, you can't find it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Solution
&lt;/h1&gt;

&lt;p&gt;What we need is a way to verify with a high probability that the sentence or paragraph cited by the LLM is a genuine citation. And beyond that, it would be ideal if we could find the original citation within the source text ourselves rather than trusting the LLM citation. This is useful when we want to add UI elements to text we are rendering in our application.&lt;/p&gt;

&lt;p&gt;The solution I came up with can be summarised as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM cites a sentence&lt;/li&gt;
&lt;li&gt;I fuzzy search to find the best match to that sentence in the original text&lt;/li&gt;
&lt;li&gt;If the match ratio is close (&amp;gt;90%) it's considered valid&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fuzz Partial Ratio is useful when you want to find the similarity between two strings, focusing only on the best matching substring. So searching for 'pie' inside 'apple pie' yields a score of 100 because the shorter string 'pie' is found within the longer string 'apple pie'.&lt;/p&gt;

&lt;p&gt;I modified the original &lt;code&gt;partial_ratio&lt;/code&gt; function to return the best scoring match and its start index, because this weirdly wasn't included in the &lt;code&gt;partial_ratio&lt;/code&gt; function from fuzzball.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fuzzball&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SequenceMatcher&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;difflib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// modified from: https://github.com/nol13/fuzzball.js/blob/773b82991f2bcacc950b413615802aa953193423/fuzzball.js#L942&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;partial_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;str2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;str2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;shorter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str1&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;longer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str2&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;shorter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str2&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;longer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SequenceMatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shorter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMatchingBlocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestStartIdx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;long_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;long_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;long_start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;shorter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;long_substr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;longer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;long_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;long_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shorter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;long_substr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bestScore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;bestScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;bestMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;long_substr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;bestStartIdx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;long_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;99.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bestMatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;bestScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;bestStartIdx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I hope this can be useful to someone!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>openai</category>
    </item>
    <item>
      <title>Fine Tuning LLMs to Process Massive Amounts of Data 50x Cheaper than GPT-4</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Mon, 08 Jan 2024 18:40:14 +0000</pubDate>
      <link>https://dev.to/experilearning/fine-tuning-llms-to-process-massive-amounts-of-data-50x-cheaper-than-gpt-4-4a1d</link>
      <guid>https://dev.to/experilearning/fine-tuning-llms-to-process-massive-amounts-of-data-50x-cheaper-than-gpt-4-4a1d</guid>
      <description>&lt;p&gt;In this article I'll share how I used &lt;a href="https://openpipe.ai/"&gt;OpenPipe&lt;/a&gt; to effortlessly fine tune Mistral 7B, reducing the cost of one of my prompts by 50x. I included tips and recommendations if you are doing this for the first time, because I definitely left some performance increases on the table. Skip to &lt;a href="https://dev.tofine-tuning-open-recommender"&gt;Fine Tuning Open Recommender&lt;/a&gt; if you are specifically interested in what the fine tuning process looks like. You can always DM me on Twitter (&lt;a class="mentioned-user" href="https://dev.to/experilearning"&gt;@experilearning&lt;/a&gt;) or leave a comment if you have questions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Over the past month I have been working on &lt;a href="https://github.com/bjsi/open-recommender"&gt;Open Recommender&lt;/a&gt;, an open source YouTube video recommendation system which takes your Twitter feed as input and recommends you relevant YouTube-shorts style video clips.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KbBwhuVpqC0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I've successfully iterated on the prompts, raising the interestingness and relevancy of the clip recommendations from 50% to over 80%. See &lt;a href="https://dev.to/experilearning/building-an-open-source-llm-recommender-system-prompt-iteration-and-refinement-7b4"&gt;this article&lt;/a&gt; where I share the prompt engineering techniques I used. I also implemented a &lt;a href="https://open-recommender-ui.onrender.com/"&gt;basic web UI you can try out&lt;/a&gt;. Open Recommender even has 3 paying users (not my Mum, Dad and sister lol 😃).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a1c6QW4c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s8s6yr9l5donpz0q5m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a1c6QW4c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s8s6yr9l5donpz0q5m6.png" alt="revenue" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we are one step closer to scaling to millions of users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jn399ZGw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/izdkp85t6wcrrz1z8k06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jn399ZGw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/izdkp85t6wcrrz1z8k06.png" alt="todos" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But lets not get ahead of ourselves. There are a couple of problems I need to solve before I can even consider scaling things up. Each run is currently very expensive, costing at least &lt;strong&gt;$10-15 per run&lt;/strong&gt; 😲, with the bulk of the cost coming from pouring vast numbers of tokens from video transcripts into GPT-4. I expect the cost will get even worse when we add more input sources like blogs and articles!&lt;/p&gt;

&lt;p&gt;It also takes 5-10 minutes to run the entire pipeline for a single user 🫣. There are still some things unrelated to model inference that can be improved here, so I'm not &lt;em&gt;massively&lt;/em&gt; concerned, but it would be great if we could speed things up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine Tuning
&lt;/h2&gt;

&lt;p&gt;Fine tuning provides a solution to both the cost and speed issues I'm experiencing. The most powerful LLMs like GPT-4 are trained on a vast array of internet text to understand language generally and perform a wide array of tasks at a high level. But most applications outside of highly general AI agents only require the LLM to be good at a specific task, like summarising documents, search result filtering or recommendation reranking. Instead of a generalist Swiss Army knife LLM, we'd rather have a highly specialised natural language processing function.&lt;/p&gt;

&lt;p&gt;Fine-tuning involves taking an LLM with some general understanding of language and then training it further on a specific type of task. It turns out that if we only require proficiency at one specific language-based task, then we can get away with using a much smaller model. For instance, we could fine tune a 7 billion parameter model like Llama or Mistral and achieve performance that is just as good or better than GPT-4 on our specific task, but 10x cheaper and faster.&lt;/p&gt;

&lt;p&gt;Now traditional fine tuning is a massive pain, because it requires a bunch of setup, manual dataset curation, buying or renting GPUs, and potential frustration if you set something up incorrectly and your fine tuning run blows up in the middle. Then once you have your fine tuned model, you have to figure out how to host it and manage the inference server, scaling, LLM ops etc... That's where &lt;a href="https://openpipe.ai/"&gt;OpenPipe&lt;/a&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenPipe
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openpipe.ai/"&gt;OpenPipe&lt;/a&gt; is an &lt;a href="https://github.com/OpenPipe/OpenPipe"&gt;open source&lt;/a&gt; project that specialises in helping companies incrementally replace expensive OpenAI GPT-4 prompts with faster, cheaper fine-tuned models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9Q-6KN-8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9rnq16r7tr9euv99sdjy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9Q-6KN-8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9rnq16r7tr9euv99sdjy.png" alt="open pipe" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They have a simple drop-in replacement for OpenAI's library which records your requests into a web interface to help you curate a dataset from your GPT-4 calls and use them to fine-tune a smaller, faster, cheaper open source model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v7c4v46r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtpuq21xuynzm8qxrtng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v7c4v46r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtpuq21xuynzm8qxrtng.png" alt="how openpipe works" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Effectively OpenPipe helps you use GPT-4 to "teach" a smaller model how to perform at a high level on your specific task through fine tuning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OpenAI Wrapper Playbook
&lt;/h2&gt;

&lt;p&gt;You need to have a dataset of examples to fine tune these models first, because out of the box performance of these smaller models cannot compete with GPT-4. So we need to start out with GPT-4 to collect synthetic training data, then fine tune an open source model once we have enough examples.&lt;/p&gt;

&lt;p&gt;So at a high level the playbook for LLM startups looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the most powerful model available and iterate on your prompts until you get something working reasonably well.&lt;/li&gt;
&lt;li&gt;Record each request, building up a dataset of input and output pairs created with GPT-4.&lt;/li&gt;
&lt;li&gt;Use the dataset to train a smaller, faster, cheaper open source model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;✨ When exactly you decide to fine tune will vary depending on your use case. If you use LLMs for heavy amounts of text processing, you might need to do it before you scale, to avoid huge OpenAI bills. But if you only make a moderate number of calls, you can focus more on prompt iteration and finding product market fit before fine tuning. It all depends on your revenue, model usage and costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine Tuning Open Recommender
&lt;/h2&gt;

&lt;p&gt;Here's what the process looked like in practice for Open Recommender.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup OpenPipe
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.openpipe.ai/getting-started/quick-start"&gt;Sign up for an OpenPipe API key&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OPENPIPE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your key&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then replace the OpenAI library with OpenPipe's wrapper.&lt;/p&gt;

&lt;p&gt;Replace this line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this one&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;openpipe&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✨ Turn on prompt logging. It's enabled by default, but I disabled it during the prompt iteration phase thinking that I'd bloat the request logs with a bunch of data that I'd need to filter out later. With hindsight, it's better to just keep it turned on from the start and use OpenPipe's &lt;a href="https://docs.openpipe.ai/getting-started/openpipe-sdk"&gt;request tagging feature&lt;/a&gt; to save the &lt;code&gt;prompt_id&lt;/code&gt; on each request log. Later when you want to create a dataset for fine tuning, you can easily filter your dataset down to a particular version of your prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Dataset
&lt;/h3&gt;

&lt;p&gt;The next step is to run your prompts a bunch to create some data!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SFuMBc-R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqe5pv1zd5ay52k272on.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SFuMBc-R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqe5pv1zd5ay52k272on.png" alt="sending recs to users 1" width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CtKa7T0b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h920ugfgjo36lwlgw0ew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CtKa7T0b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h920ugfgjo36lwlgw0ew.png" alt="sending recs to users 2" width="800" height="685"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After running my prompts a bunch on my initial set of users, I had a moderate sized dataset. I decided to do a quick and dirty fine tune to see what kind of performance I could get out of Mistral 7B without any dataset filtering or augmentation.&lt;/p&gt;

&lt;p&gt;✨ I exported all of my request logs and did some quick analysis with a script to figure out which prompts my pipeline account for the bulk of the latency and cost. I figured it makes sense to prioritise fine tuning for the costliest and slowest prompts. For Open Recommender, the "Recommend Clips" prompt which is responsible for cutting long YouTube transcripts into clips is very slow and costly, so I started there.&lt;/p&gt;

&lt;p&gt;In the OpenPipe request log UI you can add lots of different filters to create your dataset. I just did a simple filter for all requests with a particular prompt name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TnDdAMCT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/be36qwv7lty8dvwkwuxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TnDdAMCT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/be36qwv7lty8dvwkwuxc.png" alt="request log filters" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you can create the dataset and get to fine tuning!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---So9yeBT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s3639kyeh87rkwzi4cv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---So9yeBT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5s3639kyeh87rkwzi4cv.png" alt="create dataset" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Model?
&lt;/h3&gt;

&lt;p&gt;Before you start a fine tuning job, you need to pick which model you want to fine tune. Since this is my first time doing this, I can't give very specialised advice, but from internet research larger models generally have higher capacity and can perform better on a wider range of tasks but require more computational resources and time for both fine-tuning and inference. Smaller models are more efficient but might not capture as complex features as larger ones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gbjugwae--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpaiffcekn1mlayvxwfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gbjugwae--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpaiffcekn1mlayvxwfy.png" alt="model choice" width="800" height="911"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I decided to go with Mistral 7B because my prompt is quite simple - it's a single task with a single function call response. Also I want to speed up the pipeline. If the outcome with a smaller model is good enough, then I can avoid needing to optimise the dataset or switching to a larger, slower the model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Rk1xDmZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/037rriz2qk5gfymiavwa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Rk1xDmZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/037rriz2qk5gfymiavwa.png" alt="model choice" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few hours later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O_RxYTX2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v37yplj2bzb7owdd8qaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O_RxYTX2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v37yplj2bzb7owdd8qaj.png" alt="deployed" width="800" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's automatically deployed!&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xrFRb8YT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zj8fkdv3k9gbs25c6ii4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xrFRb8YT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zj8fkdv3k9gbs25c6ii4.png" alt="eval" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking this link takes you to a page where you can see a comparison between GPT-4's output and the fine tuned model on the test set (OpenPipe automatically uses 20% of your dataset for testing).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p6hnhmWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnph2ox1ip45vko85qe9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p6hnhmWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnph2ox1ip45vko85qe9.png" alt="eval table" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From a quick scan it looks promising...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--frB7S6zR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwn3ueyxl1lsn1fnn6fz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--frB7S6zR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwn3ueyxl1lsn1fnn6fz.png" alt="meme" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the only way to know for sure is to play with it. I decided to run it on three test cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;Tweets&lt;/th&gt;
&lt;th&gt;Relatedness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lex Fridman Podcast with Jeff Bezos&lt;/td&gt;
&lt;td&gt;Three tweets about LLM recommender systems and LLM data processing pipelines&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced RAG Tutorial&lt;/td&gt;
&lt;td&gt;1 tweet about AI therapists with advanced RAG&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Podcast about plants&lt;/td&gt;
&lt;td&gt;Three tweets about LLM recommender systems and LLM data processing pipelines&lt;/td&gt;
&lt;td&gt;Unrelated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Medium
&lt;/h3&gt;

&lt;p&gt;❌ 2 clips hallucinated that the user's tweets&lt;br&gt;
indicated interests in Amazon / startups.&lt;br&gt;
✅ 2 clips correctly linking moderately related&lt;br&gt;
clips to the user's tweets about data pipelines&lt;/p&gt;

&lt;h3&gt;
  
  
  Strong
&lt;/h3&gt;

&lt;p&gt;✅ 1 clip extracting the most relevant clip from the transcript&lt;/p&gt;

&lt;h3&gt;
  
  
  Unrelated
&lt;/h3&gt;

&lt;p&gt;❌ 1 clip hallucinating that user's tweets indicated interests in plants&lt;/p&gt;

&lt;p&gt;So there were definitely some performance decreases over GPT-4. I suspect this was largely due to my dataset - I realised that my dataset contained very few cases where the transcript was unrelated to the tweets, because I was collecting data from the end of the pipeline where it's already been through a bunch of filter steps.&lt;/p&gt;

&lt;p&gt;✨ Because these smaller models have weaker reasoning abilities than GPT-4, you need to make sure your training dataset covers all possible input cases.&lt;/p&gt;

&lt;p&gt;The fine tuned model is absolutely still usable though given its performance on strongly related transcripts. Additionally, since my pipeline has a re-ranking step to filter and order the clips after the create clips stage, any unwanted clips should get filtered out. Couple that with the 50x price decrease over GPT-4, and it's a no brainer!&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;p&gt;We can actually make some additions to the LLM startup playbook above. After collecting our dataset from GPT-4, we can improve the dataset quality by filtering out or editing failed cases. Improving the quality of the dataset can help us get better output performance than GPT-4.&lt;/p&gt;

&lt;p&gt;I've been thinking about ways the dataset filtering and curation workflow can be improved. OpenPipe already lets you attach data to your request logs. This is useful for tagging each request with the name of the prompt. Something that the OpenPipe team are working on is adding extra data to a request at a later time. Then you would be able to use user feedback to filter down the dataset, making dataset curation a lot less work. &lt;/p&gt;

&lt;p&gt;For example, in Open Recommender, likes and dislikes could be used to filter the request log:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6xP_IaoB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r514ncjbt0sejqzsxy3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6xP_IaoB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r514ncjbt0sejqzsxy3o.png" alt="Image description" width="800" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, OpenPipe supports a couple of cool additional features like GPT-4-powered LLM evals and token pruning. I'll do a walkthrough of those when I do more intensive fine tunes later. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;That's it for now! Thanks for reading. If you want to try out the Open Recommender Beta or have questions about how to use OpenPipe, please DM me on Twitter &lt;a class="mentioned-user" href="https://dev.to/experilearning"&gt;@experilearning&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>From Spaced Repetition Systems to Open Recommender Systems</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Sun, 31 Dec 2023 11:21:03 +0000</pubDate>
      <link>https://dev.to/experilearning/from-spaced-repetition-systems-to-open-recommender-systems-25ab</link>
      <guid>https://dev.to/experilearning/from-spaced-repetition-systems-to-open-recommender-systems-25ab</guid>
      <description>&lt;p&gt;In this piece, I want to connect YouTube Shorts and TikTok to &lt;a href="https://gwern.net/spaced-repetition"&gt;spaced repetition&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=oNCLLNZEtz0"&gt;incremental reading&lt;/a&gt; which I've worked on for the past 4 years.&lt;/p&gt;

&lt;p&gt;If you think about it, all of these systems fall into the general bucket of "recommender systems" - systems which attempt to predict and provide content that is most relevant to the user based on their past interactions.&lt;/p&gt;

&lt;p&gt;I made the connection while working at &lt;a href="https://remnote.com/"&gt;RemNote&lt;/a&gt; and thinking about ways to make the spaced repetition review experience more enjoyable - why do people kick back and relax at the end of a long day by watching four hours of YouTube shorts, but find 10-minute Anki review sessions to be a miserable chore? Is the only way to make flashcard queues more engaging to make the content more sensationalist and "trashy" similar to YouTube shorts and TikTok, or can you become addicted to educational content that helps you improve your life and make progress towards your goals?&lt;/p&gt;

&lt;p&gt;I want to explore the potential for a system that borrows the best elements from each to create something that feels as effortless and engaging as a queue of YouTube shorts but actually helps you make progress towards meaningful goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Recommendation System for Your Memory
&lt;/h2&gt;

&lt;p&gt;Spaced repetition systems (SRSs) are digital flashcard managers like &lt;a href="https://apps.ankiweb.net/"&gt;Anki&lt;/a&gt;, &lt;a href="https://supermemo.store/products/supermemo-19-for-windows"&gt;SuperMemo&lt;/a&gt; and &lt;a href="https://remnote.com/"&gt;RemNote&lt;/a&gt;. You can think of them as &lt;strong&gt;recommender systems for your memory&lt;/strong&gt; which direct your attention towards information you are about to forget.&lt;/p&gt;

&lt;p&gt;While they are &lt;a href="https://gwern.net/spaced-repetition"&gt;extremely effective&lt;/a&gt; at combatting forgetting, many users struggle to maintain consistent review habits because the system prioritises showing you information that you are about to forget over information that you would find interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Spaced Repetition Works
&lt;/h3&gt;

&lt;p&gt;Spaced repetition systems use algorithms which calculate the optimal review times and fewest repetitions required to keep flashcards in your memory. The intervals between reviews expand with each repetition allowing you to maintain your knowledge with exponentially lower effort over time.&lt;/p&gt;

&lt;p&gt;By controlling the rate of forgetting, you eliminate the churn effect in learning. Without spaced repetition, your knowledge grows asymptotically towards a saturation level as the rate of forgetting old knowledge balances the rate of learning new knowledge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fzDjQySI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7w55pxu9rkbahr0qvfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fzDjQySI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7w55pxu9rkbahr0qvfk.png" alt="no srs" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spaced repetition &lt;strong&gt;linearises the acquisition of knowledge&lt;/strong&gt;. By calculating optimal review dates and exponentially increasing review intervals, a single flashcard may be repeated as little as 6-20 times across your entire lifetime, meaning that you may be able to remember as many as &lt;a href="https://supermemo.guru/wiki/How_much_knowledge_can_human_brain_hold"&gt;250-300 thousand flashcards by the time you retire&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fu3_bhrK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t188pm8p2kdg17d0kwtc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fu3_bhrK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t188pm8p2kdg17d0kwtc.png" alt="with srs" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Hasn't Spaced Repetition Taken Over the World
&lt;/h3&gt;

&lt;p&gt;Spaced repetition has proved itself to be an effective learning technique across a &lt;a href="https://www.reddit.com/r/Anki/comments/ubbdoc/anki_success_stories/"&gt;wide range of disciplines&lt;/a&gt;, from &lt;a href="https://www.youtube.com/watch?v=l3nd0cF-SOA"&gt;language learning&lt;/a&gt; to &lt;a href="https://www.reddit.com/r/medicalschoolanki/comments/hnkg7z/260_with_lightyear_deck/"&gt;medical school&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=_RdjsVngZz8"&gt;mathematics&lt;/a&gt; and &lt;a href="https://www.reddit.com/r/programming/comments/n30hl/janki_method_learning_programming_with_6000/"&gt;coding&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I used Anki to learn Chinese to the point where I can comfortably read science fiction like &lt;a href="https://en.wikipedia.org/wiki/The_Three-Body_Problem_(novel)"&gt;The Three Body Problem&lt;/a&gt; without a dictionary. I used SuperMemo to learn coding from scratch with no technical background which then became my full-time job. And I used RemNote to learn math, later writing &lt;a href="https://x.com/experilearning/status/1694642965140934913?s=20"&gt;an intro to logic and proof course using an interactive theorem prover and RemNote&lt;/a&gt; as someone who hadn't studied math since I was 16.&lt;/p&gt;

&lt;p&gt;Spaced repetition can change your life and &lt;a href="https://scalingknowledge.substack.com/p/spaced-repetition-for-knowledge-creation"&gt;it has the potential to change the world&lt;/a&gt;, but it's rare to see it used outside of cramming for exams (language learning being the major exception). Students endure the spaced repetition algorithm until they graduate and feel a great sense of relief when they can finally delete their flashcard decks for good. Burnout is also common with many users feeling crushed by their daily repetitions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Misaligned Objective Function
&lt;/h4&gt;

&lt;p&gt;The objective function of the spaced repetition algorithm is to maximise the user's memory retention with the fewest number of repetitions. I've argued before that this is often out of line with users' real-world goals and makes it hard to enjoy the review experience.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1476655088999538699-151" src="https://platform.twitter.com/embed/Tweet.html?id=1476655088999538699"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1476655088999538699-151');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1476655088999538699&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Because the goal is to maximise memory retention, &lt;strong&gt;the bulk of your reviews will be focused on the flashcards that are most difficult for you&lt;/strong&gt;. From the perspective of the spaced repetition algorithm, it would be considered a waste of time to review flashcards that you easily remember because if you can easily remember them, the system can confidently boost the review interval far into the future, satisfying its goal of minimising your repetition load. Instead, it's considered better to show you the cards you find most difficult more often so you can burn them into your memory.&lt;/p&gt;

&lt;p&gt;As a result, reviewing your flashcard queue becomes a punishing experience where you are predominantly reviewing information you don't understand very well. Avoiding this requires quite a lot of attention and care, obsessing over &lt;a href="https://controlaltbackspace.org/precise/"&gt;flashcard formulation&lt;/a&gt;, eliminating &lt;a href="https://docs.ankiweb.net/leeches.html"&gt;leeches&lt;/a&gt; and aiming for a consistent &lt;a href="https://supermemo.guru/wiki/Forgetting_index_in_SuperMemo#:~:text=If%20the%20forgetting%20index%20is%20too%20high%2C%20your%20repetitions%20will%20be%20stressful%20due%20to%20constant%20problems%20with%20recall."&gt;90% retention rate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eg-__4KO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xunzmk5a0fnknm909pp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eg-__4KO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xunzmk5a0fnknm909pp.gif" alt="Image description" width="250" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's another way to think about it: in a pure spaced repetition system &lt;strong&gt;flashcards outcompete other flashcards for your attention by being more difficult to remember&lt;/strong&gt;. Wouldn't we be happier if our flashcards competed for our attention by being &lt;em&gt;more interesting&lt;/em&gt; as opposed to more difficult? I realised that this is effectively how recommender systems like YouTube and TikTok work - they are markets of video clips competing to be as interesting and engaging as possible for users. This thought is what got me interested in studying them more closely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Incremental Reading
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=oNCLLNZEtz0"&gt;Incremental reading&lt;/a&gt; systems like &lt;a href="https://supermemo.guru/wiki/Incremental_reading"&gt;SuperMemo&lt;/a&gt; are a superset of spaced repetition systems. Incremental reading involves interleaving your flashcards with snippets from articles, books and videos in a big prioritised queue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The general idea is to form a "funnel of knowledge" in which a vast worldwide web is converted into a "selection of own material", that moves to important highlights (extracts), that get converted to active knowledge (cloze deletion), which is then made stable in memory, and, in the last step, acted upon in a creative manner in problem solving. - &lt;a href="https://supermemo.guru/wiki/Incremental_reading#:~:text=The%20general%20idea,problem%20solving."&gt;Incremental Reading&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/oNCLLNZEtz0?start=131"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It's interesting to analyse them through the lens of recommender systems and compare them to pure spaced repetition systems like Anki because &lt;strong&gt;incremental reading systems do a much better job at allowing the user to enjoy the review process&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Between 2020 and 2022 I spent a lot of time using SuperMemo, read all of &lt;a href="https://supermemo.guru/"&gt;Piotr Wozniak's articles&lt;/a&gt;, talked to hundreds of people in the &lt;a href="https://discord.com/invite/vUQhqCT"&gt;SuperMemo.Wiki Discord channel&lt;/a&gt; and started a &lt;a href="https://www.youtube.com/channel/UCIaS9XDdQkvIjASBfgim1Uw"&gt;YouTube channel&lt;/a&gt; and &lt;a href="https://www.youtube.com/channel/UC9PA26yTZOsJB_wHJXN6sKg"&gt;podcast&lt;/a&gt; related to these ideas.&lt;/p&gt;

&lt;p&gt;I met &lt;a href="https://www.youtube.com/watch?v=0VvSj2hHGk4"&gt;ex-gamers who went from spending 8 hours per day playing League of Legends&lt;/a&gt; to live-streaming themselves reviewing their incremental reading queue for 8 hours per day. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z6ZACFHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kds6bvp2negdo5y7opat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z6ZACFHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kds6bvp2negdo5y7opat.png" alt="quit gaming for srs" width="798" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ksdzQxAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbox4460h7kzle0t6hto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ksdzQxAP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbox4460h7kzle0t6hto.png" alt="learning addiction" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many people were not in school (myself included) or had never been to school at all! We didn't have exams to study for. But instead of playing video games, we spent all day using this obscure learning software with a UI like a NASA rocket dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y_EY3Nji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhhn6auzvgshny50od2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y_EY3Nji--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhhn6auzvgshny50od2q.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of watching Netflix, we had month-long debates over &lt;a href="https://www.youtube.com/watch?v=Aul2gX0j5Oo"&gt;whether flashcards ought to be formulated using analogies&lt;/a&gt; &lt;br&gt;
and &lt;a href="https://www.youtube.com/watch?v=OwV5HPKMrbg"&gt;made guides about how you should prioritise and value learning material&lt;/a&gt;. Why?&lt;/p&gt;

&lt;p&gt;The only way to explain it is by breaking down the concept of knowledge valuation.&lt;/p&gt;
&lt;h3&gt;
  
  
  A Nose for the Interesting
&lt;/h3&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/pLFwsGL0sX0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Humans are naturally attracted to information that is surprising, novel, consistent and coherent. Sometimes we are attracted to contradictory information - I think it depends on your personality type. But for the most part, we love information that "slots in" or that we can relate in some way to our prior knowledge and our current goals. &lt;strong&gt;&lt;a href="https://www.youtube.com/watch?v=eAnNGqwI2AQ"&gt;Understanding is pleasurable&lt;/a&gt;&lt;/strong&gt;. We don't like information that we can't relate in some way to our prior knowledge. When we are forced to try to understand something when we don't know the pre-requisite concepts, we get bored and frustrated and start yawning.&lt;/p&gt;

&lt;h4&gt;
  
  
  Semantic Distance
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q0ukKiIu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k901eipw5b0jni67j1l6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q0ukKiIu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k901eipw5b0jni67j1l6.png" alt="semantic distance" width="362" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Semantic distance is like the "knowledge gap" between what you already know and some new information input. For example, there's a semantic distance between your current knowledge of mathematics and a new mathematical subject you haven't studied before. When the gap is too large, it makes it impossible to understand the new information because you can't relate it meaningfully to what you already know. If you take a graduate-level math lecture before having studied math 101, it will be impossible, you will display physical signs of displeasure, like fidgeting, restlessness, yawning and more - your body is telling you to go do something else!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T8pvYmSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjz226xzc6ea2328w6v7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T8pvYmSc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjz226xzc6ea2328w6v7.png" alt="Image description" width="800" height="789"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyone who has used spaced repetition systems at school understands the feeling of needing to drill flashcards into your brain that just won't stick and it's a horrible feeling.&lt;/p&gt;

&lt;p&gt;There are two main features in incremental reading systems which avoid this making them much more enjoyable to use - prioritisation and variable reward.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Priority Queue
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cGOeTjvF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nat62spnwb36fc2s4j4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cGOeTjvF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nat62spnwb36fc2s4j4h.png" alt="Image description" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Incremental reading systems add prioritisation to the queue, so unlike a pure spaced repetition system, you aren't shown information based only on how likely you are to forget it. You can use the priority system to attach a subjective sense of importance or "interestingness" to each item in the queue. The priority is just a number from 0-100, what exactly it means is up to the user to determine and will likely vary over time as their values and goals evolve.&lt;/p&gt;

&lt;p&gt;You are also encouraged to break up large pieces of content into smaller chunks and apply more granular prioritisation mechanisms. This is very similar to the way people chop up clips from long podcasts and upload the highlights to YouTube shorts or TikTok.&lt;/p&gt;

&lt;p&gt;This is an improvement over pure spaced repetition because &lt;strong&gt;flashcards no longer compete for your attention based on how difficult they are, but also based on how interesting you find them&lt;/strong&gt;. Since &lt;a href="https://www.youtube.com/watch?v=eAnNGqwI2AQ"&gt;understanding is pleasurable&lt;/a&gt;, people naturally assign the highest priorities to material that is novel but comprehensible. Any material that is too complex gets sent to the back of the queue. By the time you reach it, you may have built up the pre-requisite concepts for it to become interesting to you. &lt;/p&gt;

&lt;p&gt;This works well as long as you are diligent about updating priorities for each flashcard or article in your collection to maintain the mapping between how interesting your brain finds them and their order in the priority queue. But what inevitably happens is that you take a break from the system, your interests change and the priorities you assigned 6 months ago are now out of sync with your current interests. Users end up complaining about "stale collections" of material they used to find interesting, but which they no longer care about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8MP66Wtz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/per3vkt82x5a23am6s4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8MP66Wtz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/per3vkt82x5a23am6s4r.png" alt="Image description" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It would be better if the system could quickly and dynamically adjust what it recommends to you by inferring your interests from your current behaviour, similar to modern recommender algorithms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variable Reward
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kWNoQEkV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36oy31nfi97i99hnjvp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kWNoQEkV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36oy31nfi97i99hnjvp2.png" alt="variable reward in IR" width="800" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just having a mixture of different information types in your queue makes it much more enjoyable. It can be draining to read or answer flashcards for hours on end, why not break it up with some more passive sources like YouTube videos?&lt;/p&gt;

&lt;p&gt;Incremental reading systems differ from recommender systems in that you are responsible for adding all the material manually into your queue. But as the intervals between repetitions and the amount of items in your collection grows, you tend to forget what you put in your queue, so you can't predict what's coming next. All you know is that whatever comes next is something that you believed was important enough to import and assign a priority. This makes going through your queue surprising. Articles and videos interleaved together with flashcards force seemingly unrelated concepts into your attention in quick succession, often resulting in unexpected connections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fbW-lZCu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fflh8vw7lalm20ql534q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fbW-lZCu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fflh8vw7lalm20ql534q.png" alt="Image description" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But current incremental reading systems aren't set up to give you completely unexpected content - they rely on you manually going out to search for things. Nor is there any notion of collaborative filtering - incremental reading systems are single-player games. And due to information overload and the degradation in the quality of the search results from Google search, this is becoming more and more frustrating. &lt;/p&gt;

&lt;h2&gt;
  
  
  YouTube Shorts
&lt;/h2&gt;

&lt;p&gt;Now let's examine YouTube shorts. Why is it that people are able to kick back and relax at the end of a long day by watching four hours of YouTube shorts, but would despise spending four hours doing flashcard repetitions in  Anki? &lt;/p&gt;

&lt;p&gt;Why do people willingly forego meals and sleep to watch TikTok but only do Anki repetitions reluctantly?&lt;/p&gt;

&lt;p&gt;For spaced repetition apps (and ed-tech apps in general) to take over the world they need to see themselves as competing with Netflix, TikTok and YouTube shorts for users' attention. When you scroll through TikTok or YouTube shorts, you never have the experience of failing to understand something. Everything is perfectly comprehensible. There is the novelty and surprisal aspect of not knowing what is coming next. YouTube shorts is a market of clips competing to entertain you the most. Clips are collaboratively filtered by the community and recommended to you based on your watch history. They have high retention because they allow you to quickly "channel zap" and find something new when you get bored.&lt;/p&gt;

&lt;p&gt;But while the user interface and algorithms implemented into YouTube shorts and TikTok make them very engaging, the content quality is often extremely bad. It's hard to curate a feed of really high quality educational content that will advance you towards your goals. These systems are black boxes with little opportunity for customisation. They also lack the active recall aspect of spaced repetition systems which help you internalise and reflect on important concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Recommender System?
&lt;/h2&gt;

&lt;p&gt;Is there potential for a recommender system that blends the best characteristics from spaced repetition, incremental reading and YouTube shorts into one? People will argue that without sensationalist, clickbait content, YouTube shorts and TikTok would lose their addictive power, but my experience in the SuperMemo.Wiki Discord has shown me that people can become hooked on content that improves their life under the right circumstances.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wvh_88w_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5u7wvs7jhoj6ubx3zgj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wvh_88w_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5u7wvs7jhoj6ubx3zgj.png" alt="Image description" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I dreamt of back in university was a system that could infer my interests from my daily activities, like flashcard reviews, reading my behaviour and browsing habits, and use that information to recommend me videos, articles and podcasts from YouTube that I could watch in the evening after school. I never got anything off the ground until a month ago when I revisited &lt;a href="https://erik.bjareholt.com/wiki/importance-of-open-recommendation-systems/"&gt;Erik Bjäreholt's blog post on Open Recommender Systems&lt;/a&gt; and realised that LLMs have made sophisticated, customisable and explainable recommendation systems easier than ever to build!&lt;/p&gt;

&lt;p&gt;I think huge advances have suddenly made possible and we need to start exploring what is possible now we can use LLMs as agents, constantly scanning the web on our behalf, searching for golden nuggets that we'll find interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Recommender
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/OpenPipe"&gt;
        OpenPipe
      &lt;/a&gt; / &lt;a href="https://github.com/OpenPipe/open-recommender"&gt;
        open-recommender
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Using LLMs to implement an open source YouTube video recommendation system.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
    &lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/bjsi/open-recommender/main/img/logo.webp"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1mOFz53e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/bjsi/open-recommender/main/img/logo.webp" alt="Open Recommender Logo" height="200"&gt;&lt;/a&gt;
    &lt;br&gt;
    Open Recommender - An open source, LLM-powered YouTube video recommendation system
&lt;/h1&gt;
&lt;h3&gt;
⚠️ Work in Progress... ⚠️
&lt;/h3&gt;
&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 Build an MVP of the data pipeline&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Iterate on the prompts until 8/10 recommendations are good&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Curate fine tune dataset&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Create a website so people can sign up&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Collect more fine tune data&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Fine tune using &lt;a href="https://openpipe.ai/" rel="nofollow"&gt;OpenPipe&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Scale to more users&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Add more recommendation sources (e.g. articles, twitters, books, etc.)&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 Scale to millions of users&lt;/li&gt;
&lt;/ul&gt;
&lt;br&gt;
&lt;h2&gt;
🚀 Overview&lt;/h2&gt;
&lt;p&gt;Welcome to Open Recommender, an open source recommendation system for YouTube. &lt;em&gt;See the video intro below&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=KbBwhuVpqC0" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/97940e08fab9c4d43110e065e2a1484d75be6e66ff8c4bf3f3f89bccd8bc7e09/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f4b624277687556707143302f687164656661756c742e6a7067" alt="Video"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
🏆 Goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Understand your current interests by analyzing your Twitter engagement (likes, retweets, etc.)&lt;/li&gt;
&lt;li&gt;Create a large database of potentially interesting videos&lt;/li&gt;
&lt;li&gt;Recommend interesting sections from videos&lt;/li&gt;
&lt;li&gt;Recommend "timeless" content rather than "trending" content&lt;/li&gt;
&lt;li&gt;Surface "niche bangers" - difficult to find but really high quality content&lt;/li&gt;
&lt;li&gt;Biased towards learning as opposed to entertainment&lt;/li&gt;
&lt;li&gt;Read my blog post for more: &lt;a href="https://dev.to/experilearning/building-an-llm-powered-open-source-recommendation-system-40fg" rel="nofollow"&gt;Building an&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/OpenPipe/open-recommender"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;For the past month I've been working on a proof of concept for an open source recommender system called Open Recommender. Currently it works by taking your public Twitter data as input, uses LLMs to process it and infer your interests and searches YouTube to curate lists of short 30-60 second clips tailored to your current interests. I wrote &lt;a href="https://dev.to/experilearning/building-an-llm-powered-open-source-recommendation-system-40fg"&gt;an article about it here&lt;/a&gt; and made &lt;a href="https://www.youtube.com/watch?v=KbBwhuVpqC0"&gt;a video showing an early prototype&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I already have plans to make it even more targeted at an individual's unique interests and prior knowledge - for example it could take arbitrary information sources as input, like the history and probability of recall for all of the information you had added into your spaced repetition system.&lt;/p&gt;

&lt;p&gt;I'll also be adding ways to tune the behaviour of the recommender to your personal tastes - the recommendations could exist along a spectrum from appealing to the interests the system knows the learner already has to giving recommendations from new unexplored territory. And because it's implemented using LLMs, you can provide custom instructions using natural language and the LLM can provide understandable explanations for its recommendations.&lt;/p&gt;

&lt;p&gt;And I'm also working on an early version of the UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CiiGWf-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kowv6ma07ulet3i6qxpi.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CiiGWf-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kowv6ma07ulet3i6qxpi.gif" alt="Image description" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this sounds exciting to you and you are interested in supporting the project, you can &lt;a href="https://buy.stripe.com/bIY7tbco90f23sY9AC"&gt;subscribe here&lt;/a&gt; and I'll include you in the test of the UI before it becomes available to anyone else, as well as implement your feedback ASAP. If you have any good ideas, please get in touch with me on Twitter &lt;a class="mentioned-user" href="https://dev.to/experilearning"&gt;@experilearning&lt;/a&gt;. Thanks for reading!!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>openai</category>
    </item>
    <item>
      <title>Avoiding Cascading Failure in LLM Prompt Chains</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Fri, 29 Dec 2023 14:06:28 +0000</pubDate>
      <link>https://dev.to/experilearning/avoiding-cascading-failure-in-llm-prompt-chains-9bf</link>
      <guid>https://dev.to/experilearning/avoiding-cascading-failure-in-llm-prompt-chains-9bf</guid>
      <description>&lt;p&gt;A common problem faced when building LLM applications composed of chains of prompts is that failures and inaccuracies early on in the chain get compounded into system-wide failures. It's like the &lt;a href="https://en.wikipedia.org/wiki/Cascading_failure" rel="noopener noreferrer"&gt;cascading failure problem&lt;/a&gt; where a failure in a small subset of nodes propagates outwards bringing down the entire network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7xv2y5eh2se5pzqi10y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7xv2y5eh2se5pzqi10y.gif" alt="cascading failure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I noticed this a lot while working on &lt;a href="https://github.com/OpenPipe/open-recommender" rel="noopener noreferrer"&gt;Open Recommender&lt;/a&gt;, an open source YouTube video recommender system which takes users' Twitter feeds as input, infers the kind of topics they are interested in and searches YouTube to find relevant YouTube videos and clips to recommend them.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KbBwhuVpqC0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;The beginning of the data processing pipeline looks like this. The main part I'll discuss in this article is the &lt;code&gt;createQueries&lt;/code&gt; step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validateArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getTweets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// generates YouTube queries based on tweets&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createQueries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchForVideos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// ... more stages&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I run &lt;code&gt;pipeline.execute&lt;/code&gt;, each stage gets executed sequentially and the output of the previous stage is passed as input to the next. The &lt;code&gt;getTweets&lt;/code&gt; stage outputs a list of a user's last 30 tweets. These get passed to the &lt;code&gt;createQueries&lt;/code&gt; stage which constructs a list of YouTube search queries. Those search queries are then passed to the &lt;code&gt;searchForVideos&lt;/code&gt; stage which searches YouTube and returns a list of search results for each query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chinese Whispers
&lt;/h2&gt;

&lt;p&gt;The problem is that LLM prompt chains are like a game of Chinese whispers - without building error recovery mechanisms into your program errors compound into stranger and stranger outputs.&lt;/p&gt;

&lt;p&gt;I kept running into issues where the &lt;code&gt;createQueries&lt;/code&gt; function was simultaneously a strong determinant of the quality of the final recommendations as well as very difficult to get working reliably.&lt;/p&gt;

&lt;p&gt;Constructing really effective search queries is inherently a difficult problem because it requires a great deal of knowledge about the user - it's not enough to know that the user has tweeted about a particular topic, you also need to infer the user's expertise level and whether it's a passing interest or something they really care about.&lt;/p&gt;

&lt;p&gt;Initially my approach in the &lt;code&gt;createQueries&lt;/code&gt; stage was to run the user's tweets through a prompt called &lt;code&gt;inferInterests&lt;/code&gt;. The idea was to extract an array of topics (concepts, people, events and problems) the user was interested in and use those to construct search queries. But this felt like quite a one dimensional compression of the users interests and erased a lot of nuance in terms of what the user was expressing about the topic.&lt;/p&gt;

&lt;p&gt;This meant that the quality of the &lt;code&gt;createQueries&lt;/code&gt; output could range between great and very poor and as many as half of the recommended videos presented to the user at the end of the pipeline felt irrelevant.&lt;/p&gt;

&lt;p&gt;It was difficult to build in error recovery mechanisms too, because if I added a step to compare the video search results against the queries, they would look reasonable, but comparing them against the tweets made it clear that a lot of results were missing the mark in terms of relevancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;My first realisation was that compressing a user's tweets into a list of topics, people, events and problems was an extremely lossy compression of a user's interests. And strong lossy compression does not allow stages later in the pipeline to effectively recover from errors.&lt;/p&gt;

&lt;p&gt;For that reason I removed the intermediate &lt;code&gt;inferInterests&lt;/code&gt; step and instead generate queries directly from the user's tweets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;CreateYouTubeSearchQueries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;tweetIDs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in the return type I ask GPT to include the IDs of the tweets that it used to generate each search query. Later in the pipeline I use these tweets to double check that the outputs of subsequent stage are still relevant. So for example, in the signature of the &lt;code&gt;filterSearchResults&lt;/code&gt; prompt, you can see that it takes arrays tweets and search results as input and returns an array of search results with relevancy scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;FilterSearchResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tweet&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;relevance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this was I'm controlling the error compounding by comparing against the "ground truth" of users' tweets.&lt;/p&gt;

&lt;p&gt;Additionally by adding a simple relevancy score in the prompt output schema, I can filter out bad query recommendations by setting a search result relevancy cutoff value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj6adi3y93frint6x7ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmj6adi3y93frint6x7ba.png" alt="Something"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally at the end of the pipeline, I added a more expensive filtering and ranking step inspired by &lt;a href="https://github.com/sunnweiwei/RankGPT" rel="noopener noreferrer"&gt;RankGPT&lt;/a&gt; to do a final ordering over the remaining video clips, picking only the top 10-15 to recommend to the user. &lt;/p&gt;

&lt;h2&gt;
  
  
  Core Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It's best to carry the "ground truth" data for your prompt through the pipeline rather than relying on a lossy compressed summary of it.&lt;/li&gt;
&lt;li&gt;Employ other mechanisms like filtering and re-ranking to minimise the effect of errors built up earlier in the pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;DM me on Twitter (&lt;a class="mentioned-user" href="https://dev.to/experilearning"&gt;@experilearning&lt;/a&gt;) if you want to try the current version of Open Recommender! I'll run it on your Twitter data and send you the results. Check out the &lt;a href="https://dev.to/experilearning/open-recommender-beta-3ie4"&gt;beta roadmap&lt;/a&gt; to see what will be available over the next month or so.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Open Recommender Beta Roadmap</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Fri, 29 Dec 2023 07:31:58 +0000</pubDate>
      <link>https://dev.to/experilearning/open-recommender-beta-3ie4</link>
      <guid>https://dev.to/experilearning/open-recommender-beta-3ie4</guid>
      <description>&lt;p&gt;Over the past month I have been working on &lt;a href="https://github.com/OpenPipe/open-recommender"&gt;Open Recommender&lt;/a&gt;, an open source YouTube video recommendation system which takes your Twitter feed as input and recommends YouTube-shorts style clips tailored to your interests. I made a &lt;a href="https://www.youtube.com/watch?v=KbBwhuVpqC0"&gt;video about it&lt;/a&gt; if you want a more in-depth introduction.&lt;/p&gt;

&lt;p&gt;In this article I want to quickly share my plan for the next month to polish Open Recommender to a beta state. If you prefer, there is a 3 minute breakdown of the roadmap, otherwise feel free to skim the rest of the article.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/9Km1pVgWywE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Tasks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Get Users
&lt;/h3&gt;

&lt;p&gt;Open Recommender Alpha will be me DMing people on Twitter, running the pipeline on their data, sending them recommendations and asking for feedback. The GPT-4 request logs will get auto saved into &lt;a href="https://openpipe.ai/"&gt;OpenPipe&lt;/a&gt; for fine tuning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine Tune
&lt;/h3&gt;

&lt;p&gt;I will fine tune using a dataset collected from the Open Recommender Alpha. I will also use data collected from running the pipeline over slices of my own twitter data. I will use &lt;a href="https://openpipe.ai/"&gt;OpenPipe&lt;/a&gt; to do the fine tuning.&lt;/p&gt;

&lt;p&gt;I won't bother using user feedback signals / manual dataset filtering or augmentation at this stage, just raw GPT-4 ⇒ Mistral / Llama 7B.  The point is to just bring the cost down.&lt;/p&gt;

&lt;p&gt;Once the fine tune is done we can test the fine tuned model performance against GPT-4 to check for performance degradation,&lt;/p&gt;

&lt;h3&gt;
  
  
  Build UI
&lt;/h3&gt;

&lt;p&gt;I will implement a basic YouTube shorts style UI supporting both mobile and web. I don't think I need to bother with auth yet. Open Recommender only uses public data at the moment, so a user's recommendations can just be a public URL that gets DM'd/emailed to them&lt;/p&gt;

&lt;h3&gt;
  
  
  Lower Priority
&lt;/h3&gt;

&lt;p&gt;Maybe I'll get round to these.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hHFActrL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u7rgci6mmueov7uhdzp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hHFActrL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u7rgci6mmueov7uhdzp0.png" alt="pipeline" width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;filterShitposts&lt;/code&gt; prompt step to filter out tweets which aren't relevant for making recommendations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make a PR to OpenPipe to support adding user feedback to the request logs to make dataset filtering easier.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;DM me on Twitter (&lt;a class="mentioned-user" href="https://dev.to/experilearning"&gt;@experilearning&lt;/a&gt;) if you want to try the current version of Open Recommender :)&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building an Open Source LLM Recommender System: Prompt Iteration and Refinement</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Thu, 28 Dec 2023 08:45:29 +0000</pubDate>
      <link>https://dev.to/experilearning/building-an-open-source-llm-recommender-system-prompt-iteration-and-refinement-7b4</link>
      <guid>https://dev.to/experilearning/building-an-open-source-llm-recommender-system-prompt-iteration-and-refinement-7b4</guid>
      <description>&lt;p&gt;Over the past month I have been working on &lt;a href="https://github.com/bjsi/open-recommender"&gt;Open Recommender&lt;/a&gt;, an open source YouTube video recommendation system which takes your Twitter feed as input and recommends YouTube-shorts style clips tailored to your interests. I made a &lt;a href="https://www.youtube.com/watch?v=KbBwhuVpqC0"&gt;video about it&lt;/a&gt; if you want a more in-depth introduction.&lt;/p&gt;

&lt;p&gt;The data pipeline looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pDFd9Kh8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrfnlcv9osacamdl6axf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pDFd9Kh8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrfnlcv9osacamdl6axf.png" alt="Image description" width="800" height="1058"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So based on analysing your Twitter feed, the system generates YouTube search queries and uses the search API to find relevant videos. It then chops up the videos into clips. All of this data processing is controlled using LLMs, currently GPT-4, but over the next couple of weeks I'm going to be migrating away from OpenAI's closed source expensive APIs towards fine tuned open source models using &lt;a href="https://openpipe.ai/"&gt;OpenPipe&lt;/a&gt;, a brilliant service for incrementally replacing OpenAI's models with smaller, faster, cheaper fine tuned open source models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Iteration
&lt;/h2&gt;

&lt;p&gt;The main focus over the past couple of weeks has been tweaking and improving the reliability of the prompts and data processing pipeline to the point where 8/10 of the recommendations feel interesting. When I started, only half of the recommendations felt relevant, which was quite encouraging because I knew from previous projects that as long as you have a decent LLM program, with enough tweaking it's possible to turn it into something great. I'm happy to report that after many hours banging my head against the wall I have finally achieved the 8/10 quality recommendations goal consistently across runs, at least for my own Twitter data. Here are some of the key things I learned over the past couple of weeks:&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Tools for Prompt Engineering
&lt;/h3&gt;

&lt;p&gt;We need better tools for prompt engineering. Ideally prompts should be written and auto optimised by an LLM with optional human in the loop feedback. I frequently ran into issues where my approach wasn't working, but I didn't have the energy to try something else because it would take too much time without any guarantee that it would perform better. Just like in programming, you want the experimentation cycle to be short so you can quickly filter through possible solutions to find something that works. But this just isn't possible with prompt engineering right now. It takes a huge amount of time to set up alternative prompts, in-context examples or re-jig your prompt chain to quickly experiment with a different approach. Minimising friction here is essential.&lt;/p&gt;

&lt;p&gt;Based on my experience here I started working on a TypeScript library called &lt;a href="https://github.com/bjsi/prompt-iteration-assistant"&gt;Prompt Iteration Assistant&lt;/a&gt;. I described it as "a set of simple tools to speed up the prompt engineering iteration cycle". It gives you a nice CLI dialog for creating, testing and iterating on prompts. To create a new prompt, you tell it the goal and ideal output from the prompt and it bootstraps a new prompt by getting GPT-4 to write it. It infers the input and output schemas and will support code generation to add the prompt to your codebase automatically.&lt;/p&gt;

&lt;p&gt;My goal is to make prompt engineering 10x easier, but it definitely hasn't reached that level yet. I think the DX is nice because the CLI dialogs and code generation makes writing prompts a lot faster, but I don't think this represents the next generation of prompt engineering yet.&lt;/p&gt;

&lt;p&gt;A couple of days ago I ran into a really impressive project called DSPy which supports auto generating and optimising whole programs composed of multiple prompts. To quote the docs: "DSPy gives you general-purpose modules (e.g., ChainOfThought) and takes care of optimising prompts for your program and your metric."&lt;/p&gt;

&lt;p&gt;Please see my article specifically on "Better Tools for Prompt Engineering" where I go into more detail about these topics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimise the Main Levers and Avoid Cascading Failure
&lt;/h3&gt;

&lt;p&gt;I realised that the &lt;code&gt;createQueries&lt;/code&gt; and &lt;code&gt;createClips&lt;/code&gt; prompts are the two stages in the pipeline that make the biggest impact on the quality of the recommendations. &lt;code&gt;createQueries&lt;/code&gt; controls which queries get sent to the YouTube search API and &lt;code&gt;createClips&lt;/code&gt; controls whether and how each video gets split up into YouTube-shorts style clips.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;createClips&lt;/code&gt; function, I was able to improve the quality of the output using traditional prompt engineering techniques. I kept tweaking the prompt and evaluating it against 3 datasets - an unrelated transcript, a moderately related transcript and a completely unrelated transcript to validate that I got the expected output from each one. But I wasn't able to guaruntee the quality of the clips. To make the quality more reliable I implemented a re-ranking prompt for video clips (inspired by &lt;a href="https://github.com/sunnweiwei/RankGPT"&gt;RankGPT&lt;/a&gt;) to make sure only the best of the best gets recommended. I also added some logic to control the number of recommendations from the same source to make sure there is enough variety in the final recommended clips.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;createQueries&lt;/code&gt; prompt, I made some improvements to the prompt by obsessing over the in-context example I created from my own Twitter data. But I realised that occasional strange queries would always sneak in there and cause a cascading failure of poor recommendations further down the pipeline. One generalisation I have reasoned my way to is that a long LLM program is like a game of Chinese whispers - if you don't build error correction and recovery into the system, your output will get stranger and stranger due to error compounding.&lt;/p&gt;

&lt;p&gt;I controlled for this by implementing a &lt;code&gt;filterSearchResults&lt;/code&gt; prompt which compares video search results returned from the YouTube API against the user's Tweets and filters out the ones which are unrelated. Importantly I used the user's Tweets to compare against the search results, rather than comparing against the queries or a summary of the user's tweets. This controls against the LLM compounding errors earlier in the pipeline because the LLM may have generated strange queries, or misinterpret something in its summary of the user's tweets. It's better to compare against the "ground truth" for the user's interests which is the Tweets themselves. &lt;/p&gt;

&lt;p&gt;In my article on "Avoiding Cascading Failure in LLM Pipelines" I analysed the cascading failure problem in more detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Look at Your Data
&lt;/h3&gt;

&lt;p&gt;A week ago I was running the pipeline over my Twitter data and I realised that I was consistently getting strange recommendations that made no sense. Looking at my Twitter likes and tweets I couldn't understand why certain videos had been recommended to me. Why was I getting wrestling video recommendations when I have never tweeted about anything to do with wrestling?&lt;/p&gt;

&lt;p&gt;It wasn't until I inspected the raw data getting fed into the LLM requests using &lt;a href="https://openpipe.ai/"&gt;OpenPipe's&lt;/a&gt; request log web UI that I noticed that there were Tweets included in my Twitter data that I did not recognise. I ran the &lt;code&gt;getTweets&lt;/code&gt; function a bunch more times and realised that the unofficial Twitter API I'm using to fetch tweets was returning advertisement tweets interleaved within my own tweets!&lt;/p&gt;

&lt;p&gt;I caught another bug in the &lt;code&gt;appraiseTranscripts&lt;/code&gt; prompt. I noticed upon re-running the prompt many times over the same video it would output the correct response only 50% of the time. Using my testing setup I was able to quickly debug the issue. I found that the prompt performed fine with 250, 500 and 1000 tokens of transcript context. But frequently fails with specifically 350 tokens of context! The transcript was a video called "The 10 AI Innovations Expected to Revolutionize 2024 - 2025". The correct output would be to classify it as spam.&lt;/p&gt;

&lt;p&gt;Here was GPT's reasoning for recommending it in the 350 token context test: "The video uses some buzzwords and makes some broad claims about the future of AI, but it also provides specific examples and details about current developments in the field, such as self-driving cars and drone delivery services.'&lt;/p&gt;

&lt;p&gt;My explanation is that with fewer tokens of context, GPT can't appraise the quality of the transcript well, because one interesting nugget can skew the assessment of quality a lot. So even if 50% of the 350 tokens is buzzwords, a couple of quality sentences can "persuade" GPT to recommend it. The funny part is that the 250 token context test passes every time because it excludes a mildly interesting example about self-driving cars! So in conclusion, to get a stable, accurate assessment of average quality you need to pass a larger number of tokens (quite obvious in hindsight).&lt;/p&gt;

&lt;p&gt;There are tons of other bugs I caught too like inconsistent in-context example formatting, using the incorrect function name for function call examples and prompt variables that weren't getting replaced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;Now the prompt engineering is done, it's time to start curating a dataset for fine tuning using OpenPipe. This will bring down the cost of running the pipeline and allow me to scale to more users. If you want to try out the recommendations and give suggestions about how they could be improved, please DM me on Twitter &lt;a class="mentioned-user" href="https://dev.to/experilearning"&gt;@experilearning&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>openai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Managing Long-Running LLM Data Processing Pipelines</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Wed, 06 Dec 2023 06:56:28 +0000</pubDate>
      <link>https://dev.to/experilearning/managing-long-running-llm-data-processing-pipelines-48f9</link>
      <guid>https://dev.to/experilearning/managing-long-running-llm-data-processing-pipelines-48f9</guid>
      <description>&lt;p&gt;Looking for a simple abstraction to help you run and debug your LLM data processing pipeline without losing your mind? Look no further!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1732293110988734681-268" src="https://platform.twitter.com/embed/Tweet.html?id=1732293110988734681"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1732293110988734681-268');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1732293110988734681&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;While working on &lt;a href="https://dev.to/experilearning/building-an-llm-powered-open-source-recommendation-system-40fg"&gt;Open Recommender&lt;/a&gt;, my open source, LLM-powered recommendation system for YouTube videos, I quickly ran into issues due to the incredibly slow development loop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---I3STA7p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btodeeyns1lqi5pzdwxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---I3STA7p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btodeeyns1lqi5pzdwxy.png" alt="Data Pipeline Image" width="800" height="1058"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The data pipeline so far. &lt;a href="https://dev.to/experilearning/building-an-llm-powered-open-source-recommendation-system-40fg"&gt;Read more about the project here&lt;/a&gt; or &lt;a href="https://www.youtube.com/watch?v=KbBwhuVpqC0"&gt;watch my video&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A full run of the pipeline takes &lt;strong&gt;up to 40 minutes&lt;/strong&gt; to complete for a single user due to the large number of GPT-4 calls involved. When I encountered crashes and bugs I would have to re-run the entire pipeline from scratch without any guarantee of reproducing the error due to the non-determinism inherent in LLM applications. As you can imagine, this became incredibly tiring and annoying.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ww_pfiL4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nfhx0b8uli2l9aepzmw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ww_pfiL4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nfhx0b8uli2l9aepzmw.png" alt="Me when it crashes" width="680" height="1020"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;me.png&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;The solution I came up with was to add individual parts of the data processing chain into a &lt;code&gt;Pipeline&lt;/code&gt; class and use this to automatically save the inputs and outputs of each stage while the pipeline is running. This all gets saved to disk in &lt;code&gt;run-id.json&lt;/code&gt; files. If a pipeline crashes, I can look at the error, add debugger statements or logging and restore the failed run from a checkpoint, allowing me to immediately debug and fix the failure without running the entire pipeline again from scratch.&lt;/p&gt;
&lt;h3&gt;
  
  
  How it Works
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validateArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getTweets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createQueries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchForVideos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filterSearchResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadTranscripts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appraiseTranscripts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunkTranscripts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recommendations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;simplified from &lt;a href="https://github.com/bjsi/open-recommender/blob/main/src/pipeline/main.ts"&gt;src/pipeline/main.ts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Each stage in the pipeline is just a function with a name and description that takes the previous stage's output as input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTweets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-tweets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Get tweets from Twitter user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GetTweetsStageArgs&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CreateQueriesArgs&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tweets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;n_tweets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No tweets found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tweets&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The &lt;code&gt;getTweets&lt;/code&gt; stage. Simplified from &lt;a href="https://github.com/bjsi/open-recommender/blob/1dc1e9b4afe58afe4ea5853fba9754afa1605066/src/pipeline/stages.ts#L40C1-L65C3"&gt;src/pipeline/stages&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since errors can occur at each stage in the pipeline, each PipelineFunction is modelled as function which can either succeed with a value of type &lt;code&gt;T&lt;/code&gt; or fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PipelineFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PipelineStage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PipelineFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Simplified from &lt;a href="https://github.com/bjsi/open-recommender/blob/main/src/pipeline/stages.ts"&gt;src/pipeline/stages&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I execute the pipeline, the &lt;code&gt;Pipeline&lt;/code&gt; class iterates over each of the stages, executing them in turn and passing the result of the prior stage as input to the next. All intermediate results are saved into a &lt;code&gt;run-id.json&lt;/code&gt; file so checkpoints can be restored later if required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;saveStage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PipelineStage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRunById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;saveRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when I encounter unexpected errors, I can add any debugger and logging statements required to understand the issue and re-run the pipeline from the beginning of the failed stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn main &lt;span class="nt"&gt;--cloneRunID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;run-id&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;--stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;name&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has improved the developer experience 10,000x compared to how it was before!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1731913928630849978-520" src="https://platform.twitter.com/embed/Tweet.html?id=1731913928630849978"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1731913928630849978-520');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1731913928630849978&amp;amp;theme=dark"
  }



 &lt;/p&gt;

&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;Here are some improvements I'll consider making to the pipeline in the future, especially once I get it running in production.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create different base classes for different kinds of errors. Eg. &lt;code&gt;RetryableError&lt;/code&gt; for errors that the pipeline can recover from.&lt;/li&gt;
&lt;li&gt;It's common for parts of a stage to be able to proceed without waiting around for the rest of the stage to complete. Forcing stages to run synchronously means the pipeline runs slower than it could.

&lt;ul&gt;
&lt;li&gt;Maybe it's possible to support more concurrency but still have checkpoints to save the results of a complete stage.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;I should probably break up some of the stages a little bit more, eg. the &lt;code&gt;chunkTranscripts&lt;/code&gt; stage is remarkably slow and if it crashes in the middle it can still take 10 mins to re-run.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building an LLM-Powered Open Source Recommendation System for YouTube</title>
      <dc:creator>Jamesb</dc:creator>
      <pubDate>Tue, 05 Dec 2023 19:09:29 +0000</pubDate>
      <link>https://dev.to/experilearning/building-an-llm-powered-open-source-recommendation-system-40fg</link>
      <guid>https://dev.to/experilearning/building-an-llm-powered-open-source-recommendation-system-40fg</guid>
      <description>&lt;p&gt;My main project for the past month or so has been &lt;a href="https://github.com/bjsi/open-recommender" rel="noopener noreferrer"&gt;Open Recommender&lt;/a&gt;, an open source LLM-powered recommendation system for YouTube videos. It works by taking your Twitter data (tweets, likes, retweets and quotes) and analyses it to infer what topics you are currently interested in. It then searches YouTube to find relevant videos and narrows them down to the clips that are most likely to interest you.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KbBwhuVpqC0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I have been curious about the idea of open recommendation systems for years. I've always found it unnerving how much control third parties have over the content I see. And I'm frustrated by the misalignment between the objective function of most platforms' recommendation algorithms and my personal reason for using the platform - platforms want to keep me scrolling to sell my attention to advertisers. But I want my recommendation system to be built with the purpose of improving my life and helping me make progress towards my goals.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When we go on our Facebook feed or that of any other social media site, we are at their recommendation algorithms mercy. They presumably optimize for clicks, time spent, and endless scrolling. That’s what they want us to do, but is that what we want out of Facebook? - &lt;a href="https://erik.bjareholt.com/wiki/importance-of-open-recommendation-systems/" rel="noopener noreferrer"&gt;&lt;em&gt;The Importance of Open Recommender Systems&lt;/em&gt; - Erik Bjäreholt&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What I dreamt of back in university was a system that could infer my interests from my daily activities, like flashcard reviews, reading my behaviour and browsing habits, and use that information to recommend me videos and podcasts from YouTube that I could watch in the evening after school. I never got anything off the ground until a couple of weeks ago when I revisited Erik Bjäreholt's &lt;a href="https://erik.bjareholt.com/wiki/importance-of-open-recommendation-systems/" rel="noopener noreferrer"&gt;blog post on Open Recommender Systems&lt;/a&gt; and realised that LLMs have made sophisticated, customisable and explainable recommendation systems easier than ever to build!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1726290619428131208-109" src="https://platform.twitter.com/embed/Tweet.html?id=1726290619428131208"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1726290619428131208-109');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1726290619428131208&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;On top of that, massive price decreases and the increased performance of cheap, fine-tunable open source models have made this economically viable too. I reached out to a company called &lt;a href="https://openpipe.ai/" rel="noopener noreferrer"&gt;OpenPipe&lt;/a&gt; who specialise in helping companies incrementally replace expensive OpenAI GPT-4 prompts with faster, cheaper fine-tuned models and they were kind enough to sponsor all of the OpenAI calls and fine-tuning costs for this project! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ad2l2z7rf0ippqbiia3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ad2l2z7rf0ippqbiia3.png" alt="OpenPipe image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They have a super simple drop-in replacement for OpenAI's library which records your requests into a simple web interface to help you curate a dataset and fine-tune a model. I am extremely grateful for their support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Recommender
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goals
&lt;/h3&gt;

&lt;p&gt;Here are some of the goals of Open Recommender.&lt;/p&gt;

&lt;h4&gt;
  
  
  Understand the user's interests
&lt;/h4&gt;

&lt;p&gt;We will use users' public Twitter feeds as a proxy for their current interests. This is ethical because it relies purely on public information and it's an effective data source because people interact with tweets that are related in some way to their current interests. Of course, not everyone has an active Twitter account, but it's a good place to start. For the time being you can consider Open Recommender to be "the recommendation system for the terminally online".&lt;/p&gt;

&lt;h4&gt;
  
  
  Customizable and Explainable
&lt;/h4&gt;

&lt;p&gt;No more black box mystery algorithms - LLMs are the perfect replacement! Users can provide custom instructions using natural language and the LLM can provide understandable explanations for its recommendations. Eg. it can explain which Twitter posts influenced its decision to recommend you a certain podcast or interview.&lt;/p&gt;

&lt;h4&gt;
  
  
  Recommend interesting clips from videos
&lt;/h4&gt;

&lt;p&gt;I want to experiment with recommending smaller units of content, similar to YouTube shorts. There's so much great information buried in 4 hour long podcasts that I don't have time to watch. I want the recommendation system to show me the specific clip I'll find most interesting. Then I can decide whether to continue watching the whole thing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Recommend "timeless" content
&lt;/h4&gt;

&lt;p&gt;It's not always the case that newer videos are better. Current recommendation algorithms are biased towards trends and virality. In addition to the latest and greatest I also want to be able to recommend old videos which have stood the test of time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Biased towards learning as opposed to entertainment
&lt;/h4&gt;

&lt;p&gt;I love the user interface of YouTube shorts. It reminds me a lot of &lt;a href="https://www.youtube.com/watch?v=oNCLLNZEtz0" rel="noopener noreferrer"&gt;incremental reading&lt;/a&gt; which I'm a huge fan of. I just wish the content wasn't so sensationalist, clickbaity and trashy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current State
&lt;/h3&gt;

&lt;p&gt;I've already finished the MVP of the data processing pipeline. Here's a diagram, or you can take a scroll through &lt;a href="https://github.com/bjsi/open-recommender/blob/main/src/pipeline/main.ts" rel="noopener noreferrer"&gt;src/pipeline/main.ts&lt;/a&gt;. It's actually quite a simple set of steps to go from Twitter data to YouTube video recommendations! &lt;/p&gt;

&lt;p&gt;Since Open Recommender is open source, you can even run the current version yourself right now by following the &lt;a href="https://github.com/bjsi/open-recommender/blob/main/README.md#installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;, but be warned - it can get expensive!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fef7muhtedi89ar8gxlfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fef7muhtedi89ar8gxlfi.png" alt="Data pipeline image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next steps for the project are to continue iterating on the GPT-4 prompts to improve the quality of the recommendations. So far for each pipeline run, roughly half of the recommendations are good and half are kinda meh. The goal is to improve this ratio to the point where 80% of the recommended videos are good. At that point I will transition to fine-tuning to bring down the cost. Then we can start getting some users!&lt;/p&gt;

&lt;p&gt;To quote Kyle, one of the founders of OpenPipe:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1724502554762187198-935" src="https://platform.twitter.com/embed/Tweet.html?id=1724502554762187198"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1724502554762187198-935');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1724502554762187198&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;In the next few weeks I'll also be writing more articles with code snippets and technical details about the lessons I learned building the data processing pipeline for Open Recommender, especially regarding prompt engineering and iteration. Looking forward to any ideas you have and can't wait to share the progress with you!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
