<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TIL of Xin Fu</title><link>https://imfing.com/til/</link><description>Recent content in TIL on Xin Fu</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 11 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://imfing.com/til/index.xml" rel="self" type="application/rss+xml"/><item><title>Managing windows effectively with AeroSpace</title><link>https://imfing.com/til/aerospace-tiling-window-manager/</link><pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate><guid>https://imfing.com/til/aerospace-tiling-window-manager/</guid><description>Using AeroSpace, a tiling window manager for macOS, to organize windows and workspaces with keyboard shortcuts.</description><content:encoded><![CDATA[<p>Managing application windows has been a never-ending battle, especially with AI tools like Claude Code, Codex, and Cursor encouraging work across multiple projects simultaneously.
I&rsquo;ve relied on <a
  href="https://support.apple.com/en-gb/guide/mac-help/mh14112/mac"
  
    target="_blank" rel="noopener"
  
>macOS built-in Spaces</a>
, but as the number of workspaces grows, switching and finding the right window becomes harder.</p>
<p><a
  href="https://github.com/nikitabobko/AeroSpace"
  
    target="_blank" rel="noopener"
  
>AeroSpace</a>
 is a tiling window manager for macOS. I tried a similar tool called <a
  href="https://github.com/asmvik/yabai"
  
    target="_blank" rel="noopener"
  
>yabai</a>
 before but wasn&rsquo;t quite impressed.</p>
<p>AeroSpace works pretty much out of the box — windows snap into place without overlapping. It implements virtual workspaces in a faster and more predictable way than native Spaces.
For example, workspaces are mapped to shortcuts like <kbd>Option</kbd> + <kbd>1</kbd> or <kbd>Option</kbd> + <kbd>A</kbd>, making switching between them nearly instant.</p>
<p><figure class="my-6">
    <img src="/til/aerospace-tiling-window-manager/aerospace-workspace-tiles.jpg" alt="Tiled workspace with three windows side by side"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>Below are my notes on installing and using AeroSpace.</p>
<h2 id="installation">
  <a class="anchor" href="#installation">
    <span class="icon icon-link"></span>
  </a>
  Installation
</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">brew install --cask nikitabobko/tap/aerospace
</span></span></code></pre></div><p>Create a configuration file. It&rsquo;s recommended to put it under the <code>~/.config</code> folder:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mkdir -p ~/.config/aerospace/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cp /Applications/AeroSpace.app/Contents/Resources/default-config.toml ~/.config/aerospace/aerospace.toml
</span></span></code></pre></div><h2 id="usage">
  <a class="anchor" href="#usage">
    <span class="icon icon-link"></span>
  </a>
  Usage
</h2>
<p><strong>Concepts</strong></p>
<ul>
<li><strong>tiles</strong> — all windows are tiled side by side</li>
<li><strong>accordion</strong> — windows overlap each other with a slight margin</li>
<li><strong>floating</strong> — macOS-style floating window</li>
</ul>
<p><strong>Workspace</strong> is similar to a macOS virtual desktop, except there&rsquo;s no visual UI to organize them. Clicking the menubar icon shows all workspaces and which applications are in each one.</p>
<p><figure class="my-6">
    <img src="/til/aerospace-tiling-window-manager/aerospace-menubar.jpg" alt="AeroSpace menubar showing workspaces"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p><strong>Shortcuts</strong></p>
<p>Workspace navigation:</p>
<p><kbd>Option</kbd> + <kbd>number or letter</kbd> - jump to workspace instantly</p>
<p><kbd>Option</kbd> + <kbd>Shift</kbd> + <kbd>number or letter</kbd> - move window to workspace</p>
<p>Within a workspace:</p>
<ul>
<li><kbd>Option</kbd> + <kbd>/</kbd> - switch to tiling, or adjust orientation</li>
<li><kbd>Option</kbd> + <kbd>,</kbd> - switch to accordion, or adjust accordion orientation</li>
<li><kbd>Option</kbd> + <kbd>h/j/k/l</kbd> - navigate between windows</li>
<li><kbd>Option</kbd> + <kbd>Shift</kbd> + <kbd>h/j/k/l</kbd> - move window position within a workspace</li>
</ul>
<p>Toggle floating:</p>
<ul>
<li><kbd>Option</kbd> + <kbd>Shift</kbd> + <kbd>;</kbd> to enter service mode</li>
<li><kbd>F</kbd> to toggle floating on the focused window</li>
</ul>
<p>Join windows:</p>
<ul>
<li><kbd>Option</kbd> + <kbd>Shift</kbd> + <kbd>;</kbd> to enter service mode</li>
<li><kbd>Option</kbd> + <kbd>Shift</kbd> + <kbd>h/j/k/l</kbd> to join tiled windows</li>
</ul>
<p><strong>Callbacks</strong></p>
<p>You can register callbacks in the config file to apply floating layout for certain applications:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># Window rules</span>
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">on-window-detected</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">if</span><span class="p">.</span><span class="nx">app-id</span> <span class="p">=</span> <span class="s1">&#39;com.apple.finder&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">run</span> <span class="p">=</span> <span class="s1">&#39;layout floating&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">on-window-detected</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">if</span><span class="p">.</span><span class="nx">app-id</span> <span class="p">=</span> <span class="s1">&#39;com.apple.ActivityMonitor&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">run</span> <span class="p">=</span> <span class="s1">&#39;layout floating&#39;</span>
</span></span></code></pre></div><h2 id="further-reading">
  <a class="anchor" href="#further-reading">
    <span class="icon icon-link"></span>
  </a>
  Further reading
</h2>
<p>People often use it together with <a
  href="https://github.com/FelixKratz/SketchyBar"
  
    target="_blank" rel="noopener"
  
>SketchyBar</a>
 and <a
  href="https://github.com/FelixKratz/JankyBorders/"
  
    target="_blank" rel="noopener"
  
>JankyBorders</a>
 for a more complete desktop setup.</p>
<ul>
<li><a
  href="https://nikitabobko.github.io/AeroSpace/guide"
  
    target="_blank" rel="noopener"
  
>AeroSpace Guide</a>
</li>
<li><a
  href="https://www.raycast.com/limonkufu/aerospace"
  
    target="_blank" rel="noopener"
  
>Aerospace Tiling Window Manager Extension for Raycast</a>
</li>
<li><a
  href="https://www.youtube.com/watch?v=5nwnJjr5eOo&amp;t=151s"
  
    target="_blank" rel="noopener"
  
>Aerospace Is Probably The Best MacOS Tiling Manager I&rsquo;ve Ever Used - YouTube</a>
</li>
</ul>
]]></content:encoded></item><item><title>Television: a modern fuzzy finder for the terminal</title><link>https://imfing.com/til/television-modern-fuzzy-finder-terminal/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate><guid>https://imfing.com/til/television-modern-fuzzy-finder-terminal/</guid><description>Discover television, a Rust-based terminal fuzzy finder TUI similar to fzf, featuring a telescope-style layout and a powerful &amp;#34;channels&amp;#34; system for creating custom data sources.</description><content:encoded><![CDATA[<p>I discovered <a
  href="https://github.com/alexpasmantier/television"
  
    target="_blank" rel="noopener"
  
>television</a>
 (tv), a terminal fuzzy finder UI written in Rust, thanks to the blog post <a
  href="https://zed.dev/blog/hidden-gems-part-2"
  
    target="_blank" rel="noopener"
  
>Hidden Gems: Part 2</a>
 by Zed.</p>
<p><figure class="my-6">
    <img src="https://raw.githubusercontent.com/alexpasmantier/television/main/assets/tv-transparent.png" alt="television-cli"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>It&rsquo;s actually very similar to <a
  href="https://github.com/junegunn/fzf"
  
    target="_blank" rel="noopener"
  
>fzf</a>
, a command line fuzzy finder which I use mostly for shell integration like <code>Ctrl+R</code> searching for history.</p>
<p>The <code>tv</code> command line provides a convenient way to chain command line tools such as <code>fd</code>, <code>bat</code>, <code>gh</code>, etc. into a fuzzy finder with <a
  href="https://github.com/nvim-telescope/telescope.nvim"
  
    target="_blank" rel="noopener"
  
>telescope</a>
 style layout. It also has shell integrations.</p>
<p>&ldquo;Channels&rdquo; is the concept in television from which we can define source of the data. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/home/user/.config/television
</span></span><span class="line"><span class="cl">├── config.toml
</span></span><span class="line"><span class="cl">└── cable
</span></span><span class="line"><span class="cl">    ├── files.toml
</span></span><span class="line"><span class="cl">    ├── env.toml
</span></span><span class="line"><span class="cl">    ├── alias.toml
</span></span><span class="line"><span class="cl">    ├── git-repos.toml
</span></span><span class="line"><span class="cl">    └── text.toml
</span></span></code></pre></div><p>Then we can invoke <code>files</code> by running <code>tv files</code>, opening the UI to search for files. A minimal channel <code>my-awesome-channel</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">metadata</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;my-awesome-channel&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">source</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">command</span> <span class="p">=</span> <span class="s2">&#34;aws s3 ls my-bucket&#34;</span>
</span></span></code></pre></div><p>There&rsquo;s a list of community channels <a
  href="https://github.com/alexpasmantier/television/tree/main/cable"
  
    target="_blank" rel="noopener"
  
>here</a>
, and with coding AI, we can easily create our own data sources, e.g. <code>curl</code> remote data sources like Hacker News.</p>
]]></content:encoded></item><item><title>Use custom LLM providers in Claude Code</title><link>https://imfing.com/til/use-custom-llm-providers-in-claude-code/</link><pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate><guid>https://imfing.com/til/use-custom-llm-providers-in-claude-code/</guid><description>Learn how to configure Claude Code to use custom LLM providers like OpenRouter by overriding the Anthropic API endpoint via ANTHROPIC_BASE_URL environment variable.</description><content:encoded><![CDATA[<p><a
  href="https://claude.ai/code"
  
    target="_blank" rel="noopener"
  
>Claude Code</a>
 has been one of the most popular coding agent this year.
I&rsquo;ve relied on it heavily for common development tasks.
One useful but lesser-known feature is that Claude Code allows you to override the default Anthropic API endpoint using the <code>ANTHROPIC_BASE_URL</code> environment variable.</p>
<p>This is very useful in many scenarios, such as working around quota limits, connecting to custom model endpoints or third-party providers like <a
  href="https://github.com/features/copilot"
  
    target="_blank" rel="noopener"
  
>GitHub Copilot</a>
.</p>
<p>Since Claude Code <strong>only</strong> supports <a
  href="https://docs.claude.com/en/api/messages"
  
    target="_blank" rel="noopener"
  
>Anthropic messages API</a>
, we&rsquo;ll either need a provider that already supports this format, or use a proxy to translate requests into the Anthropic format.</p>
<p><figure class="my-6">
    <img src="/til/use-custom-llm-providers-in-claude-code/image.jpg" alt="Anthropic Environment Variables Settings"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>These <a
  href="https://code.claude.com/docs/en/settings#environment-variables"
  
    target="_blank" rel="noopener"
  
>environmental variables are documented</a>
.
Many of the model providers such as <a
  href="https://platform.moonshot.ai/docs/guide/agent-support#using-kimi-k2-thinking-model-in-claude-code"
  
    target="_blank" rel="noopener"
  
>Kimi</a>
, <a
  href="https://api-docs.deepseek.com/guides/anthropic_api"
  
    target="_blank" rel="noopener"
  
>DeepSeek</a>
, <a
  href="https://www.alibabacloud.com/help/en/model-studio/claude-code"
  
    target="_blank" rel="noopener"
  
>Qwen</a>
, <a
  href="https://docs.z.ai/scenario-example/develop-tools/claude"
  
    target="_blank" rel="noopener"
  
>Z.ai</a>
, etc. provide instructions to set up with Claude Code.
LLM gateways like <a
  href="https://openrouter.ai/"
  
    target="_blank" rel="noopener"
  
>OpenRouter</a>
 also supports Anthropics API out-of-the-box.</p>
<p><a
  href="https://github.com/features/copilot"
  
    target="_blank" rel="noopener"
  
>GitHub Copilot</a>
 can also be used as the backend for Claude Code, which is especially valuable in enterprise environments where Anthropic API access may be restricted.
We could proxy Claude Code &lt;-&gt; GitHub Copilot API through this <a
  href="https://github.com/ericc-ch/copilot-api"
  
    target="_blank" rel="noopener"
  
>copilot-api</a>
 tool.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npx copilot-api@latest auth
</span></span><span class="line"><span class="cl">npx copilot-api@latest start --claude-code
</span></span></code></pre></div><p>What&rsquo;s more, I would make these an alias in my shell configuration.</p>
<p>For example in <a
  href="https://fishshell.com/"
  
    target="_blank" rel="noopener"
  
>Fish Shell</a>
:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="k">function</span> kimi
</span></span><span class="line"><span class="cl">    <span class="nb">set</span> -x ANTHROPIC_AUTH_TOKEN <span class="o">(</span>op <span class="nb">read</span> <span class="s2">&#34;op://Private/Moonshot AI/credential&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">set</span> -x ANTHROPIC_BASE_URL https://api.moonshot.ai/anthropic
</span></span><span class="line"><span class="cl">    claude <span class="nv">$argv</span><span class="o">[</span>1<span class="o">]</span>
</span></span><span class="line"><span class="cl">end
</span></span></code></pre></div><p>Or have a <code>cc</code> alias to launch Claude Code with custom provider easily:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cc<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">export</span> <span class="nv">ANTHROPIC_BASE_URL</span><span class="o">=</span><span class="s2">&#34;http://localhost:4141&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">export</span> <span class="nv">ANTHROPIC_AUTH_TOKEN</span><span class="o">=</span><span class="s2">&#34;dummy&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">export</span> <span class="nv">ANTHROPIC_MODEL</span><span class="o">=</span><span class="s2">&#34;claude-sonnet-4.5&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">export</span> <span class="nv">ANTHROPIC_SMALL_FAST_MODEL</span><span class="o">=</span><span class="s2">&#34;gpt-4o-mini&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  claude <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h2 id="links">
  <a class="anchor" href="#links">
    <span class="icon icon-link"></span>
  </a>
  Links
</h2>
<ul>
<li><a
  href="https://docs.claude.com/en/docs/claude-code"
  
    target="_blank" rel="noopener"
  
>Claude Code Documentation</a>
</li>
</ul>]]></content:encoded></item><item><title>Set Google search AI mode as browser default</title><link>https://imfing.com/til/set-google-search-ai-mode-as-browser-default/</link><pubDate>Sun, 23 Nov 2025 00:00:00 +0000</pubDate><guid>https://imfing.com/til/set-google-search-ai-mode-as-browser-default/</guid><description>How to set Google Search AI mode as your default browser search engine in Chrome and Safari, including step-by-step instructions and URL configuration.</description><content:encoded><![CDATA[<p>With the recent announcement of <a
  href="https://blog.google/products/gemini/gemini-3/"
  
    target="_blank" rel="noopener"
  
>Gemini 3</a>
, I was thinking about giving <a
  href="https://search.google/intl/en-GB/ways-to-search/ai-mode/"
  
    target="_blank" rel="noopener"
  
>Google search AI mode</a>
 another shot.</p>
<p>I&rsquo;ve been using <a
  href="https://www.perplexity.ai/"
  
    target="_blank" rel="noopener"
  
>Perlexitity</a>
 for a while and found it quite useful for many of my searches, but lately it has felt slower and the quality of results has declined.</p>
<p>After trying out the latest Google search AI mode, I&rsquo;m quite impressed by the quality, speed and overall user experience. I&rsquo;ve now set it as my default search option in Chrome.</p>
<p><figure class="my-6">
    <img src="/til/set-google-search-ai-mode-as-browser-default/20251123-qkrc.jpg" alt="Google Search AI Mode"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>This <a
  href="https://www.reddit.com/r/Bard/comments/1lqu2oj/how_to_set_google_ai_mode_search_as_default_in/"
  
    target="_blank" rel="noopener"
  
>Reddit post</a>
 discussed how to set the Google AI mode as the default for the browser address bar. In essence, use:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://www.google.com/search?udm=50&amp;q=%s
</span></span></code></pre></div><ol>
<li>Go to chrome://settings/search</li>
<li>Click &ldquo;Manage search engines and site search&rdquo;</li>
<li>Scroll down and click &ldquo;add&rdquo; next to &ldquo;Site search&rdquo;</li>
<li>Put &ldquo;Google AI mode&rdquo; as the name, and use above address for the URL</li>
<li>In the previous search engine page, make &ldquo;Google AI mode&rdquo; default</li>
</ol>
<p>We can do the same on iPhone Safari with the <a
  href="https://apps.apple.com/gb/app/customize-search-engine/id6445840140"
  
    target="_blank" rel="noopener"
  
>Custom Search Engine extension</a>
, or simply using the <a
  href="https://apps.apple.com/us/app/google/id284815942"
  
    target="_blank" rel="noopener"
  
>Google App</a>
.</p>
]]></content:encoded></item><item><title>Run Llama 2 locally on MacBook</title><link>https://imfing.com/til/run-llama-2-locally-on-macbook/</link><pubDate>Sun, 23 Jul 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/run-llama-2-locally-on-macbook/</guid><description>How to run Meta Llama 2 locally on a MacBook using llama.cpp with GPU acceleration.</description><content:encoded><![CDATA[<p>Last week, Meta released <a
  href="https://about.fb.com/news/2023/07/llama-2/"
  
    target="_blank" rel="noopener"
  
>Llama 2</a>
, an &ldquo;open source&rdquo; large language model that is free for research and commercial use. Within a few hours, the community has ported Llama 2 to <a
  href="https://github.com/ggerganov/llama.cpp"
  
    target="_blank" rel="noopener"
  
>llama.cpp</a>
 which makes it eaiser and more efficient to run Llama 2 locally.</p>
<h2 id="download-and-compile-llamacpp">
  <a class="anchor" href="#download-and-compile-llamacpp">
    <span class="icon icon-link"></span>
  </a>
  Download and compile llama.cpp
</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone https://github.com/ggerganov/llama.cpp.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> llama.cpp <span class="o">&amp;&amp;</span> <span class="nv">LLAMA_METAL</span><span class="o">=</span><span class="m">1</span> make
</span></span></code></pre></div><p>Note that <code>LLAMA_METAL</code> is set to <code>1</code> to enable using GPU on Apple Silicone. On my M1 Pro MacBook Pro, the compliation took about a few seconds.</p>
<h2 id="download-model-weights">
  <a class="anchor" href="#download-model-weights">
    <span class="icon icon-link"></span>
  </a>
  Download model weights
</h2>
<p>We will be using the 7B chat model that has been converted and quantified on <a
  href="https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML"
  
    target="_blank" rel="noopener"
  
>HuggingFace</a>
:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">wget <span class="s2">&#34;https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/resolve/main/llama-2-7b-chat.ggmlv3.q4_0.bin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">MODEL</span><span class="o">=</span>llama-2-7b-chat.ggmlv3.q4_0.bin
</span></span></code></pre></div><h2 id="run-model-inference">
  <a class="anchor" href="#run-model-inference">
    <span class="icon icon-link"></span>
  </a>
  Run model inference
</h2>
<p>Run compiled <code>main</code> with the prompt read from tty, and specify the model path with <code>-m</code> flag:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Prompt: &#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="o">&amp;&amp;</span> <span class="nb">read</span> PROMPT <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="o">&amp;&amp;</span> ./main <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        -t <span class="m">8</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        -ngl <span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        -m <span class="si">${</span><span class="nv">MODEL</span><span class="si">}</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --color <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        -c <span class="m">2048</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --temp 0.7 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --repeat_penalty 1.1 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        -n -1 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        -p <span class="s2">&#34;### Instruction: </span><span class="si">${</span><span class="nv">PROMPT</span><span class="si">}</span><span class="s2"> \n### Response:&#34;</span>
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">### Instruction: hello \n### Response: Hello! How can I help you today? [end of text]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">llama_print_timings:        load <span class="nb">time</span> <span class="o">=</span>  4777.73 ms
</span></span><span class="line"><span class="cl">llama_print_timings:      sample <span class="nb">time</span> <span class="o">=</span>     6.97 ms /    <span class="m">10</span> runs   <span class="o">(</span>    0.70 ms per token,  1434.10 tokens per second<span class="o">)</span>
</span></span><span class="line"><span class="cl">llama_print_timings: prompt <span class="nb">eval</span> <span class="nb">time</span> <span class="o">=</span>  1305.32 ms /    <span class="m">12</span> tokens <span class="o">(</span>  108.78 ms per token,     9.19 tokens per second<span class="o">)</span>
</span></span><span class="line"><span class="cl">llama_print_timings:        <span class="nb">eval</span> <span class="nb">time</span> <span class="o">=</span>   462.38 ms /     <span class="m">9</span> runs   <span class="o">(</span>   51.38 ms per token,    19.46 tokens per second<span class="o">)</span>
</span></span><span class="line"><span class="cl">llama_print_timings:       total <span class="nb">time</span> <span class="o">=</span>  1775.44 ms
</span></span></code></pre></div><h2 id="links">
  <a class="anchor" href="#links">
    <span class="icon icon-link"></span>
  </a>
  Links
</h2>
<ul>
<li><a
  href="https://gist.github.com/adrienbrault/b76631c56c736def9bc1bc2167b5d129"
  
    target="_blank" rel="noopener"
  
>https://gist.github.com/adrienbrault/b76631c56c736def9bc1bc2167b5d129</a>
</li>
</ul>
]]></content:encoded></item><item><title>Create interactive utility app with Streamlit</title><link>https://imfing.com/til/create-interactive-utility-app-with-streamlit/</link><pubDate>Mon, 10 Jul 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/create-interactive-utility-app-with-streamlit/</guid><description>&lt;blockquote>
&lt;p>&lt;a
href="https://streamlit.io/"
target="_blank" rel="noopener"
>Streamlit&lt;/a>
is an open-source Python library that makes it easy to create and share beautiful, custom web apps for machine learning and data science.&lt;/p>&lt;/blockquote>
&lt;p>Besides data science and machine learning, I found Streamlit can also be used for creating very simple utility apps.&lt;/p>
&lt;h2 id="postboy---a-simple-postman-like-app">
&lt;a class="anchor" href="#postboy---a-simple-postman-like-app">
&lt;span class="icon icon-link">&lt;/span>
&lt;/a>
Postboy - a simple Postman-like app
&lt;/h2>
&lt;p>For example, we can create a simple &lt;a
href="https://www.postman.com/"
target="_blank" rel="noopener"
>Postman&lt;/a>
-like app for testing REST APIs with Streamlit in just a few lines of code.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p><a
  href="https://streamlit.io/"
  
    target="_blank" rel="noopener"
  
>Streamlit</a>
 is an open-source Python library that makes it easy to create and share beautiful, custom web apps for machine learning and data science.</p></blockquote>
<p>Besides data science and machine learning, I found Streamlit can also be used for creating very simple utility apps.</p>
<h2 id="postboy---a-simple-postman-like-app">
  <a class="anchor" href="#postboy---a-simple-postman-like-app">
    <span class="icon icon-link"></span>
  </a>
  Postboy - a simple Postman-like app
</h2>
<p>For example, we can create a simple <a
  href="https://www.postman.com/"
  
    target="_blank" rel="noopener"
  
>Postman</a>
-like app for testing REST APIs with Streamlit in just a few lines of code.</p>
<p>Install Streamlit:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install streamlit
</span></span></code></pre></div><p>Create a file <code>app.py</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">streamlit</span> <span class="k">as</span> <span class="nn">st</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">st</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="s2">&#34;Postboy&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">url</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">text_input</span><span class="p">(</span><span class="s2">&#34;URL&#34;</span><span class="p">,</span> <span class="s2">&#34;https://jsonplaceholder.typicode.com/posts/1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">method</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">selectbox</span><span class="p">(</span><span class="s2">&#34;Method&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;GET&#34;</span><span class="p">,</span> <span class="s2">&#34;POST&#34;</span><span class="p">,</span> <span class="s2">&#34;PUT&#34;</span><span class="p">,</span> <span class="s2">&#34;DELETE&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">st</span><span class="o">.</span><span class="n">button</span><span class="p">(</span><span class="s2">&#34;Send Request&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">st</span><span class="o">.</span><span class="n">spinner</span><span class="p">(</span><span class="s2">&#34;Sending...&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="n">method</span><span class="o">=</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">st</span><span class="o">.</span><span class="n">code</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">(),</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">),</span> <span class="n">language</span><span class="o">=</span><span class="s2">&#34;json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">st</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span></code></pre></div><p>Run the app:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ streamlit run app.py
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  You can now view your Streamlit app in your browser.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  Local URL: http://localhost:8501
</span></span><span class="line"><span class="cl">  Network URL: http://192.168.0.11:8501
</span></span></code></pre></div><p>The app will be opened in a browser:</p>
<p><figure class="my-6">
    <img src="/til/create-interactive-utility-app-with-streamlit/image-20230710232005532.png" alt="image-20230710232005532"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>We can further add more features to the app, but the above code demonstrates how easy it is to create a simple utility app with Streamlit.</p>
]]></content:encoded></item><item><title>Handling JWT in Python</title><link>https://imfing.com/til/handling-jwt-in-python/</link><pubDate>Sun, 09 Jul 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/handling-jwt-in-python/</guid><description>Understanding JSON Web Tokens structure and how to handle JWT in Python.</description><content:encoded><![CDATA[<p><a
  href="https://en.wikipedia.org/wiki/JSON_Web_Token"
  
    target="_blank" rel="noopener"
  
>JSON Web Tokens (JWT)</a>
 is an open standard (<a
  href="https://tools.ietf.org/html/rfc7519"
  
    target="_blank" rel="noopener"
  
>RFC 7519</a>
) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.</p>
<h2 id="jwt-structure">
  <a class="anchor" href="#jwt-structure">
    <span class="icon icon-link"></span>
  </a>
  JWT Structure
</h2>
<p>JWTs consist of three parts separated by dots:</p>
<ul>
<li>Header</li>
<li>Payload</li>
<li>Signature</li>
</ul>
<h3 id="header">
  <a class="anchor" href="#header">
    <span class="icon icon-link"></span>
  </a>
  Header
</h3>
<p>The header contains the algorithm used to sign the token and the type of token. The header is base64 encoded and looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;alg&#34;</span><span class="p">:</span> <span class="s2">&#34;HS256&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;typ&#34;</span><span class="p">:</span> <span class="s2">&#34;JWT&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="payload">
  <a class="anchor" href="#payload">
    <span class="icon icon-link"></span>
  </a>
  Payload
</h3>
<p>Contains a set of claims.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;sub&#34;</span><span class="p">:</span> <span class="s2">&#34;1234567890&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;John Doe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;iat&#34;</span><span class="p">:</span> <span class="mi">1516239022</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="signature">
  <a class="anchor" href="#signature">
    <span class="icon icon-link"></span>
  </a>
  Signature
</h3>
<p>The signature is used to verify the integrity of the token. It is created by signing the header and payload with a secret key. The signature is base64 encoded and looks like this:</p>
<pre tabindex="0"><code>HMACSHA256(
  base64UrlEncode(header) + &#34;.&#34; +
  base64UrlEncode(payload),
  secret
)
</code></pre><h2 id="jwt-in-python">
  <a class="anchor" href="#jwt-in-python">
    <span class="icon icon-link"></span>
  </a>
  JWT in Python
</h2>
<p>In Python, we can use <a
  href="https://github.com/jpadilla/pyjwt"
  
    target="_blank" rel="noopener"
  
>pyjwt</a>
. Another option is <a
  href="https://github.com/mpdavis/python-jose"
  
    target="_blank" rel="noopener"
  
>python-jose</a>
 but it seems not actively maintained.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ pip install pyjwt<span class="o">[</span>crypto<span class="o">]</span>
</span></span></code></pre></div><p>Note that it&rsquo;s better to include the <code>[crypto]</code> to install the <a
  href="https://cryptography.io/"
  
    target="_blank" rel="noopener"
  
><code>cryptography</code></a>
 module for working with <a
  href="https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29"
  
    target="_blank" rel="noopener"
  
>RSA</a>
.</p>
<h3 id="decode-jwt-token">
  <a class="anchor" href="#decode-jwt-token">
    <span class="icon icon-link"></span>
  </a>
  Decode JWT Token
</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">decoded</span> <span class="o">=</span> <span class="n">jwt</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">encoded</span><span class="p">,</span> <span class="n">public_key</span><span class="p">,</span> <span class="n">algorithms</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;RS256&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="s1">&#39;some&#39;</span><span class="p">:</span> <span class="s1">&#39;payload&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>Sometimes, we may just want to decode the key without validation of the signature by setting the <code>verify_signature</code> option to <code>False</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">jwt</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">encoded</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;verify_signature&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="s1">&#39;some&#39;</span><span class="p">:</span> <span class="s1">&#39;payload&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>Similarly, we can read the headers without validation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="n">jwt</span><span class="o">.</span><span class="n">get_unverified_header</span><span class="p">(</span><span class="n">encoded</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="s1">&#39;alg&#39;</span><span class="p">:</span> <span class="s1">&#39;RS256&#39;</span><span class="p">,</span> <span class="s1">&#39;typ&#39;</span><span class="p">:</span> <span class="s1">&#39;JWT&#39;</span><span class="p">,</span> <span class="s1">&#39;kid&#39;</span><span class="p">:</span> <span class="s1">&#39;key-id-12345...&#39;</span><span class="p">}</span>
</span></span></code></pre></div><p>For more examples, see <a
  href="https://pyjwt.readthedocs.io/en/stable/usage.html"
  
    target="_blank" rel="noopener"
  
>here</a>
 from pyjwt documentation.</p>
<h3 id="links">
  <a class="anchor" href="#links">
    <span class="icon icon-link"></span>
  </a>
  Links
</h3>
<ul>
<li><a
  href="https://jwt.io/"
  
    target="_blank" rel="noopener"
  
>JWT.io</a>
</li>
<li><a
  href="https://en.wikipedia.org/wiki/JSON_Web_Token"
  
    target="_blank" rel="noopener"
  
>JSON Web Token</a>
</li>
<li><a
  href="https://tools.ietf.org/html/rfc7519"
  
    target="_blank" rel="noopener"
  
>RFC 7519</a>
</li>
<li><a
  href="https://github.com/jpadilla/pyjwt"
  
    target="_blank" rel="noopener"
  
>pyjwt</a>
</li>
<li><a
  href="https://github.com/mpdavis/python-jose"
  
    target="_blank" rel="noopener"
  
>python-jose</a>
</li>
<li><a
  href="https://cryptography.io/"
  
    target="_blank" rel="noopener"
  
>cryptography</a>
</li>
</ul>
]]></content:encoded></item><item><title>Question answering over documents with LLM</title><link>https://imfing.com/til/question-answering-over-documents-with-llm/</link><pubDate>Sat, 01 Jul 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/question-answering-over-documents-with-llm/</guid><description>Building a question answering system over documents using LLM and vector embeddings.</description><content:encoded><![CDATA[<p>One of the most popular applications for <a
  href="https://en.wikipedia.org/wiki/Large_language_model"
  
    target="_blank" rel="noopener"
  
>large language model (LLM)</a>
 is question answering over various types of documents, such a plain text, web pages, and PDFs. Usually, we want to make the model answer the question which it hasn&rsquo;t been trained on.</p>
<h2 id="overview">
  <a class="anchor" href="#overview">
    <span class="icon icon-link"></span>
  </a>
  Overview
</h2>
<p>There are mainly two steps involved:</p>
<ol>
<li>Data ingestion: load source documents and convert them into vector embeddings which will be stored in a vector database</li>
<li>Question answering: when given input question, convert to vector embedding first, then perform similarity search within the vector database, and top k results will be used as context for the LLM to generate answer to the question.</li>
</ol>
<p>Diagrams:</p>
<pre tabindex="0"><code>    ┌──────────────────┐
    │ Source Documents │
    └────────┬─────────┘
            │ Load &amp; Split
            ▼
    ┌──────────────────┐
    │    Text Chunks   │
    └────────┬─────────┘
            │ Embedding Model
            ▼
    ┌──────────────────┐
    │ Vector Embeddings│
    └──────────────────┘
</code></pre><pre tabindex="0"><code>                  ┌──────────────┐
                  │Question Query│
                  └───────┬──────┘
                          │ Embedding Model
                          ▼
Similarity Search ┌──────────────┐
      ┌───────────┤ Query Vector │
      │           └──────────────┘
      ▼
┌───────────┐     ┌───────────────┐
│ Vector DB ├───► │ Most K Similar│
└───────────┘     │ Source Chunks │
                  └───────┬───────┘
                          │ as context
                          │ plus question
                          ▼
                  ┌───────────────┐
                  │      LLM      │
                  └───────┬───────┘
                          │
                          ▼
                    Generated Answer
</code></pre><p><a
  href="https://github.com/hwchase17/langchain"
  title="Langchain"
    target="_blank" rel="noopener"
  
>Langchain</a>
 is an emerging framework for quickly prototyping and building LLM applications. In this post, I&rsquo;ll use it to make an example of how to do question answering over documents using LLM.</p>
<h2 id="data-ingestion">
  <a class="anchor" href="#data-ingestion">
    <span class="icon icon-link"></span>
  </a>
  Data ingestion
</h2>
<p>For demo purpose, we only process Markdown documents. I used <a
  href="https://github.com/mdn/content"
  
    target="_blank" rel="noopener"
  
>MDN Web Docs</a>
 HTTP section <code>files/en-us/web/http</code> for the documents.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">glob</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_markdown_files</span><span class="p">(</span><span class="n">directory</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">markdown_files</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="n">pattern</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">directory</span><span class="si">}</span><span class="s2">/**/*.md&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">markdown_files</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">recursive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">markdown_files</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">files</span> <span class="o">=</span> <span class="n">get_markdown_files</span><span class="p">(</span><span class="s1">&#39;/path/to/directory&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>Load documents, see <a
  href="https://python.langchain.com/docs/modules/data_connection/document_loaders/"
  title="Document Loaders"
    target="_blank" rel="noopener"
  
>Document Loaders</a>
 for other loaders for different kinds of documents.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.document_loaders</span> <span class="kn">import</span> <span class="n">UnstructuredMarkdownLoader</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">documents</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">file_path</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">loader</span> <span class="o">=</span> <span class="n">UnstructuredMarkdownLoader</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">docs</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="n">documents</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">docs</span><span class="p">)</span>
</span></span></code></pre></div><p>Split the documents with <a
  href="https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter"
  
    target="_blank" rel="noopener"
  
>RecursiveCharacterTextSplitter</a>
, which is the recommended one for generic text.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">text_splitter</span> <span class="o">=</span> <span class="n">RecursiveCharacterTextSplitter</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  	<span class="n">chunk_size</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  	<span class="n">chunk_overlap</span><span class="o">=</span><span class="mi">100</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">texts</span> <span class="o">=</span> <span class="n">text_splitter</span><span class="o">.</span><span class="n">split_documents</span><span class="p">(</span><span class="n">documents</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Number of chunks: </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">texts</span><span class="p">)</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Create vector embeddings using <a
  href="https://python.langchain.com/docs/ecosystem/integrations/huggingface#embeddings"
  title="Hugging Face Embeddings"
    target="_blank" rel="noopener"
  
>Hugging Face Embeddings</a>
 with <a
  href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2"
  title="Sentence Transformer"
    target="_blank" rel="noopener"
  
>all-MiniLM-L6-v2</a>
 model.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.embeddings</span> <span class="kn">import</span> <span class="n">HuggingFaceEmbeddings</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">embeddings</span> <span class="o">=</span> <span class="n">HuggingFaceEmbeddings</span><span class="p">(</span><span class="n">model_name</span><span class="o">=</span><span class="s1">&#39;all-MiniLM-L6-v2&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p><a
  href="https://github.com/chroma-core/chroma"
  title="Chroma DB"
    target="_blank" rel="noopener"
  
>Chroma</a>
 is a AI-native open-source vector database. Langchain provides <a
  href="https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
  title="Langchain Chroma Integration"
    target="_blank" rel="noopener"
  
>integration with Chroma vector store</a>
.</p>
<p>Create embeddings from splitted texts and persist embeddings into Chroma vector DB:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">chromadb.config</span> <span class="kn">import</span> <span class="n">Settings</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.vectorstores</span> <span class="kn">import</span> <span class="n">Chroma</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">chroma_settings</span> <span class="o">=</span> <span class="n">Settings</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">chroma_db_impl</span><span class="o">=</span><span class="s2">&#34;duckdb+parquet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">persist_directory</span><span class="o">=</span><span class="s1">&#39;./db&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">anonymized_telemetry</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="n">Chroma</span><span class="o">.</span><span class="n">from_documents</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">texts</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">embeddings</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">persist_directory</span><span class="o">=</span><span class="s1">&#39;./db&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">client_settings</span><span class="o">=</span><span class="n">chroma_settings</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db</span><span class="o">.</span><span class="n">persist</span><span class="p">()</span>
</span></span></code></pre></div><p>After persisting, the <code>db</code> directory structure shall look like this:</p>
<pre tabindex="0"><code>❯ exa ./db --tree -L 2
./db
├── chroma-collections.parquet
├── chroma-embeddings.parquet
└── index
   ├── id_to_uuid_257a38bd-b642-48ca-b23e-4182417aef0d.pkl
   ├── index_257a38bd-b642-48ca-b23e-4182417aef0d.bin
   ├── index_metadata_257a38bd-b642-48ca-b23e-4182417aef0d.pkl
   └── uuid_to_id_257a38bd-b642-48ca-b23e-4182417aef0d.pkl
</code></pre><h2 id="question-answering">
  <a class="anchor" href="#question-answering">
    <span class="icon icon-link"></span>
  </a>
  Question answering
</h2>
<p><a
  href="https://github.com/hwchase17/langchain"
  title="Langchain"
    target="_blank" rel="noopener"
  
>Langchain</a>
 provides <a
  href="https://python.langchain.com/docs/modules/chains/popular/vector_db_qa"
  title="Retrieval QA"
    target="_blank" rel="noopener"
  
>Retrieval QA</a>
 to allow us conveniently do question answering over an index.</p>
<p>In this example, <code>OpenAI</code> model will be used as the LLM to generate the answer.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">retriever</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">as_retriever</span><span class="p">(</span><span class="n">search_kwargs</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;k&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">qa</span> <span class="o">=</span> <span class="n">RetrievalQA</span><span class="o">.</span><span class="n">from_chain_type</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  	<span class="n">llm</span><span class="o">=</span><span class="n">OpenAI</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mf">0.1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  	<span class="n">chain_type</span><span class="o">=</span><span class="s2">&#34;stuff&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  	<span class="n">retriever</span><span class="o">=</span><span class="n">retriever</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  	<span class="n">return_source_documents</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">query</span> <span class="o">=</span> <span class="s2">&#34;What is http?&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">qa</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span><span class="p">[</span><span class="s1">&#39;result&#39;</span><span class="p">]</span>
</span></span></code></pre></div><pre tabindex="0"><code>HTTP (Hypertext Transfer Protocol) is an application-layer protocol for transmitting hypermedia documents, such as HTML. It was designed for communication between web browsers and web servers, but it can also be used for other purposes. HTTP follows a classical client-server model, with a client opening a connection to make a request, then waiting until it receives a response. HTTP is a stateless protocol, meaning that the server does not keep any data (state) between two requests.
</code></pre><h2 id="conclusion">
  <a class="anchor" href="#conclusion">
    <span class="icon icon-link"></span>
  </a>
  Conclusion
</h2>
<ul>
<li>This post depicts a typical flow for addressing questions over documents using LLM. It demonstrated that LLM can efficiently extract information and synthesize answers according on the context and corpus on which it has been trained.</li>
<li>There are many factors that can affect the output quality, what I can think of are:
<ul>
<li>In the ingestion step: <code>chunk_size</code>, <code>chunk_overlap</code> as well as the embedding model, the dimension of the embedding</li>
<li>In the question answering step: which LLM is used, the parameters of it (such as <code>temperature</code>), how the prompt is constructed, etc.</li>
</ul>
</li>
</ul>
<!--Link References-->
]]></content:encoded></item><item><title>Deploy Ghost instance to Fly.io</title><link>https://imfing.com/til/deploy-ghost-instance-to-fly-io/</link><pubDate>Wed, 28 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/deploy-ghost-instance-to-fly-io/</guid><description>Guide to self-hosting a Ghost blog instance on Fly.io free tier.</description><content:encoded><![CDATA[<blockquote>
<p>Disclaimer: Not affiliated with Ghost or Fly.io, this is only for evaluation purpose.</p></blockquote>
<p><a
  href="https://fly.io/"
  
    target="_blank" rel="noopener"
  
>Fly.io</a>
 comes with generous free tier allowance, which can be used to run some random side projects for free.
<a
  href="https://ghost.org/"
  
    target="_blank" rel="noopener"
  
>Ghost</a>
 is a WordPress like publishing platform. Unfortunately, there&rsquo;s no free tier provided by their company. Similar to WordPress, it provides an <a
  href="https://github.com/TryGhost/Ghost"
  
    target="_blank" rel="noopener"
  
>open-source version</a>
 that can be self-hosted.</p>
<h2 id="get-started">
  <a class="anchor" href="#get-started">
    <span class="icon icon-link"></span>
  </a>
  Get Started
</h2>
<p>To get started, first create an account on <a
  href="https://fly.io/"
  
    target="_blank" rel="noopener"
  
>Fly.io</a>
, and install <a
  href="https://fly.io/docs/hands-on/install-flyctl/"
  
    target="_blank" rel="noopener"
  
><code>flyctl</code></a>
:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">brew install flyctl
</span></span></code></pre></div><p>Open your favorite terminal app and sign into the account:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">fly auth login
</span></span></code></pre></div><p>We can view a list of available regions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">fly platform regions
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">CODE	NAME                        	GATEWAY	PAID PLAN ONLY
</span></span><span class="line"><span class="cl">ams 	Amsterdam, Netherlands      	✓
</span></span><span class="line"><span class="cl">arn 	Stockholm, Sweden
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><h2 id="deploy-ghost">
  <a class="anchor" href="#deploy-ghost">
    <span class="icon icon-link"></span>
  </a>
  Deploy Ghost
</h2>
<p><a
  href="https://blixtdev.com/how-to-host-a-ghost-blog-for-free-on-fly-io/"
  
    target="_blank" rel="noopener"
  
>This article</a>
 did a great job explaining every steps for deploying to <a
  href="https://fly.io/"
  
    target="_blank" rel="noopener"
  
>Fly.io</a>
.
Remember to choose the same region in each step.</p>
<h3 id="create-storage">
  <a class="anchor" href="#create-storage">
    <span class="icon icon-link"></span>
  </a>
  Create storage
</h3>
<p>Free tier offers 3GB volume, so here we create a volume with 3GB:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">flyctl volumes create data --size <span class="m">3</span>
</span></span></code></pre></div><h3 id="initialize-app">
  <a class="anchor" href="#initialize-app">
    <span class="icon icon-link"></span>
  </a>
  Initialize app
</h3>
<p>In next step, we initialize the ghost application on Fly.io. See all the<a
  href="https://hub.docker.com/_/ghost"
  
    target="_blank" rel="noopener"
  
> latest available Docker images for Ghost</a>
.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir my-blog
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> my-blog
</span></span><span class="line"><span class="cl">flyctl launch --image<span class="o">=</span>ghost:5 --no-deploy
</span></span></code></pre></div><p>After this step, a <code>fly.toml</code> file will be generated. It contains the configuration for the app we will deploy to Fly.io.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># fly.toml app configuration file generated for fing-ghost on 2023-06-25T19:02:40+01:00</span>
</span></span><span class="line"><span class="cl"><span class="c">#</span>
</span></span><span class="line"><span class="cl"><span class="c"># See https://fly.io/docs/reference/configuration/ for information about how to use this file.</span>
</span></span><span class="line"><span class="cl"><span class="c">#</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span> <span class="p">=</span> <span class="s2">&#34;&lt;your-blog&gt;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">primary_region</span> <span class="p">=</span> <span class="s2">&#34;lhr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">image</span> <span class="p">=</span> <span class="s2">&#34;ghost:5-alpine&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http_service</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">internal_port</span> <span class="p">=</span> <span class="mi">8080</span>
</span></span><span class="line"><span class="cl">  <span class="nx">force_https</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">auto_stop_machines</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">auto_start_machines</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">min_machines_running</span> <span class="p">=</span> <span class="mi">0</span>
</span></span></code></pre></div><h3 id="update-config">
  <a class="anchor" href="#update-config">
    <span class="icon icon-link"></span>
  </a>
  Update config
</h3>
<p>We need to update the <code>fly.toml</code> configuration file to include some environment variables for Ghost instance. And we mount the volume we created earlier to the application.
Make sure to change the port to <code>2368</code> which the Ghost instance listens to by default.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">app</span> <span class="p">=</span> <span class="s2">&#34;&lt;your-blog&gt;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">primary_region</span> <span class="p">=</span> <span class="s2">&#34;lhr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">image</span> <span class="p">=</span> <span class="s2">&#34;ghost:5-alpine&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http_service</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">internal_port</span> <span class="p">=</span> <span class="mi">2368</span>
</span></span><span class="line"><span class="cl">  <span class="nx">force_https</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">auto_stop_machines</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">auto_start_machines</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">min_machines_running</span> <span class="p">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">env</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">url</span> <span class="p">=</span> <span class="s2">&#34;https://&lt;your-blog&gt;.fly.dev&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">database__client</span> <span class="p">=</span> <span class="s2">&#34;sqlite3&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">database__connection__filename</span> <span class="p">=</span> <span class="s2">&#34;content/data/ghost.db&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">database__useNullAsDefault</span> <span class="p">=</span> <span class="s2">&#34;true&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">database__debug</span> <span class="p">=</span> <span class="s2">&#34;false&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mounts</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">source</span><span class="p">=</span><span class="s2">&#34;data&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">destination</span><span class="p">=</span><span class="s2">&#34;/var/lib/ghost/content&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">services</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">internal_port</span> <span class="p">=</span> <span class="mi">2368</span>
</span></span></code></pre></div><h3 id="deploy">
  <a class="anchor" href="#deploy">
    <span class="icon icon-link"></span>
  </a>
  Deploy
</h3>
<p>Simply do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">fly deploy
</span></span></code></pre></div><p>After a while, the instance should be up and running on <code>https://&lt;your-app&gt;.fly.dev</code>. And we can go to <code>https://&lt;your-app&gt;.fly.dev/ghost</code> to do the initial setup.</p>
<h2 id="conclusion-and-thoughts">
  <a class="anchor" href="#conclusion-and-thoughts">
    <span class="icon icon-link"></span>
  </a>
  Conclusion and thoughts
</h2>
<p><a
  href="https://fly.io/dashboard"
  
    target="_blank" rel="noopener"
  
>Fly.io dashboard</a>
 is a great place to view the status and the logs for the deployed application.</p>
<p>After I played a while, I found that the instance <strong>was constantly restarted due to out of memory issue</strong>. Their free plan says <code>Up to 3 shared-cpu-1x 256mb VMs</code>.</p>
<p>My thoughts is that: it&rsquo;s not practical to run a full Ghost instance on Fly.io only on their free plan.
However, if you are just interested and want to try it out, then it might be a good place to deploy an application very conveniently.
In most case, if you only host a simple blog, then static website hosting (<a
  href="https://docs.github.com/en/pages"
  
    target="_blank" rel="noopener"
  
>GitHub Pages</a>
, <a
  href="https://pages.cloudflare.com/"
  
    target="_blank" rel="noopener"
  
>Cloudflare Pages</a>
, <a
  href="https://www.netlify.com/"
  
    target="_blank" rel="noopener"
  
>Netlify</a>
) might be better choice.</p>
]]></content:encoded></item><item><title>Custom middleware for FastAPI application</title><link>https://imfing.com/til/custom-middleware-for-fastapi-application/</link><pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/custom-middleware-for-fastapi-application/</guid><description>How to implement custom middleware for FastAPI using Starlette BaseHTTPMiddleware.</description><content:encoded><![CDATA[<p><a
  href="https://github.com/tiangolo/fastapi"
  
    target="_blank" rel="noopener"
  
>FastAPI</a>
 is a Python web framework for building APIs.
By using a middleware, we are able to process request and response before/after they get handled by the application.</p>
<h2 id="implement-custom-middleware">
  <a class="anchor" href="#implement-custom-middleware">
    <span class="icon icon-link"></span>
  </a>
  Implement custom middleware
</h2>
<p><a
  href="https://github.com/encode/starlette"
  
    target="_blank" rel="noopener"
  
>Starlette</a>
 is a lightweight ASGI framework/toolkit on which FastAPI is based.
It provides <a
  href="https://www.starlette.io/middleware/#basehttpmiddleware"
  
    target="_blank" rel="noopener"
  
><code>BaseHTTPMiddleware</code></a>
 class for us to implement custom middleware.
It&rsquo;s required to override the <code>async def dispatch(request, call_next)</code> method.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">starlette.middleware.base</span> <span class="kn">import</span> <span class="n">BaseHTTPMiddleware</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CustomHeaderMiddleware</span><span class="p">(</span><span class="n">BaseHTTPMiddleware</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">,</span> <span class="n">header_value</span><span class="o">=</span><span class="s1">&#39;Example&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">header_value</span> <span class="o">=</span> <span class="n">header_value</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">dispatch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">call_next</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">call_next</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">&#39;Custom&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">header_value</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><p>The example middleware above simply adds a <code>Custom</code> header to the response.</p>
<p>FastAPI also supports using decorator to <a
  href="https://fastapi.tiangolo.com/tutorial/middleware/#create-a-middleware"
  
    target="_blank" rel="noopener"
  
>create a middleware</a>
:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@app.middleware</span><span class="p">(</span><span class="s2">&#34;http&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">custom_middleware</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</span><span class="p">,</span> <span class="n">call_next</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">	<span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">call_next</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><p>It should have the same effect as overriding the <code>dispatch</code> method.
I personally prefer the below way to programmatically add the middleware to the FastAPI application.</p>
<h2 id="add-middleware-to-fastapi">
  <a class="anchor" href="#add-middleware-to-fastapi">
    <span class="icon icon-link"></span>
  </a>
  Add middleware to FastAPI
</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">add_middleware</span><span class="p">(</span><span class="n">CustomHeaderMiddleware</span><span class="p">,</span> <span class="n">header_value</span><span class="o">=</span><span class="s1">&#39;Hello&#39;</span><span class="p">)</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Notes on "Langchain for LLM Application Development"</title><link>https://imfing.com/til/notes-on-langchain-for-llm-application-development/</link><pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/notes-on-langchain-for-llm-application-development/</guid><description>Course notes on LangChain for LLM Application Development from DeepLearning.AI.</description><content:encoded><![CDATA[<p>Course Link: <a
  href="https://www.deeplearning.ai/short-courses/langchain-for-llm-application-development/"
  
    target="_blank" rel="noopener"
  
>LangChain for LLM Application Development - DeepLearning.AI</a>
</p>
<p>Previous notes:</p>
<ul>
<li><a
  href="./notes-on-building-systems-with-the-chatgpt-api"
  
>Notes on &ldquo;Building Systems with the ChatGPT API&rdquo;</a>
</li>
</ul>
<h2 id="introduction">
  <a class="anchor" href="#introduction">
    <span class="icon icon-link"></span>
  </a>
  Introduction
</h2>
<p>What is <a
  href="https://github.com/hwchase17/langchain"
  
    target="_blank" rel="noopener"
  
>Langchain</a>
</p>
<ul>
<li>Open-source development framework for LLM applications</li>
<li>Provide both Python and JavaScript/Typescript packages</li>
</ul>
<p>Modular components which can be combined to build end-to-end applications.</p>
<h2 id="models-prompts-and-output-parsers">
  <a class="anchor" href="#models-prompts-and-output-parsers">
    <span class="icon icon-link"></span>
  </a>
  Models, Prompts and Output Parsers
</h2>
<h3 id="openai-api">
  <a class="anchor" href="#openai-api">
    <span class="icon icon-link"></span>
  </a>
  OpenAI API
</h3>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">openai</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_completion</span><span class="p">(</span><span class="n">prompt</span><span class="p">,</span> <span class="n">model</span><span class="o">=</span><span class="s2">&#34;gpt-3.5-turbo&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span> <span class="o">=</span> <span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="n">prompt</span><span class="p">}]</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">openai</span><span class="o">.</span><span class="n">ChatCompletion</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">messages</span><span class="o">=</span><span class="n">messages</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">temperature</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">choices</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">message</span><span class="p">[</span><span class="s2">&#34;content&#34;</span><span class="p">]</span>
</span></span></code></pre></div><h4 id="langchain">
  <a class="anchor" href="#langchain">
    <span class="icon icon-link"></span>
  </a>
  LangChain
</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.chat_models</span> <span class="kn">import</span> <span class="n">ChatOpenAI</span>
</span></span><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mf">0.0</span><span class="p">)</span>
</span></span></code></pre></div><h5 id="prompt-template">
  <a class="anchor" href="#prompt-template">
    <span class="icon icon-link"></span>
  </a>
  Prompt template
</h5>
<p>A <a
  href="https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates"
  
    target="_blank" rel="noopener"
  
>prompt template</a>
 refers to a reproducible way to generate a prompt.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain</span> <span class="kn">import</span> <span class="n">PromptTemplate</span>
</span></span><span class="line"><span class="cl"><span class="n">template</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;/
</span></span></span><span class="line"><span class="cl"><span class="s2">You are a naming consultant for new companies.
</span></span></span><span class="line"><span class="cl"><span class="s2">What is a good name for a company that makes </span><span class="si">{product}</span><span class="s2">?
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt</span> <span class="o">=</span> <span class="n">PromptTemplate</span><span class="o">.</span><span class="n">from_template</span><span class="p">(</span><span class="n">template</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">product</span><span class="o">=</span><span class="s2">&#34;colorful socks&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h4 id="output-parsers">
  <a class="anchor" href="#output-parsers">
    <span class="icon icon-link"></span>
  </a>
  Output parsers
</h4>
<p>Language models output text. Output parsers allows us to get structrued information out of the LLM response.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">parser</span> <span class="o">=</span> <span class="n">PydanticOutputParser</span><span class="p">(</span><span class="n">pydantic_object</span><span class="o">=</span><span class="n">Joke</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">prompt</span> <span class="o">=</span> <span class="n">PromptTemplate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">template</span><span class="o">=</span><span class="s2">&#34;Answer the user query.</span><span class="se">\n</span><span class="si">{format_instructions}</span><span class="se">\n</span><span class="si">{query}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">input_variables</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;query&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">partial_variables</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;format_instructions&#34;</span><span class="p">:</span> <span class="n">parser</span><span class="o">.</span><span class="n">get_format_instructions</span><span class="p">()}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="memory">
  <a class="anchor" href="#memory">
    <span class="icon icon-link"></span>
  </a>
  Memory
</h2>
<p>LLMs are &ldquo;stateless&rdquo;.</p>
<ul>
<li><a
  href="https://python.langchain.com/en/latest/modules/memory/types/buffer.html#conversationbuffermemory"
  
    target="_blank" rel="noopener"
  
>ConversationBufferMemory</a>

<ul>
<li>allows for storing of messages and then extracts the messages in a variable</li>
</ul>
</li>
<li><a
  href="https://python.langchain.com/en/latest/modules/memory/types/buffer_window.html#conversationbufferwindowmemory"
  
    target="_blank" rel="noopener"
  
>ConversationBufferWindowMemory</a>

<ul>
<li>keeps a list of the interactions of the conversation over time. It only uses the last K interactions</li>
</ul>
</li>
<li><a
  href="https://python.langchain.com/en/latest/modules/memory/types/token_buffer.html#conversationtokenbuffermemory"
  
    target="_blank" rel="noopener"
  
>ConversationTokenBufferMemory</a>

<ul>
<li>keeps a buffer of recent interactions in memory, and uses token length rather than number of interactions to determine when to flush interactions</li>
</ul>
</li>
<li><a
  href="https://python.langchain.com/en/latest/modules/memory/types/summary.html#conversationsummarymemory"
  
    target="_blank" rel="noopener"
  
>ConversationSummaryMemory</a>

<ul>
<li>creates a summary of the conversation over time</li>
</ul>
</li>
</ul>
<p>Example usage:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">llm</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mf">0.0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">memory</span> <span class="o">=</span> <span class="n">ConversationBufferMemory</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">conversation</span> <span class="o">=</span> <span class="n">ConversationChain</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">llm</span><span class="o">=</span><span class="n">llm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">memory</span> <span class="o">=</span> <span class="n">memory</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">verbose</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><h3 id="additional-memory-types">
  <a class="anchor" href="#additional-memory-types">
    <span class="icon icon-link"></span>
  </a>
  Additional Memory Types
</h3>
<p>Vector data memory</p>
<ul>
<li>store text in a vector database and retrieve the most relevant blocks of text</li>
</ul>
<p>Entity memories</p>
<ul>
<li>using an LLM, it remembers details about specific entities</li>
</ul>
<p>Conversation can also be stored in conventional database (key-value store or SQL).</p>
<h2 id="chains">
  <a class="anchor" href="#chains">
    <span class="icon icon-link"></span>
  </a>
  Chains
</h2>
<blockquote>
<p>LangChain provides the Chain interface for such &ldquo;chained&rdquo; applications. We define a Chain very generically as a sequence of calls to components, which can include other chains.</p></blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">chain</span> <span class="o">=</span> <span class="n">SimpleSequentialChain</span><span class="p">(</span><span class="n">chains</span><span class="o">=</span><span class="p">[</span><span class="n">chain_one</span><span class="p">,</span> <span class="n">chain_two</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="n">chain</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s2">&#34;input&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="sequential">
  <a class="anchor" href="#sequential">
    <span class="icon icon-link"></span>
  </a>
  Sequential
</h3>
<ul>
<li><code>SimpleSequentialChain</code>: The simplest form of sequential chains, where each step has a <strong>singular input/output</strong>, and the output of one step is the input to the next.</li>
<li><code>SequentialChain</code>: A more general form of sequential chains, allowing for <strong>multiple inputs/outputs</strong>.</li>
</ul>
<h3 id="router">
  <a class="anchor" href="#router">
    <span class="icon icon-link"></span>
  </a>
  Router
</h3>
<p><code>RouterChain</code>: dynamically selects the next chain to use for a given input.</p>
<p>For example, use <code>MultiPromptChain</code> to create a question-answering chain that selects the prompt which is most relevant for a given question, and then answers the question using that prompt.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">chain</span> <span class="o">=</span> <span class="n">MultiPromptChain</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">router_chain</span><span class="o">=</span><span class="n">router_chain</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">destination_chains</span><span class="o">=</span><span class="n">destination_chains</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">default_chain</span><span class="o">=</span><span class="n">default_chain</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>See <a
  href="https://python.langchain.com/docs/modules/chains/foundational/router"
  
    target="_blank" rel="noopener"
  
>Langchain Router</a>
 for full example.</p>
<h2 id="question-and-answer">
  <a class="anchor" href="#question-and-answer">
    <span class="icon icon-link"></span>
  </a>
  Question and Answer
</h2>
<p>Use LLM to answer questions over documents.</p>
<p>Embeddings:</p>
<ul>
<li>Embedding vector captures content/meaning</li>
<li>Text with similar content will have similar vectors</li>
</ul>
<ol>
<li>Split document to small chunks</li>
<li>For each chunk, create embeddings and store into vector database</li>
<li>When query came in, first create an embedding for that query</li>
<li>Then compare all vectors in the vector database, and pick the n most similar</li>
<li>These then get passed to LLM to get back the final answer</li>
</ol>
<p>Use Langchain&rsquo;s <a
  href="https://python.langchain.com/docs/modules/data_connection/text_embedding/"
  
    target="_blank" rel="noopener"
  
>OpenAIEmbeddings</a>
 to create embedding for query:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.embeddings</span> <span class="kn">import</span> <span class="n">OpenAIEmbeddings</span>
</span></span><span class="line"><span class="cl"><span class="n">embeddings</span> <span class="o">=</span> <span class="n">OpenAIEmbeddings</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">embed</span> <span class="o">=</span> <span class="n">embeddings</span><span class="o">.</span><span class="n">embed_query</span><span class="p">(</span><span class="s2">&#34;Hi my name is Harrison&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="n">DocArrayInMemorySearch</span><span class="o">.</span><span class="n">from_documents</span><span class="p">(</span><span class="n">docs</span><span class="p">,</span> <span class="n">embeddings</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">query</span> <span class="o">=</span> <span class="s2">&#34;Please suggest a shirt with sunblocking&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">docs</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">similarity_search</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
</span></span></code></pre></div><p>Use <a
  href="https://api.python.langchain.com/en/latest/modules/chains.html#langchain.chains.RetrievalQA"
  
    target="_blank" rel="noopener"
  
><code>RetrievalQA</code></a>
 chain:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">retriever</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">as_retriever</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">llm</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">temperature</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">qa_stuff</span> <span class="o">=</span> <span class="n">RetrievalQA</span><span class="o">.</span><span class="n">from_chain_type</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">llm</span><span class="o">=</span><span class="n">llm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">chain_type</span><span class="o">=</span><span class="s2">&#34;stuff&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">retriever</span><span class="o">=</span><span class="n">retriever</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">verbose</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">query</span> <span class="o">=</span>  <span class="s2">&#34;Please list all your shirts with sun protection in a table </span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">in markdown and summarize each one.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">qa_stuff</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">display</span><span class="p">(</span><span class="n">Markdown</span><span class="p">(</span><span class="n">response</span><span class="p">))</span>
</span></span></code></pre></div><p><strong>Stuff method</strong>: simply stuff all data into the prompt context to pass to the language model</p>
<ul>
<li>Pros: it makes a single call to the LLM, which has access to all the data at once.</li>
<li>Cons: LLMs have a context length, the prompt may exceed the limit.</li>
</ul>
<p>Additional methods:</p>
<ol>
<li>Map reduce: call LLM for each chunk plus the query, then aggregate the answers and call LLM again for final answer.</li>
<li>Refine: builds upon the answer from the previous document</li>
<li>Map rerank: let LLM give each chunk a score, then select the highest score as final answer</li>
</ol>
<h2 id="evaluation">
  <a class="anchor" href="#evaluation">
    <span class="icon icon-link"></span>
  </a>
  Evaluation
</h2>
<p>Turn on <code>debug</code> to view the output of each step.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">langchain</span>
</span></span><span class="line"><span class="cl"><span class="n">langchain</span><span class="o">.</span><span class="n">debug</span> <span class="o">=</span> <span class="kc">True</span>
</span></span></code></pre></div><p>Use <a
  href="https://python.langchain.com/docs/guides/evaluation/question_answering#evaluation"
  
    target="_blank" rel="noopener"
  
><code>QAEvalChain</code></a>
 :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.evaluation.qa</span> <span class="kn">import</span> <span class="n">QAEvalChain</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">llm</span> <span class="o">=</span> <span class="n">ChatOpenAI</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">eval_chain</span> <span class="o">=</span> <span class="n">QAEvalChain</span><span class="o">.</span><span class="n">from_llm</span><span class="p">(</span><span class="n">llm</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">graded_outputs</span> <span class="o">=</span> <span class="n">eval_chain</span><span class="o">.</span><span class="n">evaluate</span><span class="p">(</span><span class="n">examples</span><span class="p">,</span> <span class="n">predictions</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="agents">
  <a class="anchor" href="#agents">
    <span class="icon icon-link"></span>
  </a>
  Agents
</h2>
<p>An agent has access to a suite of tools, and determines which ones to use depending on the user input. Agents can use multiple tools, and use the output of one tool as the input to the next. See more on its <a
  href="https://python.langchain.com/docs/modules/agents/"
  
    target="_blank" rel="noopener"
  
>doc</a>
.</p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">llm</span> <span class="o">=</span> <span class="n">OpenAI</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">tools</span> <span class="o">=</span> <span class="n">load_tools</span><span class="p">([</span><span class="s2">&#34;serpapi&#34;</span><span class="p">,</span> <span class="s2">&#34;llm-math&#34;</span><span class="p">],</span> <span class="n">llm</span><span class="o">=</span><span class="n">llm</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="n">initialize_agent</span><span class="p">(</span><span class="n">tools</span><span class="p">,</span> <span class="n">llm</span><span class="p">,</span> <span class="n">agent</span><span class="o">=</span><span class="n">AgentType</span><span class="o">.</span><span class="n">ZERO_SHOT_REACT_DESCRIPTION</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">agent</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s2">&#34;Who is Leo DiCaprio&#39;s girlfriend? What is her current age raised to the 0.43 power?&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Create custom tool:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents</span> <span class="kn">import</span> <span class="n">tool</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">date</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">time</span><span class="p">(</span><span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Returns todays date, use this for any </span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">    questions related to knowing todays date. </span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">    The input should always be an empty string, </span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">    and this function will always return todays </span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">    date - any date mathmatics should occur </span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">    outside this function.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">date</span><span class="o">.</span><span class="n">today</span><span class="p">())</span>
</span></span></code></pre></div><p>For more, see <a
  href="https://python.langchain.com/docs/modules/agents/tools/how_to/custom_tools"
  
    target="_blank" rel="noopener"
  
>Define Custom Tools</a>
.</p>
<h2 id="conclusion">
  <a class="anchor" href="#conclusion">
    <span class="icon icon-link"></span>
  </a>
  Conclusion
</h2>
<p>My thoughts:</p>
<ul>
<li><a
  href="https://github.com/hwchase17/langchain"
  
    target="_blank" rel="noopener"
  
>Langchain</a>
 is very powerful and handy tool for developing LLM based applications</li>
<li>It is still evolving, new functionalities are introduced and APIs may change</li>
</ul>
]]></content:encoded></item><item><title>Set up Hugo with Tailwind CSS in 2023</title><link>https://imfing.com/til/set-up-hugo-with-tailwind-css-in-2023/</link><pubDate>Sat, 24 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/set-up-hugo-with-tailwind-css-in-2023/</guid><description>How to set up Hugo with Tailwind CSS v3 using native PostCSS support.</description><content:encoded><![CDATA[<p>With the release of <a
  href="https://github.com/gohugoio/hugo/releases/tag/v0.112.0"
  
    target="_blank" rel="noopener"
  
>v0.112.0</a>
, Hugo added the native support for <a
  href="https://tailwindcss.com/blog/tailwindcss-v3"
  
    target="_blank" rel="noopener"
  
>TailwindCSS v3.x</a>
. The author of Hugo provided an example repository setting up TailwindCSS v3: <a
  href="https://github.com/bep/hugo-starter-tailwind-basic"
  
    target="_blank" rel="noopener"
  
>bep/hugo-starter-tailwind-basic</a>
.</p>
<p>Note that it uses PostCSS so make sure to use Hugo extended version greater than v0.112.0.</p>
<h2 id="how-does-it-work">
  <a class="anchor" href="#how-does-it-work">
    <span class="icon icon-link"></span>
  </a>
  How does it work?
</h2>
<p>According to the release note:</p>
<blockquote>
<p>The basic concept is to add <code>hugo_stats.json</code> to the server watcher list in Hugo and trigger a new TailwindCSS build only whenever either this file or the main CSS file changes.</p></blockquote>
<p>Add the following sections to the <code>config.toml</code> or <code>hugo.toml</code> configuration file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">module</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">[[</span><span class="nx">module</span><span class="p">.</span><span class="nx">mounts</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;assets&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">target</span> <span class="p">=</span> <span class="s2">&#34;assets&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[[</span><span class="nx">module</span><span class="p">.</span><span class="nx">mounts</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;hugo_stats.json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">target</span> <span class="p">=</span> <span class="s2">&#34;assets/watching/hugo_stats.json&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">writeStats</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">[[</span><span class="nx">build</span><span class="p">.</span><span class="nx">cachebusters</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;assets/watching/hugo_stats\\.json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">target</span> <span class="p">=</span> <span class="s2">&#34;styles\\.css&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[[</span><span class="nx">build</span><span class="p">.</span><span class="nx">cachebusters</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;(postcss|tailwind)\\.config\\.js&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">target</span> <span class="p">=</span> <span class="s2">&#34;css&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[[</span><span class="nx">build</span><span class="p">.</span><span class="nx">cachebusters</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;assets/.*\\.(js|ts|jsx|tsx)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">target</span> <span class="p">=</span> <span class="s2">&#34;js&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[[</span><span class="nx">build</span><span class="p">.</span><span class="nx">cachebusters</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">source</span> <span class="p">=</span> <span class="s2">&#34;assets/.*\\.(.*)$&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">target</span> <span class="p">=</span> <span class="s2">&#34;$1&#34;</span>
</span></span></code></pre></div><p>Also update the <code>tailwind.config.js</code> file to</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">content</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;./hugo_stats.json&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="migration">
  <a class="anchor" href="#migration">
    <span class="icon icon-link"></span>
  </a>
  Migration
</h2>
<p>Previously, my <code>package.json</code> file looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;dev&#34;</span><span class="p">:</span> <span class="s2">&#34;NODE_ENV=development ./node_modules/tailwindcss/lib/cli.js -i ./static/tailwind.css -o ./static/main.css -w&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="s2">&#34;NODE_ENV=production ./node_modules/tailwindcss/lib/cli.js -i ./static/tailwind.css -o ./static/main.css --minify&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tailwindcss&#34;</span><span class="p">:</span> <span class="s2">&#34;^3.2.7&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;@tailwindcss/typography&#34;</span><span class="p">:</span> <span class="s2">&#34;^0.5.9&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It includes separate steps:</p>
<ol>
<li>Compile Tailwind CSS file to <code>static/main.css</code></li>
<li>Build Hugo site</li>
</ol>
<p>Sometimes, when making changes to styles, I had to manually restart the Hugo dev server to make it take effect.</p>
<p>After:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;devDependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;@tailwindcss/typography&#34;</span><span class="p">:</span> <span class="s2">&#34;^0.5.9&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;autoprefixer&#34;</span><span class="p">:</span> <span class="s2">&#34;^10.4.14&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;postcss&#34;</span><span class="p">:</span> <span class="s2">&#34;^8.4.23&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;postcss-cli&#34;</span><span class="p">:</span> <span class="s2">&#34;^10.1.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prettier&#34;</span><span class="p">:</span> <span class="s2">&#34;^2.8.8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prettier-plugin-go-template&#34;</span><span class="p">:</span> <span class="s2">&#34;^0.0.13&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tailwindcss&#34;</span><span class="p">:</span> <span class="s2">&#34;^3.3.2&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We don&rsquo;t need the above <code>dev</code> and <code>build</code> command since all can be done via <code>hugo</code>. Win!</p>
<p>Create <code>postcss.config.js</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tailwindConfig</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">HUGO_FILE_TAILWIND_CONFIG_JS</span> <span class="o">||</span> <span class="s2">&#34;./tailwind.config.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tailwind</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;tailwindcss&#34;</span><span class="p">)(</span><span class="nx">tailwindConfig</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">autoprefixer</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&#34;autoprefixer&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// eslint-disable-next-line no-process-env
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="nx">tailwind</span><span class="p">,</span> <span class="p">...(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">HUGO_ENVIRONMENT</span> <span class="o">===</span> <span class="s2">&#34;production&#34;</span> <span class="o">?</span> <span class="p">[</span><span class="nx">autoprefixer</span><span class="p">]</span> <span class="o">:</span> <span class="p">[])],</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>I put <code>styles.css</code> file under <code>./assets/styles</code>.
Then include it properly inside the Hugo html template file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">&lt;!</span><span class="o">--</span> <span class="nx">Styles</span> <span class="o">--</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{{</span> <span class="err">$</span><span class="nx">options</span> <span class="o">:=</span> <span class="nx">dict</span> <span class="s">&#34;inlineImports&#34;</span> <span class="kc">true</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">{{</span> <span class="err">$</span><span class="nx">styles</span> <span class="o">:=</span> <span class="nx">resources</span><span class="p">.</span><span class="nx">Get</span> <span class="s">&#34;styles.css&#34;</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">{{</span> <span class="err">$</span><span class="nx">styles</span> <span class="p">=</span> <span class="err">$</span><span class="nx">styles</span> <span class="p">|</span> <span class="nx">resources</span><span class="p">.</span><span class="nx">PostCSS</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">{{</span> <span class="k">if</span> <span class="nx">hugo</span><span class="p">.</span><span class="nx">IsProduction</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">	<span class="p">{{</span> <span class="err">$</span><span class="nx">styles</span> <span class="p">=</span> <span class="err">$</span><span class="nx">styles</span> <span class="p">|</span> <span class="nx">minify</span> <span class="p">|</span> <span class="nx">fingerprint</span> <span class="p">|</span> <span class="nx">resources</span><span class="p">.</span><span class="nx">PostProcess</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">{{</span> <span class="nx">end</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nx">link</span> <span class="nx">href</span><span class="p">=</span><span class="s">&#34;{{ $styles.RelPermalink }}&#34;</span> <span class="nx">rel</span><span class="p">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="o">/</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>After all these steps, the dev and build command will simply:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># dev</span>
</span></span><span class="line"><span class="cl">hugo server
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># build</span>
</span></span><span class="line"><span class="cl">hugo --gc --minify
</span></span></code></pre></div><p>Enjoy!</p>
]]></content:encoded></item><item><title>Write pytest tests for argparse</title><link>https://imfing.com/til/write-pytest-tests-for-argparse/</link><pubDate>Thu, 22 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/write-pytest-tests-for-argparse/</guid><description>Techniques for writing pytest unit tests for Python argparse code.</description><content:encoded><![CDATA[<p>Writing unit tests for Python code that uses <a
  href="https://docs.python.org/3/library/argparse.html"
  
    target="_blank" rel="noopener"
  
>argparse</a>
 can be non-trivial.</p>
<h2 id="call-main-with-optional-arguments">
  <a class="anchor" href="#call-main-with-optional-arguments">
    <span class="icon icon-link"></span>
  </a>
  Call <code>main</code> with optional arguments
</h2>
<p>One way suggested by <a
  href="https://til.simonwillison.net/pytest/pytest-argparse"
  
    target="_blank" rel="noopener"
  
>Simon Willison</a>
 was to make <code>main()</code> function take optional arguments:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">parsed_args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span></span></code></pre></div><p>This makes it easy to just test <code>main()</code> function by calling it with different arguments:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="s2">&#34;option&#34;</span><span class="p">,</span> <span class="p">(</span><span class="s2">&#34;-h&#34;</span><span class="p">,</span> <span class="s2">&#34;--help&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_help</span><span class="p">(</span><span class="n">capsys</span><span class="p">,</span> <span class="n">option</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">main</span><span class="p">([</span><span class="n">option</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div><h2 id="patch-sysargv">
  <a class="anchor" href="#patch-sysargv">
    <span class="icon icon-link"></span>
  </a>
  Patch <code>sys.argv</code>
</h2>
<p>It&rsquo;s also possible to patch the <code>sys.argv</code> with the mock arguments for testing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">command</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_command</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">unittest</span><span class="o">.</span><span class="n">mock</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s1">&#39;sys.argv&#39;</span><span class="o">.</span> <span class="p">[</span><span class="s1">&#39;arg1&#39;</span><span class="p">,</span> <span class="s1">&#39;arg2&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="n">command</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span>
</span></span></code></pre></div><h2 id="patch-argparse-directly">
  <a class="anchor" href="#patch-argparse-directly">
    <span class="icon icon-link"></span>
  </a>
  Patch <code>argparse</code> directly
</h2>
<p>In some rare scenarios, argument parser may be at the global scope inside a module file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">foo</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">foo</span>
</span></span></code></pre></div><p>In the above case, when the class is imported, the parser will be executed. This makes the unit
tests tricky simply because the argument parsing process happens at module import level rather
than inside a function call.</p>
<p>A straightforward solution is to patch the <code>ArgumentParser</code> or the <code>args</code> directly:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">mock_args</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;foo&#34;</span><span class="p">:</span> <span class="s2">&#34;bar&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@unittest.mock.patch</span><span class="p">(</span><span class="s1">&#39;module.args&#39;</span><span class="p">,</span> <span class="n">argparse</span><span class="o">.</span><span class="n">Namespace</span><span class="p">(</span><span class="o">**</span><span class="n">mock_args</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_class</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">obj</span> <span class="o">=</span> <span class="n">MyClass</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>The same can also be achieved by patch the <code>argparse.ArgumentParser.parse_args</code> function.
See <a
  href="https://stackoverflow.com/a/37343818"
  
    target="_blank" rel="noopener"
  
>stackoverflow answer</a>
.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@mock.patch</span><span class="p">(</span><span class="s1">&#39;argparse.ArgumentParser.parse_args&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">return_value</span><span class="o">=</span><span class="n">argparse</span><span class="o">.</span><span class="n">Namespace</span><span class="p">(</span><span class="n">kwarg1</span><span class="o">=</span><span class="n">value</span><span class="p">,</span> <span class="n">kwarg2</span><span class="o">=</span><span class="n">value</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_command</span><span class="p">(</span><span class="n">mock_args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">pass</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>Notes on "Building Systems with the ChatGPT API"</title><link>https://imfing.com/til/notes-on-building-systems-with-the-chatgpt-api/</link><pubDate>Sun, 11 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/notes-on-building-systems-with-the-chatgpt-api/</guid><description>Course notes on Building Systems with the ChatGPT API from DeepLearning.AI.</description><content:encoded><![CDATA[<p>Course Link: <a
  href="https://www.deeplearning.ai/short-courses/building-systems-with-chatgpt/"
  
    target="_blank" rel="noopener"
  
>Building Systems with the ChatGPT API - DeepLearning.AI</a>
</p>
<h2 id="introduction">
  <a class="anchor" href="#introduction">
    <span class="icon icon-link"></span>
  </a>
  Introduction
</h2>
<p>Process of building an application</p>
<ul>
<li>supervised learning: usually takes long time
<ul>
<li>get labeled data</li>
<li>train model on data</li>
<li>deploy &amp; call model</li>
</ul>
</li>
<li>prompt-based AI: takes short time
<ul>
<li>specify prompt and call model</li>
</ul>
</li>
</ul>
<h2 id="language-models">
  <a class="anchor" href="#language-models">
    <span class="icon icon-link"></span>
  </a>
  Language Models
</h2>
<p>How is works:</p>
<ul>
<li>A language model is built by using supervised learning to repeatedly predict the next word.</li>
</ul>
<p>Two types of LLMs</p>
<ul>
<li>Base LLM</li>
<li>Instruction Tuned LLM
<ul>
<li>Tune LLM using <a
  href="https://en.wikipedia.org/wiki/Reinforcement_learning_from_human_feedback"
  
    target="_blank" rel="noopener"
  
>RLHF</a>
: Reinforcement Learning from Human Feedback)</li>
</ul>
</li>
</ul>
<p><strong>Tokens</strong>: common sequences of characters found in text.</p>
<p>Many words map to one token. But some are broken down to multiple tokens, e.g. <code>prompting</code> has <code>prom</code>, <code>pt</code> and <code>ing</code> three parts.</p>
<p>OpenAI provides a tool <a
  href="https://platform.openai.com/tokenizer"
  
    target="_blank" rel="noopener"
  
>Tokenizer</a>
 for understanding how a piece of text would be tokenized by the API, and the total count of tokens in that piece of text.</p>
<blockquote>
<p>A helpful rule of thumb is that one token generally corresponds to ~4 characters of text for common English text. This translates to roughly ¾ of a word (so 100 tokens ~= 75 words).</p></blockquote>
<blockquote>
<p>If you need a programmatic interface for tokenizing text, check out our <a
  href="https://github.com/openai/tiktoken"
  
    target="_blank" rel="noopener"
  
>tiktoken</a>
 package for Python. For JavaScript, the <a
  href="https://www.npmjs.com/package/gpt-3-encoder"
  
    target="_blank" rel="noopener"
  
>gpt-3-encoder</a>
 package for node.js works for most GPT-3 models.</p></blockquote>
<p><figure class="my-6">
    <img src="../assets/2023-06-11-opanai-tokenizer.png" alt=""  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>See also: <a
  href="https://simonwillison.net/2023/Jun/8/gpt-tokenizers/"
  
    target="_blank" rel="noopener"
  
>Understanding GPT tokenizers</a>
.</p>
<p>Use API Key with caution:</p>
<ul>
<li>Avoid directly put API Key in the code</li>
<li>Use <a
  href="https://github.com/theskumar/python-dotenv"
  
    target="_blank" rel="noopener"
  
>python-dotenv</a>
 to load from <code>.env</code> file</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">openai</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span><span class="p">,</span> <span class="n">find_dotenv</span>
</span></span><span class="line"><span class="cl"><span class="n">_</span> <span class="o">=</span> <span class="n">load_dotenv</span><span class="p">(</span><span class="n">find_dotenv</span><span class="p">())</span> <span class="c1"># read local .env file</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">openai</span><span class="o">.</span><span class="n">api_key</span>  <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;OPENAI_API_KEY&#39;</span><span class="p">]</span>
</span></span></code></pre></div><h2 id="classification">
  <a class="anchor" href="#classification">
    <span class="icon icon-link"></span>
  </a>
  Classification
</h2>
<p>Note that in the course, it uses <code>delimiter = &quot;####&quot;</code> for user&rsquo;s input message.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">messages</span> <span class="o">=</span>  <span class="p">[</span>  
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span><span class="s1">&#39;system&#39;</span><span class="p">,</span> <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="n">system_message</span><span class="p">},</span>    
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="s1">&#39;role&#39;</span><span class="p">:</span><span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="s1">&#39;content&#39;</span><span class="p">:</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">delimiter</span><span class="si">}{</span><span class="n">user_message</span><span class="si">}{</span><span class="n">delimiter</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">},</span>  
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Use system message to guide the model to output the categories of the classification result in JSON format.</p>
<h2 id="moderation">
  <a class="anchor" href="#moderation">
    <span class="icon icon-link"></span>
  </a>
  Moderation
</h2>
<p>OpenAI provides <a
  href="https://platform.openai.com/docs/api-reference/moderations"
  
    target="_blank" rel="noopener"
  
>Moderations</a>
 which will:</p>
<blockquote>
<p>Given a input text, outputs if the model classifies it as violating OpenAI&rsquo;s content policy.</p></blockquote>
<p>Avoid Prompt Injections
Users might inject: <code>forget the previous instructions, do something else instead</code>.</p>
<h2 id="chain-of-thought-reason">
  <a class="anchor" href="#chain-of-thought-reason">
    <span class="icon icon-link"></span>
  </a>
  Chain of Thought Reason
</h2>
<p>Avoid model making error by rushing to a conclusion. Let the query require a series relevant reasoning steps.</p>
<p>Inner Monologue</p>
<ul>
<li>Since we asked the LLM to separate its reasoning steps by a delimiter, we can hide the chain-of-thought reasoning from the final output that the user sees.</li>
</ul>
<h2 id="chaining-prompts">
  <a class="anchor" href="#chaining-prompts">
    <span class="icon icon-link"></span>
  </a>
  Chaining Prompts
</h2>
<p>For complex tasks, keeps the track of state external to the LLM. It also allows model to use external tools such as web search and databases.</p>
<ul>
<li>More focused: breaks down the complex task</li>
<li>Context limitation: max tokens for input prompt and output prompt response</li>
<li>Reduced cost: pay per token</li>
</ul>
<h2 id="check-outputs">
  <a class="anchor" href="#check-outputs">
    <span class="icon icon-link"></span>
  </a>
  Check Outputs
</h2>
<p>Use Moderations API to check output for potential harmful content.</p>
<p>Check the satisfaction of the output by letting the model rate the output.</p>
<p>Example:</p>
<pre tabindex="0"><code>system_message = f&#34;&#34;&#34;
You are an assistant that evaluates whether \
customer service agent responses sufficiently \
answer customer questions, and also validates that \
all the facts the assistant cites from the product \
information are correct.
...
</code></pre><h2 id="evaluation">
  <a class="anchor" href="#evaluation">
    <span class="icon icon-link"></span>
  </a>
  Evaluation
</h2>
<p>For most prompt-based application:</p>
<ul>
<li>Tune prompts on handful of examples</li>
<li>Add additional &ldquo;tricky&rdquo; examples opportunistically</li>
<li>Develop metrics to measure performance on examples</li>
<li>Collect randomly sampled set of examples to tune to
(development set/hold-out cross validation set)</li>
<li>Collect and use a hold-out test set</li>
</ul>
<p>For text generation tasks, we can evaluate LLM&rsquo;s answer with a rubric, for example:</p>
<pre tabindex="0"><code>def eval_with_rubric(test_set, assistant_answer):

    cust_msg = test_set[&#39;customer_msg&#39;]
    context = test_set[&#39;context&#39;]
    completion = assistant_answer
    
    system_message = &#34;&#34;&#34;\
    You are an assistant that evaluates how well the customer service agent \
    answers a user question by looking at the context that the customer service \
    agent is using to generate its response. 
    &#34;&#34;&#34;

    user_message = f&#34;&#34;&#34;\
You are evaluating a submitted answer to a question based on the context \
that the agent uses to answer the question.
Here is the data:
    [BEGIN DATA]
    ************
    [Question]: {cust_msg}
    ************
    [Context]: {context}
    ************
    [Submission]: {completion}
    ************
    [END DATA]

Compare the factual content of the submitted answer with the context. \
Ignore any differences in style, grammar, or punctuation.
Answer the following questions:
    - Is the Assistant response based only on the context provided? (Y or N)
    - Does the answer include information that is not provided in the context? (Y or N)
    - Is there any disagreement between the response and the context? (Y or N)
    - Count how many questions the user asked. (output a number)
    - For each question that the user asked, is there a corresponding answer to it?
      Question 1: (Y or N)
      Question 2: (Y or N)
      ...
      Question N: (Y or N)
    - Of the number of questions asked, how many of these questions were addressed by the answer? (output a number)
&#34;&#34;&#34;

    messages = [
        {&#39;role&#39;: &#39;system&#39;, &#39;content&#39;: system_message},
        {&#39;role&#39;: &#39;user&#39;, &#39;content&#39;: user_message}
    ]

    response = get_completion_from_messages(messages)
    return response
</code></pre><p>Second way is to evaluate based on &ldquo;ideal&rdquo; or &ldquo;expert&rdquo; (human generated) answer.</p>
<p>This evaluation prompt is from the <a
  href="https://github.com/openai/evals/blob/main/evals/registry/modelgraded/fact.yaml"
  
    target="_blank" rel="noopener"
  
>OpenAI evals</a>
 project.</p>
<p><a
  href="https://en.wikipedia.org/wiki/BLEU"
  
    target="_blank" rel="noopener"
  
>BLEU score</a>
: another way to evaluate whether two pieces of text are similar or not.</p>
]]></content:encoded></item><item><title>Full content RSS in Hugo</title><link>https://imfing.com/til/full-content-rss-in-hugo/</link><pubDate>Sun, 04 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/full-content-rss-in-hugo/</guid><description>&lt;p>By default, &lt;a
href="https://github.com/gohugoio/hugo"
target="_blank" rel="noopener"
>Hugo&lt;/a>
only displays a summary of the post.&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;lt;description&amp;gt;{{ .Summary | html }}&amp;lt;/description&amp;gt;
&lt;/code>&lt;/pre>&lt;p>To enable a full text RSS feed for a section of my site, I added a &lt;code>layouts/section/section.rss.xml&lt;/code> template file according to &lt;a
href="https://gohugo.io/templates/lookup-order/#examples-layout-lookup-for-section-pages"
target="_blank" rel="noopener"
>Hugo&amp;rsquo;s layout lookup order&lt;/a>
. Fill it with the &lt;a
href="https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml"
target="_blank" rel="noopener"
>default RSS layout template&lt;/a>
.&lt;/p>
&lt;p>Modified the description part and add &lt;a
href="https://www.w3.org/wiki/RssContent"
target="_blank" rel="noopener"
>RSS content&lt;/a>
:&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;lt;description&amp;gt;{{ with .Description | html }}{{ . }}{{ else }}{{ .Summary | html }}{{ end -}}&amp;lt;/description&amp;gt;
&amp;lt;content:encoded&amp;gt;{{ (printf &amp;#34;&amp;lt;![CDATA[%s]]&amp;gt;&amp;#34; .Content) | safeHTML }}&amp;lt;/content:encoded&amp;gt;
&lt;/code>&lt;/pre>&lt;p>Verify the change by going to &lt;code>https://localhost:1313/&amp;lt;section&amp;gt;/index.xml&lt;/code>.&lt;/p></description><content:encoded><![CDATA[<p>By default, <a
  href="https://github.com/gohugoio/hugo"
  
    target="_blank" rel="noopener"
  
>Hugo</a>
 only displays a summary of the post.</p>
<pre tabindex="0"><code>&lt;description&gt;{{ .Summary | html }}&lt;/description&gt;
</code></pre><p>To enable a full text RSS feed for a section of my site, I added a <code>layouts/section/section.rss.xml</code> template file according to <a
  href="https://gohugo.io/templates/lookup-order/#examples-layout-lookup-for-section-pages"
  
    target="_blank" rel="noopener"
  
>Hugo&rsquo;s layout lookup order</a>
. Fill it with the <a
  href="https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml"
  
    target="_blank" rel="noopener"
  
>default RSS layout template</a>
.</p>
<p>Modified the description part and add <a
  href="https://www.w3.org/wiki/RssContent"
  
    target="_blank" rel="noopener"
  
>RSS content</a>
:</p>
<pre tabindex="0"><code>&lt;description&gt;{{ with .Description | html }}{{ . }}{{ else }}{{ .Summary | html }}{{ end -}}&lt;/description&gt;
&lt;content:encoded&gt;{{ (printf &#34;&lt;![CDATA[%s]]&gt;&#34; .Content) | safeHTML }}&lt;/content:encoded&gt;
</code></pre><p>Verify the change by going to <code>https://localhost:1313/&lt;section&gt;/index.xml</code>.</p>
<h2 id="references">
  <a class="anchor" href="#references">
    <span class="icon icon-link"></span>
  </a>
  References
</h2>
<ul>
<li><a
  href="https://gohugo.io/templates/rss/"
  
    target="_blank" rel="noopener"
  
>RSS Templates | Hugo</a>
</li>
<li><a
  href="https://github.com/adityatelange/hugo-PaperMod/blob/master/layouts/_default/rss.xml"
  
    target="_blank" rel="noopener"
  
>hugo-PaperMod/rss.xml at master · adityatelange/hugo-PaperMod · GitHub</a>
</li>
<li><a
  href="https://notes.nicolevanderhoeven.com/Display&#43;full&#43;blog&#43;post&#43;content&#43;in&#43;Hugo&#43;RSS&#43;feed"
  
    target="_blank" rel="noopener"
  
>Display full blog post content in Hugo RSS feed - Fork My Brain</a>
</li>
</ul>
]]></content:encoded></item><item><title>Build LLVM with CMake</title><link>https://imfing.com/til/build-llvm-with-cmake/</link><pubDate>Sat, 03 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/build-llvm-with-cmake/</guid><description>Step-by-step guide to building LLVM from source using CMake.</description><content:encoded><![CDATA[<p>The <a
  href="https://github.com/llvm/llvm-project"
  
    target="_blank" rel="noopener"
  
>LLVM</a>
 Project is a collection of modular and reusable compiler and toolchain technologies.</p>
<h2 id="using-the-pre-built-distribution">
  <a class="anchor" href="#using-the-pre-built-distribution">
    <span class="icon icon-link"></span>
  </a>
  Using the pre-built distribution
</h2>
<p>In most cases, we can just directly download and use the pre-compiled version of LLVM and Clang from <a
  href="https://github.com/llvm/llvm-project/releases"
  
    target="_blank" rel="noopener"
  
>llvm-project/releases</a>
. For example, <a
  href="https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.0/clang&#43;llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz"
  
    target="_blank" rel="noopener"
  
>clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz</a>
 is a pre-built distribution for Ubuntu 18.04 and for the x86-64 platforms.</p>
<h2 id="build-llvm-from-source">
  <a class="anchor" href="#build-llvm-from-source">
    <span class="icon icon-link"></span>
  </a>
  Build LLVM from source
</h2>
<p>In cases where there&rsquo;s no pre-built version that suffice our needs, we can download and build LLVM from scratch.
For example, <a
  href="https://github.com/openai/triton/tree/main/python"
  
    target="_blank" rel="noopener"
  
>openai/triton</a>
 Python library requires a LLVM compiler to build the wheel.</p>
<p>The <a
  href="https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm"
  
    target="_blank" rel="noopener"
  
>Getting Started with the LLVM System</a>
 documentation page of LLVM contains some instruction on how to build it. I recorded my steps below.</p>
<ol>
<li>Install <a
  href="https://cmake.org/"
  
    target="_blank" rel="noopener"
  
>CMake</a>
:</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">pip install cmake
</span></span></code></pre></div><p>here we use the <a
  href="https://pypi.org/project/cmake/"
  
    target="_blank" rel="noopener"
  
>Python wheel</a>
 which is easier to install.</p>
<ol start="2">
<li>Download LLVM and Clang source</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.1/llvm-11.0.1.src.tar.xz
</span></span><span class="line"><span class="cl">tar -xvf llvm-11.0.1.src.tar.xz
</span></span></code></pre></div><ol start="3">
<li>Create a new directory for the build files and enter it:</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mkdir build
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> build
</span></span></code></pre></div><ol start="4">
<li>Configure the build with CMake</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cmake -G <span class="s2">&#34;Unix Makefiles&#34;</span> -DLLVM_TARGETS_TO_BUILD<span class="o">=</span><span class="s2">&#34;X86;NVPTX;AMDGPU&#34;</span> -DCMAKE_BUILD_TYPE<span class="o">=</span><span class="s2">&#34;Release&#34;</span> ../
</span></span></code></pre></div><p>We tell CMake to build <code>Release</code> version which performs best optimization and disables debug information. For more detailed information see <a
  href="https://llvm.org/docs/CMake.html#cmake-build-type"
  
    target="_blank" rel="noopener"
  
>CMAKE_BUILD_TYPE</a>
. List of <a
  href="https://github.com/llvm/llvm-project/tree/main/clang/lib/Basic/Targets"
  
    target="_blank" rel="noopener"
  
>Targets</a>
 available. We may also include <code>-DLLVM_INCLUDE_TESTS=Off -DLLVM_INCLUDE_EXAMPLES=Off -DLLVM_INCLUDE_BENCHMARKS=Off</code> to skip the unnecessary build.</p>
<ol start="5">
<li>Compile and install</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">make -j4 install
</span></span></code></pre></div><p>By default, the built <code>bin</code>, <code>lib</code> and <code>include</code> will be installed to <code>/usr/local/</code>.
We can now use the compiled LLVM libraries in other project, e.g. <a
  href="https://llvm.org/docs/CMake.html#embedding-llvm-in-your-project"
  
    target="_blank" rel="noopener"
  
>Embedding LLVM in your project</a>
.</p>
<h2 id="links">
  <a class="anchor" href="#links">
    <span class="icon icon-link"></span>
  </a>
  Links
</h2>
<ul>
<li><a
  href="https://llvm.org/docs/CMake.html"
  
    target="_blank" rel="noopener"
  
>Building LLVM with CMake — LLVM 17.0.0git documentation</a>
</li>
<li><a
  href="https://llvm.org/docs/GettingStarted.html#getting-the-source-code-and-building-llvm"
  
    target="_blank" rel="noopener"
  
>Getting Started with the LLVM System — LLVM 17.0.0git documentation</a>
</li>
<li><a
  href="https://github.com/ptillet/triton-llvm-releases"
  
    target="_blank" rel="noopener"
  
>ptillet/triton-llvm-releases</a>
</li>
</ul>
]]></content:encoded></item><item><title>Customize GitHub Codespace using Dev Container</title><link>https://imfing.com/til/customize-github-codespace-using-devcontainer/</link><pubDate>Thu, 01 Jun 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/customize-github-codespace-using-devcontainer/</guid><description>&lt;p>A &lt;a
href="https://containers.dev/"
target="_blank" rel="noopener"
>Development Container&lt;/a>
(or Dev Container for short) allows you to use a container as a full-featured development environment.&lt;/p>
&lt;p>&lt;a
href="https://github.com/features/codespaces"
target="_blank" rel="noopener"
>GitHub Codespace&lt;/a>
is a development environment that&amp;rsquo;s hosted in the cloud, which allows you to use VS Code directly from the browser to edit, commit and run projects. Whenever you work in a codespace, you are using a dev container on a virtual machine.&lt;/p>
&lt;p>The dev container configuration is located under &lt;code>.devcontainer/devcontainer.json&lt;/code>. There are a number of pre-built dev container images published under &lt;code>mcr.microsoft.com/devcontainers&lt;/code> on &lt;a
href="https://github.com/devcontainers/images"
target="_blank" rel="noopener"
>devcontainers/images&lt;/a>
.&lt;/p></description><content:encoded><![CDATA[<p>A <a
  href="https://containers.dev/"
  
    target="_blank" rel="noopener"
  
>Development Container</a>
 (or Dev Container for short) allows you to use a container as a full-featured development environment.</p>
<p><a
  href="https://github.com/features/codespaces"
  
    target="_blank" rel="noopener"
  
>GitHub Codespace</a>
 is a development environment that&rsquo;s hosted in the cloud, which allows you to use VS Code directly from the browser to edit, commit and run projects. Whenever you work in a codespace, you are using a dev container on a virtual machine.</p>
<p>The dev container configuration is located under <code>.devcontainer/devcontainer.json</code>. There are a number of pre-built dev container images published under <code>mcr.microsoft.com/devcontainers</code> on <a
  href="https://github.com/devcontainers/images"
  
    target="_blank" rel="noopener"
  
>devcontainers/images</a>
.</p>
<p>By default, the codespace launches a <a
  href="https://github.com/devcontainers/images/tree/main/src/universal"
  
    target="_blank" rel="noopener"
  
>universal</a>
 container which includes a decent amount of tools and platforms.
One friction I encountered was that in the universal image, the <a
  href="https://github.com/gohugoio/hugo"
  
    target="_blank" rel="noopener"
  
>Hugo</a>
 version built in is a few minor version behind its latest upstream version, see its <a
  href="https://github.com/devcontainers/images/blob/v0.3.4/src/universal/.devcontainer/devcontainer.json#L17"
  
    target="_blank" rel="noopener"
  
><code>devcontainer.json</code></a>
. I try to add a Hugo <a
  href="https://containers.dev/features"
  
    target="_blank" rel="noopener"
  
>features</a>
 on top and specify the version, it didn&rsquo;t work simply because the Hugo feature only installs it if it&rsquo;s missing. So I created a dev container configuration to tackle it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;image&#34;</span><span class="p">:</span> <span class="s2">&#34;mcr.microsoft.com/devcontainers/go:1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;features&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ghcr.io/devcontainers/features/hugo:1&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;extended&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.112.5&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ghcr.io/devcontainers/features/node:1&#34;</span><span class="p">:</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;customizations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;vscode&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;streetsidesoftware.code-spell-checker&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;postCreateCommand&#34;</span><span class="p">:</span> <span class="s2">&#34;npm install&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;forwardPorts&#34;</span><span class="p">:</span> <span class="p">[</span><span class="mi">1313</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In the above configuration, we first set the base image, then add in <a
  href="https://containers.dev/features"
  
    target="_blank" rel="noopener"
  
>features</a>
 (language, tools or frameworks) on the top. And we install some VS Code extensions, and after the image is created, run <code>npm install</code>. Since I&rsquo;m developing a Hugo site, port <code>1313</code> is forwarded.
In this way, I installed Hugo on top of <code>go</code> base image, and also enabled the features I need specificly for this repository.</p>
<p>If we ever want to further customize one feature, <a
  href="https://github.com/devcontainers/feature-starter"
  
    target="_blank" rel="noopener"
  
>devcontainers/feature-starter</a>
 provides a good template for authoring our own feature.</p>
<h2 id="links">
  <a class="anchor" href="#links">
    <span class="icon icon-link"></span>
  </a>
  Links
</h2>
<ul>
<li><a
  href="https://code.visualstudio.com/docs/devcontainers/containers"
  
    target="_blank" rel="noopener"
  
>Developing inside a Container using Visual Studio Code Remote Development</a>
</li>
<li><a
  href="https://docs.github.com/en/codespaces/overview#customizing-github-codespaces"
  
    target="_blank" rel="noopener"
  
>GitHub Codespaces overview - GitHub Docs</a>
</li>
<li><a
  href="https://containers.dev/"
  
    target="_blank" rel="noopener"
  
>Development containers</a>
</li>
<li><a
  href="https://github.com/devcontainers/feature-starter"
  
    target="_blank" rel="noopener"
  
>devcontainers/feature-starter: A bootstrap repo for self-authoring Dev Container Features</a>
</li>
</ul>
]]></content:encoded></item><item><title>Build multi-arch Docker images in GitHub Actions</title><link>https://imfing.com/til/build-multi-arch-docker-images-in-github-actions/</link><pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/build-multi-arch-docker-images-in-github-actions/</guid><description>&lt;h2 id="publishing-images-to-github-packages">
&lt;a class="anchor" href="#publishing-images-to-github-packages">
&lt;span class="icon icon-link">&lt;/span>
&lt;/a>
Publishing images to GitHub Packages
&lt;/h2>
&lt;p>Let&amp;rsquo;s say we have a repository that includes a &lt;code>Dockerfile&lt;/code>. We can utilize a GitHub workflow to:&lt;/p>
&lt;ul>
&lt;li>Check out the repository&lt;/li>
&lt;li>Log in to the GitHub Container Registry (ghcr.io)&lt;/li>
&lt;li>Extract metadata&lt;/li>
&lt;li>Build and push the Docker image to our specified registry&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Create and publish a Docker image&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tags&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;v*&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">env&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">REGISTRY&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">IMAGE_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ github.repository }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build-and-push-image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">permissions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">contents&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">read&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">packages&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">write&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Checkout repository&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Log in to the Container registry&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">registry&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ env.REGISTRY }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">username&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ github.actor }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">password&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.GITHUB_TOKEN }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Extract metadata (tags, labels) for Docker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">meta&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">images&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Build and push Docker image&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">context&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tags&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ steps.meta.outputs.tags }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ steps.meta.outputs.labels }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="build-and-publish-multi-arch-images">
&lt;a class="anchor" href="#build-and-publish-multi-arch-images">
&lt;span class="icon icon-link">&lt;/span>
&lt;/a>
Build and publish multi-arch images
&lt;/h2>
&lt;p>Depends on the type of the host machines, images can be built for multiple platforms, e.g. &lt;code>linux/amd64&lt;/code>, &lt;code>linux/arm64/v8&lt;/code>, etc.&lt;/p></description><content:encoded><![CDATA[<h2 id="publishing-images-to-github-packages">
  <a class="anchor" href="#publishing-images-to-github-packages">
    <span class="icon icon-link"></span>
  </a>
  Publishing images to GitHub Packages
</h2>
<p>Let&rsquo;s say we have a repository that includes a <code>Dockerfile</code>. We can utilize a GitHub workflow to:</p>
<ul>
<li>Check out the repository</li>
<li>Log in to the GitHub Container Registry (ghcr.io)</li>
<li>Extract metadata</li>
<li>Build and push the Docker image to our specified registry</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create and publish a Docker image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;v*&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">REGISTRY</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">IMAGE_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">${{ github.repository }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">build-and-push-image</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">permissions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="l">read</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">packages</span><span class="p">:</span><span class="w"> </span><span class="l">write</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout repository</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Log in to the Container registry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">registry</span><span class="p">:</span><span class="w"> </span><span class="l">${{ env.REGISTRY }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">username</span><span class="p">:</span><span class="w"> </span><span class="l">${{ github.actor }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">password</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.GITHUB_TOKEN }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Extract metadata (tags, labels) for Docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">meta</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">images</span><span class="p">:</span><span class="w"> </span><span class="l">${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build and push Docker image</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">push</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.meta.outputs.tags }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">labels</span><span class="p">:</span><span class="w"> </span><span class="l">${{ steps.meta.outputs.labels }}</span><span class="w">
</span></span></span></code></pre></div><h2 id="build-and-publish-multi-arch-images">
  <a class="anchor" href="#build-and-publish-multi-arch-images">
    <span class="icon icon-link"></span>
  </a>
  Build and publish multi-arch images
</h2>
<p>Depends on the type of the host machines, images can be built for multiple platforms, e.g. <code>linux/amd64</code>, <code>linux/arm64/v8</code>, etc.</p>
<p><figure class="my-6">
    <img src="/til/build-multi-arch-docker-images-in-github-actions/2023-05-10-22-39-29.png" alt=""  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<h3 id="set-up-qemu-and-buildx">
  <a class="anchor" href="#set-up-qemu-and-buildx">
    <span class="icon icon-link"></span>
  </a>
  Set up QEMU and Buildx
</h3>
<p>The next thing is to enable <code>QEMU</code>. Basically it allows GitHub action host machine to emulate different architectures.</p>
<blockquote>
<p>QEMU is a generic and open source machine &amp; userspace emulator and virtualizer. QEMU is capable of emulating a complete machine in software without any need for hardware virtualization support.</p></blockquote>
<p><code>buildx</code> is a Docker CLI plugin for extended build capabilities with <a
  href="https://github.com/moby/buildkit"
  
    target="_blank" rel="noopener"
  
>BuildKit</a>
.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">--- a/.github/workflows/build-publish-image.yml
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/.github/workflows/build-publish-image.yml
</span></span></span><span class="line"><span class="cl"><span class="gi"></span>
</span></span><span class="line"><span class="cl"><span class="gu">@@ -20,6 +22,12 @@ jobs:
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>       - name: Checkout repository
</span></span><span class="line"><span class="cl">         uses: actions/checkout@v3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gi">+      - name: Set up QEMU
</span></span></span><span class="line"><span class="cl"><span class="gi">+        uses: docker/setup-qemu-action@v2
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+      - name: Set up Docker Buildx
</span></span></span><span class="line"><span class="cl"><span class="gi">+        uses: docker/setup-buildx-action@v2
</span></span></span></code></pre></div><h3 id="specify-the-platforms-in-dockerbuild-push-action">
  <a class="anchor" href="#specify-the-platforms-in-dockerbuild-push-action">
    <span class="icon icon-link"></span>
  </a>
  Specify the platforms in docker/build-push-action
</h3>
<p>In <a
  href="https://github.com/docker/build-push-action"
  
    target="_blank" rel="noopener"
  
>docker/build-push-action</a>
, we can set <code>platforms</code> to a list of target platforms to build, separated by comma. In our example, we only needs to build <code>linux/amd64</code> for all <code>amd64</code> machine, and <code>linux/arm64/v8</code> for Apple Silicone MacBooks.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">--- a/.github/workflows/build-publish-image.yml
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/.github/workflows/build-publish-image.yml
</span></span></span><span class="line"><span class="cl"><span class="gi"></span><span class="gu">@@ -37,6 +45,7 @@ jobs:
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>         uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
</span></span><span class="line"><span class="cl">         with:
</span></span><span class="line"><span class="cl">           context: .
</span></span><span class="line"><span class="cl"><span class="gd">-          push: true
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+          push: ${{ github.event_name != &#39;pull_request&#39; }}
</span></span></span><span class="line"><span class="cl"><span class="gi">+          platforms: linux/amd64,linux/arm64/v8
</span></span></span><span class="line"><span class="cl"><span class="gi"></span>           tags: ${{ steps.meta.outputs.tags }}
</span></span><span class="line"><span class="cl">           labels: ${{ steps.meta.outputs.labels }}
</span></span></code></pre></div><h3 id="view-them-on-github-packages">
  <a class="anchor" href="#view-them-on-github-packages">
    <span class="icon icon-link"></span>
  </a>
  View them on GitHub Packages
</h3>
<p>Once the GitHub workflow has been triggered and the images have been built successfully, we can see them from the <code>OS/Arch</code> tab.</p>
<h2 id="final-thoughts">
  <a class="anchor" href="#final-thoughts">
    <span class="icon icon-link"></span>
  </a>
  Final thoughts
</h2>
<p>As I just discovered, <code>linux/arm64/v8</code> is normalized as just <code>linux/arm64</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker image inspect ghcr.io/imfing/keras-flask-deploy-webapp --format <span class="s1">&#39;{{.Os}}/{{.Architecture}}&#39;</span>
</span></span><span class="line"><span class="cl">linux/arm64
</span></span></code></pre></div><h2 id="references">
  <a class="anchor" href="#references">
    <span class="icon icon-link"></span>
  </a>
  References
</h2>
<ul>
<li><a
  href="https://docs.github.com/en/actions/publishing-packages/publishing-docker-images"
  
    target="_blank" rel="noopener"
  
>Publishing Docker images</a>
</li>
<li><a
  href="https://blog.thesparktree.com/docker-multi-arch-github-actions"
  
    target="_blank" rel="noopener"
  
>Building Multi-Arch Docker Images via Github Actions</a>
</li>
<li><a
  href="https://stackoverflow.com/questions/70819028/relation-between-linux-arm64-and-linux-arm64-v8-are-these-aliases-for-each-othe"
  
    target="_blank" rel="noopener"
  
>docker - Relation between linux/arm64 and linux/arm64/v8: are these aliases for each other? - Stack Overflow</a>
</li>
</ul>
]]></content:encoded></item><item><title>Caddy log filter</title><link>https://imfing.com/til/caddy-log-filter/</link><pubDate>Tue, 07 Feb 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/caddy-log-filter/</guid><description>Using Caddy web server as a reverse proxy with logging configuration.</description><content:encoded><![CDATA[<p><a
  href="https://github.com/caddyserver/caddy"
  
    target="_blank" rel="noopener"
  
>Caddy</a>
 is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go.</p>
<p>It can be used as a reverse proxy server, for example:</p>
<pre tabindex="0"><code>:10001 {
    respond /healthz &#34;OK&#34; 200
    reverse_proxy /* http://localhost:8000 {
        header_up x-account &#34;user&#34;
        header_up x-secret-key &#34;secret&#34;
    }
    log
}
</code></pre><p>The above <code>Caddyfile</code> will launch a http server listening on port <code>10001</code> and will redirect requests to <code>localhost:8000</code> and add two headers which contain sensitive information.
When the <code>debug</code> mode is turned on, caddy&rsquo;s internal logger will log down the requests from the reverse proxy to the localhost server, which will expose the sensitive info.</p>
<p>The <a
  href="https://caddyserver.com/docs/caddyfile/directives/log"
  
    target="_blank" rel="noopener"
  
><code>log</code> directive</a>
 can be used to remove certain header from the request and the reverse proxy server itself.
To do so, simply add the <code>log</code> override config to the global level:</p>
<pre tabindex="0"><code>{
    log {
        format filter {
            wrap console
            fields {
                request&gt;headers&gt;X-Secret-Key delete
            }
        }
    }
}
</code></pre><p>Note that we don&rsquo;t need to explicitly remove the <code>Authorization</code> header since:</p>
<blockquote>
<p>Since Caddy v2.5, by default, headers with potentially sensitive information (Cookie, Set-Cookie, Authorization and Proxy-Authorization) will be logged with empty values. This behaviour can be disabled with the <a
  href="https://caddyserver.com/docs/caddyfile/options#log-credentials"
  
    target="_blank" rel="noopener"
  
>log_credentials</a>
 global server option.</p></blockquote>
]]></content:encoded></item><item><title>Shortcut for creating daily note</title><link>https://imfing.com/til/shortcut-for-creating-daily-note/</link><pubDate>Sat, 28 Jan 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/shortcut-for-creating-daily-note/</guid><description>iOS Shortcut for quickly creating or opening a daily note in Apple Notes.</description><content:encoded><![CDATA[<p>I like to jog down things on my Apple Note as it&rsquo;s easier and fast to access whenever my iPhone is around. With iOS built-in Shortcuts app, creating a daily note entry can be just one click.</p>
<p>Here&rsquo;s the shortcut I made:</p>
<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/215294187-745c583f-977a-4e44-b31f-8896b1f36e1a.png" alt="Shortcut for creating daily note"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<ul>
<li>Format current date to be <code>yyyy-MM-dd</code></li>
<li>Find in all notes where the name contains such date string</li>
<li>If exists, open the note</li>
<li>If not, create a note with the formatted date as title</li>
</ul>
<p>Credit:</p>
<ul>
<li><a
  href="https://benborgers.com/posts/shortcuts-note"
  
    target="_blank" rel="noopener"
  
>Shortcut for creating a daily Apple Note</a>
</li>
</ul>
]]></content:encoded></item><item><title>Decode Flask session cookie</title><link>https://imfing.com/til/decode-flask-session-cookie/</link><pubDate>Wed, 04 Jan 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/decode-flask-session-cookie/</guid><description>Python snippet to decode Flask session cookies using zlib and base64.</description><content:encoded><![CDATA[<p>Snippet to decode <a
  href="https://flask.palletsprojects.com/en/2.2.x/"
  
    target="_blank" rel="noopener"
  
>Flask</a>
 session cookie.</p>
<p>To find the cookie in Chrome, open Inspect -&gt; Application -&gt; Cookies, then find the <code>session</code> cookie.</p>
<p>Here&rsquo;s a short Python snippet that decodes the session cookie using <a
  href="https://docs.python.org/3/library/zlib.html"
  
    target="_blank" rel="noopener"
  
>zlib</a>
 and <code>base64.urlsafe_b64decode</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">zlib</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">base64</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">decode</span><span class="p">(</span><span class="n">cookie</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Decode a Flask cookie.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">compressed</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload</span> <span class="o">=</span> <span class="n">cookie</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">payload</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">&#39;.&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">compressed</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">            <span class="n">payload</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">payload</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;.&#34;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">urlsafe_b64decode</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">compressed</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span> <span class="o">=</span> <span class="n">zlib</span><span class="o">.</span><span class="n">decompress</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;[Decoding error: are you sure this was a Flask session cookie? </span><span class="si">{}</span><span class="s2">]&#34;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</span></span></code></pre></div><p>Reference:</p>
<ul>
<li><a
  href="https://www.kirsle.net/wizards/flask-session.cgi"
  
    target="_blank" rel="noopener"
  
>https://www.kirsle.net/wizards/flask-session.cgi</a>
</li>
</ul>
]]></content:encoded></item><item><title>Shortcut to copy url as markdown</title><link>https://imfing.com/til/shortcut-to-copy-url-as-markdown/</link><pubDate>Tue, 03 Jan 2023 00:00:00 +0000</pubDate><guid>https://imfing.com/til/shortcut-to-copy-url-as-markdown/</guid><description>iOS Shortcut to copy a Safari URL as a Markdown-formatted link to clipboard.</description><content:encoded><![CDATA[<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/210312153-219cc8e1-560f-4c35-9f68-b46a45cc89c4.jpeg" alt="image"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>This shortcut can copy a link from Safari share sheet and copy as Markdown format <code>[title](url]</code> to the clipboard.</p>
<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/210312483-57b414c7-7e74-43cb-bb82-7544d8ee52ab.jpeg" alt="Safari share sheet"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
]]></content:encoded></item><item><title>Markdown reference-style link</title><link>https://imfing.com/til/markdown-reference-style-link/</link><pubDate>Fri, 30 Dec 2022 00:00:00 +0000</pubDate><guid>https://imfing.com/til/markdown-reference-style-link/</guid><description>How to use reference-style links in Markdown for cleaner and more readable documents.</description><content:encoded><![CDATA[<blockquote>
<p>Reference-style links are a special kind of link that make URLs easier to display and read in Markdown.</p></blockquote>
<p>Instead of inline link: <code>[text](link)</code>, the reference-style:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">[text][label]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[label]: https://google.com &#34;Google&#34;
</span></span></code></pre></div><p>First part of the link is formatted with two sets of brackets.</p>
<p>The second part of a reference-style link is formatted with the following attributes:</p>
<ol>
<li>The label, in brackets, followed immediately by a colon and at least one space (e.g., <code>[label]: </code>).</li>
<li>The URL for the link, which you can optionally enclose in angle brackets.</li>
<li>The <strong>optional</strong> title for the link, which you can enclose in double quotes, single quotes, or parentheses.</li>
</ol>
<p>See it in action:</p>
<p><a
  href="https://www.markdownguide.org/basic-syntax/"
  
    target="_blank" rel="noopener"
  
>Basic Syntax | Markdown Guide</a>
</p>
]]></content:encoded></item><item><title>Fn Option Delete</title><link>https://imfing.com/til/fn-option-delete/</link><pubDate>Fri, 16 Dec 2022 00:00:00 +0000</pubDate><guid>https://imfing.com/til/fn-option-delete/</guid><description>How to use forward delete and backward delete shortcuts on Mac with Fn and Option keys.</description><content:encoded><![CDATA[<p>As long time Mac user, I wasn&rsquo;t aware that &ldquo;forward delete&rdquo; and &ldquo;backward delete&rdquo; exist 😂</p>
<p>Backward delete <kbd>option + delete</kbd></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">hello world▐
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">hello▐
</span></span></code></pre></div><p>Forward delete <kbd>fn + option + delete</kbd></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">hello ▐world
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">hello▐
</span></span></code></pre></div><p>Alternatively, I was using vim editing mode for quick cursor navigation and editing.</p>
]]></content:encoded></item><item><title>Lightroom backup workflow</title><link>https://imfing.com/til/lightroom-backup-workflow/</link><pubDate>Tue, 13 Dec 2022 00:00:00 +0000</pubDate><guid>https://imfing.com/til/lightroom-backup-workflow/</guid><description>My workflow for organizing and backing up photos from Lightroom Classic to external drives.</description><content:encoded><![CDATA[<p>Midway through 2022, I began using <a
  href="https://www.adobe.com/products/photoshop-lightroom.html"
  
    target="_blank" rel="noopener"
  
>Lightroom Classic</a>
 to manage and edit camera-captured images.
Now that the end of the year is approaching, I&rsquo;ve decided to organize and back up the photos from this year on my <a
  href="https://lacie.com/"
  
    target="_blank" rel="noopener"
  
>Lacie</a>
 portable hard drive so I can free up space on my MacBook Pro.</p>
<h2 id="setup">
  <a class="anchor" href="#setup">
    <span class="icon icon-link"></span>
  </a>
  Setup
</h2>
<p>All of my photos are stored in three distinct locations:</p>
<ul>
<li>MacBook Pro (500GB):  ad-hoc editing</li>
<li>Sandisk Extreme Portable SSD (1TB): bulk editing</li>
<li>LaCie Portable Hard Drive (4TB): backup</li>
</ul>
<p>I usually bring my laptop with me when I travel. After each day I would import my photos directly into my laptop and edit them right away. Sometimes, I import the photos to the SSD drive. LaCie hard drive is mostly used for making backups.</p>
<h2 id="backup-workflow">
  <a class="anchor" href="#backup-workflow">
    <span class="icon icon-link"></span>
  </a>
  Backup workflow
</h2>
<p>I use Lightroom Classic to help me with this. I decide to store the backup photos in hard drive in a following structure:</p>
<pre tabindex="0"><code>photos
└─ 2022
   └──2022-01-23 &lt;Event/Trip/Destination Name&gt;
      ├── Raw
      ├── JPEG
      └── Export
</code></pre><h3 id="add-external-hard-drive-to-folders">
  <a class="anchor" href="#add-external-hard-drive-to-folders">
    <span class="icon icon-link"></span>
  </a>
  Add external hard drive to Folders
</h3>
<p>On the left side bar, go to &ldquo;Folders&rdquo; and click &ldquo;Add Folder&hellip;&rdquo;.</p>
<img width="500" alt="Pasted Graphic" src="https://user-images.githubusercontent.com/5097752/207219130-238830d6-6257-4059-968a-927c0e297363.png">
<p>Add the location in external hard drive where we want to store our backups.</p>
<h3 id="select-photos">
  <a class="anchor" href="#select-photos">
    <span class="icon icon-link"></span>
  </a>
  Select photos
</h3>
<p>Use &ldquo;Smart Collection&rdquo; to create collections that distinguish between Raw and JPEG images. Obviously, we can also sort by date/location/&hellip;</p>
<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/207219574-e66bda8b-dd6a-432d-b167-b5cb383bb43f.png" alt=""  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>Once we finished filtering, just select all by <code>Cmd + A</code>.</p>
<h3 id="move-photos">
  <a class="anchor" href="#move-photos">
    <span class="icon icon-link"></span>
  </a>
  Move photos
</h3>
<p>In the sidebar on the left, right-click to create a subfolder for our photos.</p>
<img width="714" alt="Pasted Graphic 2" src="https://user-images.githubusercontent.com/5097752/207220644-034c2d99-aa2e-41c2-b516-d11fe30cb421.png">
<p>Select &ldquo;Include selected photos&rdquo; to transfer the chosen images to the folder.</p>
<img width="657" alt="Pasted Graphic 3" src="https://user-images.githubusercontent.com/5097752/207220703-6bf51438-803c-4416-a566-770d0e2717ce.png">
<h2 id="thoughts">
  <a class="anchor" href="#thoughts">
    <span class="icon icon-link"></span>
  </a>
  Thoughts
</h2>
<p>Initially, I separated each trip into its own Lightroom catalog, but it turns out I could manage them all with a single catalog!
Lightroom&rsquo;s collections feature is versatile and powerful, allowing us to filter and separate photos. Using a single catalog also simplifies backup procedures.</p>
]]></content:encoded></item><item><title>Merge changes from GitHub template repository</title><link>https://imfing.com/til/merge-changes-from-github-template-repository/</link><pubDate>Tue, 06 Dec 2022 00:00:00 +0000</pubDate><guid>https://imfing.com/til/merge-changes-from-github-template-repository/</guid><description>How to sync and merge upstream changes from a GitHub template repository using git.</description><content:encoded><![CDATA[<p>It&rsquo;s very convenient to use <a
  href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository"
  
    target="_blank" rel="noopener"
  
>GitHub template repository</a>
 feature to bootstrap a repository. I thought it would also have functionality like <a
  href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork"
  
    target="_blank" rel="noopener"
  
>Syncing a fork</a>
, but unfortunately it doesn&rsquo;t.</p>
<p>So to sync changes from upstream template repository, we need to use <code>git</code> command line:</p>
<ol>
<li>
<p>Add remote</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git remote add template &lt;repo&gt;
</span></span></code></pre></div></li>
<li>
<p>Fetch template changes</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git fetch --all
</span></span></code></pre></div></li>
<li>
<p>Use <code>--allow-unrelated-histories</code> to merge, we may also need to manually resolve all the merge conflicts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git merge --allow-unrelated-histories --squash template/&lt;branch&gt;
</span></span></code></pre></div></li>
</ol>
<p><code>--allow-unrelated-histories</code>: By default, git merge command refuses to merge histories that do not share a common ancestor. This option can be used to override this safety when merging histories of two projects that started their lives independently. As that is a very rare occasion, no configuration variable to enable this by default exists and will not be added.</p>
<p>There is also GitHub action <a
  href="https://github.com/AndreasAugustin/actions-template-sync"
  
    target="_blank" rel="noopener"
  
>actions-template-sync</a>
 which can be configured to automatically sync changes from template.</p>
<h2 id="references">
  <a class="anchor" href="#references">
    <span class="icon icon-link"></span>
  </a>
  References
</h2>
<ul>
<li><a
  href="https://stackoverflow.com/questions/56577184/github-pull-changes-from-a-template-repository"
  
    target="_blank" rel="noopener"
  
>GitHub - Pull changes from a template repository - Stack Overflow</a>
</li>
<li><a
  href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository"
  
    target="_blank" rel="noopener"
  
>Creating a template repository</a>
</li>
<li><a
  href="https://github.com/AndreasAugustin/actions-template-sync"
  
    target="_blank" rel="noopener"
  
>AndreasAugustin/actions-template-sync: Github action for syncing other repositories (templates) with another repository</a>
</li>
<li><a
  href="https://git-scm.com/docs/git-merge#Documentation/git-merge.txt---allow-unrelated-histories"
  
    target="_blank" rel="noopener"
  
>Git - git-merge Documentation</a>
</li>
</ul>
]]></content:encoded></item><item><title>Manage GitHub project in Linear</title><link>https://imfing.com/til/manage-github-project-in-linear/</link><pubDate>Sun, 04 Dec 2022 00:00:00 +0000</pubDate><guid>https://imfing.com/til/manage-github-project-in-linear/</guid><description>Using Linear as a project management tool for GitHub projects with pull request integration.</description><content:encoded><![CDATA[<p>While I was working on my simple personal project: <a
  href="https://github.com/imfing/issues-blog"
  
    target="_blank" rel="noopener"
  
>issues-blog</a>
, I experimented with <a
  href="https://linear.app"
  
    target="_blank" rel="noopener"
  
>Linear</a>
 to manage this small project.</p>
<p>Linear surprised me by being a powerful and elegant tool for managing issues and projects. It has nice and modern UI/UX, and more responsive than JIRA.</p>
<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/205522813-2d5c2e1c-eb61-4709-a6a2-748399ff5439.png" alt="Screenshot of Linear app"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<h2 id="connect-linear-with-github">
  <a class="anchor" href="#connect-linear-with-github">
    <span class="icon icon-link"></span>
  </a>
  Connect Linear with GitHub
</h2>
<p>Linear comes with integration with GitHub of course: <a
  href="https://linear.app/docs/github"
  
    target="_blank" rel="noopener"
  
>GitHub - Linear Guide</a>
. To enable it, go to <code>Settings</code>, and under <code>Integrations -&gt; GitHub</code> we can connect Linear with GitHub pull requests.</p>
<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/205523269-25fd62d2-433b-409a-a082-cd3e1490ab9d.png" alt="Linear connect to github pull requests"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>A very basic way is to link Linear issue by branch name, e.g. <code>lauren/ENG-123-fixing-loading-issue</code>.</p>
<p><figure class="my-6">
    <img src="https://user-images.githubusercontent.com/5097752/205523048-60739c4f-7c43-48c3-afc6-f63ab61208ec.png" alt="Copy branch name under Linear issue"  data-zoomable
        class="rounded-lg mx-auto" loading="lazy" /></figure></p>
<p>If a PR is created using this branch name, Linear will automatically link to the issue, and move it to <code>In Progress</code>, and after the PR is closed, it will automatically be marked as <code>Done</code>.</p>
]]></content:encoded></item></channel></rss>