<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>@chinsingh</title>
  <link href="https://chns.in/feed.xml" rel="self" />
  <link href="https://chns.in/" />
  <updated>2026-06-08T20:40:28Z</updated>
  <id>https://chns.in/</id>
  <author>
    <name>Chinmay Singh</name>
  </author>
  
  <entry>
    <title>When Not Using LLMs Is A Better Engineering Choice</title>
    <link href="https://chns.in/posts/2026-02-24-when-not-using-llms-is-a-better-engineering-choice/" />
    <id>https://chns.in/posts/2026-02-24-when-not-using-llms-is-a-better-engineering-choice/</id>
    <updated>2026-02-24T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>I’m currently working on an application that allows users to customize hundreds of product offerings in their CRM. One functionality was to edit images for those products. When I started to enhance it with AI features, one obvious enhancement which would save a ton of time for users was image generation. Since there are loads of products, customizing them one by one was a huge manual effort.</p>
<p>Two reasons why I couldn’t go for image generation via AI though:</p>
<ul>
<li>
<p>My organization’s internal AI gateway didn’t support multimodal output, and they had no plans to do it anytime soon.</p>
</li>
<li>
<p>Even if I did find a way, generating images for hundreds of products would take forever. Certainly it could be done in the background, but it would be costly and would negate the purpose it was trying to solve in the first place.</p>
</li>
</ul>
<h3>The Solution: You Don’t Need Image Generation</h3>
<p>What if instead of generating, we fetch images online and rank them?</p>
<p>I started looking for image search APIs and found <a href="https://api.openverse.org/v1/">Openverse</a>. The ranking part could be done by an LLM, which is much more efficient than generation, both in terms of cost and time. The ranking could also be done by humans, which is why I added this functionality to the manual mode of my application as well.</p>
<p>The overall pipeline still took around 2–3 minutes (sometimes up to 5). I worked around this limitation by using push notifications when the ranking was done. But it still wasn’t great user experience. And frankly, only slightly better than doing it manually. On top of that, Openverse was failing for most products, often returning irrelevant images or nothing at all. Because of this, many products ended up without images at all. So we had multiple problems at once:</p>
<ul>
<li>latency</li>
<li>poor UX</li>
<li>reliability issues</li>
<li>and incomplete coverage</li>
</ul>
<p>Too many products simply had nothing to rank, and this half-baked workflow was still taking too much time.</p>
<p>I found that one of my colleagues had used the <a href="https://www.pexels.com/api/">Pexels API</a> for their application and it performed surprisingly well, even with technical product names. I replaced Openverse in my application using it just as a fallback.</p>
<p>Pexels API was much more reliable, which meant it was fetching images for hundreds of products without failing. But that also meant the LLM now had to rank <em>all</em> those images.</p>
<h4>From Minutes to Milliseconds: Local embeddings over LLMs</h4>
<p>The LLM ranking was now taking 5–10 minutes and sometimes even 15 minutes when the number of products was high.</p>
<p>This is when I decided to revisit the requirement again. All I really needed was that the image be relevant to the product name.</p>
<p>I looked online and found a good number of models that can be used for exactly this purpose. These models work by converting images and corresponding text into vectors. They then calculate similarity using a dot product (or cosine similarity).</p>
<p><strong>LLMs only make sense when you have to ask back-and-forth questions regarding the images.</strong></p>
<p>My requirement was way more straightforward. No generation, no reasoning.<br>
<img src="https://substack-post-media.s3.amazonaws.com/public/images/30dbcd05-4e23-4397-9364-dcd6ff502900_8192x775.png" alt=""></p>
<p>I replaced the LLM calls with a local inference using the <a href="https://huggingface.co/sentence-transformers/clip-ViT-B-32">clip-ViT-B-32</a> model, and the amount of time to process all those images dropped to milliseconds. In fact, the API calls to fetch the images were now taking longer than ranking them.</p>
<h3>You may not need LLMs either</h3>
<p>In hindsight, this wasn’t an image generation problem at all. It was a retrieval and ranking problem.</p>
<p>The biggest takeaway for me was this: look closely at your actual requirement before reaching for LLMs or generative AI. Sometimes, not using LLMs is the better engineering choice.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Streaming LLM Responses with Server-Sent Events</title>
    <link href="https://chns.in/posts/2026-06-02-streaming-llm-responses-sse/" />
    <id>https://chns.in/posts/2026-06-02-streaming-llm-responses-sse/</id>
    <updated>2026-01-17T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>If you decide to wait till an LLM is done with its response, you'd probably end up never using it.  Luckily, LLMs are able to emit tokens one at a time. This allows AI applications to stream their response as it is emitted.</p>
<p>The mechanism used for this purpose is Server-Sent Events (SSE).</p>
<h2>Server Sent Events (SSE)</h2>
<p>SSE is an HTTP response format described by <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html">WHATWG HTML Living Standard</a> for servers to push data to a browser over a persistent HTTP connection.</p>
<blockquote>
<p><strong>Web Hypertext Application Technology Working Group</strong> or <strong>WHATWG</strong> maintains the HTML Living Standard which specifies how browsers should work. Which makes SSE more of a browser thing rather than an internet standard which are maintained by IETF and W3C.</p>
</blockquote>
<p>The server sends a response with <code>Content-Type: text/event-stream</code> and keeps the connection open, writing events whenever it has something to say. The client receives them as they arrive.</p>
<p>The wire format is minimal. Each event is one or more <code>data:</code> lines followed by a blank line:</p>
<pre class="language-txt"><code class="language-txt">data: This is the first message.

data: This is the second message, it
data: has two lines.

data: This is the third message.
</code></pre>
<p>Events can also carry these other fields:</p>
<ul>
<li><code>id:</code> Used for reconnection. The browser sends the last received ID in a <code>Last-Event-ID</code> header when it reconnects.</li>
<li><code>event:</code> For named event types. But in practice you often skip both and just put all the information in the <code>data:</code> JSON payload itself.</li>
</ul>
<p>The browser exposes this via the <code>EventSource</code> API:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> source <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventSource</span><span class="token punctuation">(</span><span class="token string">'/api/stream'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
source<span class="token punctuation">.</span><span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>That's all the client needs. The browser handles reconnection automatically if the connection drops.</p>
<h3>Why Not WebSockets</h3>
<p>At this point, you might have this question if you're familiar with WebSockets since it also pushes data from server to client without the client polling.</p>
<p>And you can actually use WebSockets for this purpose as well, but it would be like using a bulldozer to hammer a nail. WebSockets are bidirectional and work great for chat, collaborative editing, multiplayer games. But for the much simpler use case of streaming text, the client just sends one request and only listens - unidirectional.</p>
<p>SSE also runs over plain HTTP. It's just a long-lived response. While WebSockets require a connection upgrade and a lot of custom logic just to work.</p>
<p>SSE is just the right tool for this use case. But I don't use it as it is.</p>
<h2>My Setup</h2>
<p>It has three participants: an LLM API, my Node.js server, and the browser. The LLM API itself streams SSE. So the server receives a stream and has to forward it to the browser as a new stream.</p>
<figure class="diagram"><?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-d2-version="v0.7.1" preserveAspectRatio="xMinYMin meet" viewBox="0 0 807 268"><svg class="d2-2778515517 d2-svg" width="807" height="268" viewBox="-101 -101 807 268"><rect x="-101.000000" y="-101.000000" width="807.000000" height="268.000000" rx="0.000000" fill="#FFFFFF" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-2778515517 .text-bold {
	font-family: "d2-2778515517-font-bold";
}
@font-face {
	font-family: d2-2778515517-font-bold;
	src: url("data:application/font-woff;base64,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");
}
.d2-2778515517 .text-italic {
	font-family: "d2-2778515517-font-italic";
}
@font-face {
	font-family: d2-2778515517-font-italic;
	src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
  shape-rendering: geometricPrecision;
  stroke-linejoin: round;
}
.connection {
  stroke-linecap: round;
  stroke-linejoin: round;
}
.blend {
  mix-blend-mode: multiply;
  opacity: 0.5;
}

		.d2-2778515517 .fill-N1{fill:#0A0F25;}
		.d2-2778515517 .fill-N2{fill:#676C7E;}
		.d2-2778515517 .fill-N3{fill:#9499AB;}
		.d2-2778515517 .fill-N4{fill:#CFD2DD;}
		.d2-2778515517 .fill-N5{fill:#DEE1EB;}
		.d2-2778515517 .fill-N6{fill:#EEF1F8;}
		.d2-2778515517 .fill-N7{fill:#FFFFFF;}
		.d2-2778515517 .fill-B1{fill:#0D32B2;}
		.d2-2778515517 .fill-B2{fill:#0D32B2;}
		.d2-2778515517 .fill-B3{fill:#E3E9FD;}
		.d2-2778515517 .fill-B4{fill:#E3E9FD;}
		.d2-2778515517 .fill-B5{fill:#EDF0FD;}
		.d2-2778515517 .fill-B6{fill:#F7F8FE;}
		.d2-2778515517 .fill-AA2{fill:#4A6FF3;}
		.d2-2778515517 .fill-AA4{fill:#EDF0FD;}
		.d2-2778515517 .fill-AA5{fill:#F7F8FE;}
		.d2-2778515517 .fill-AB4{fill:#EDF0FD;}
		.d2-2778515517 .fill-AB5{fill:#F7F8FE;}
		.d2-2778515517 .stroke-N1{stroke:#0A0F25;}
		.d2-2778515517 .stroke-N2{stroke:#676C7E;}
		.d2-2778515517 .stroke-N3{stroke:#9499AB;}
		.d2-2778515517 .stroke-N4{stroke:#CFD2DD;}
		.d2-2778515517 .stroke-N5{stroke:#DEE1EB;}
		.d2-2778515517 .stroke-N6{stroke:#EEF1F8;}
		.d2-2778515517 .stroke-N7{stroke:#FFFFFF;}
		.d2-2778515517 .stroke-B1{stroke:#0D32B2;}
		.d2-2778515517 .stroke-B2{stroke:#0D32B2;}
		.d2-2778515517 .stroke-B3{stroke:#E3E9FD;}
		.d2-2778515517 .stroke-B4{stroke:#E3E9FD;}
		.d2-2778515517 .stroke-B5{stroke:#EDF0FD;}
		.d2-2778515517 .stroke-B6{stroke:#F7F8FE;}
		.d2-2778515517 .stroke-AA2{stroke:#4A6FF3;}
		.d2-2778515517 .stroke-AA4{stroke:#EDF0FD;}
		.d2-2778515517 .stroke-AA5{stroke:#F7F8FE;}
		.d2-2778515517 .stroke-AB4{stroke:#EDF0FD;}
		.d2-2778515517 .stroke-AB5{stroke:#F7F8FE;}
		.d2-2778515517 .background-color-N1{background-color:#0A0F25;}
		.d2-2778515517 .background-color-N2{background-color:#676C7E;}
		.d2-2778515517 .background-color-N3{background-color:#9499AB;}
		.d2-2778515517 .background-color-N4{background-color:#CFD2DD;}
		.d2-2778515517 .background-color-N5{background-color:#DEE1EB;}
		.d2-2778515517 .background-color-N6{background-color:#EEF1F8;}
		.d2-2778515517 .background-color-N7{background-color:#FFFFFF;}
		.d2-2778515517 .background-color-B1{background-color:#0D32B2;}
		.d2-2778515517 .background-color-B2{background-color:#0D32B2;}
		.d2-2778515517 .background-color-B3{background-color:#E3E9FD;}
		.d2-2778515517 .background-color-B4{background-color:#E3E9FD;}
		.d2-2778515517 .background-color-B5{background-color:#EDF0FD;}
		.d2-2778515517 .background-color-B6{background-color:#F7F8FE;}
		.d2-2778515517 .background-color-AA2{background-color:#4A6FF3;}
		.d2-2778515517 .background-color-AA4{background-color:#EDF0FD;}
		.d2-2778515517 .background-color-AA5{background-color:#F7F8FE;}
		.d2-2778515517 .background-color-AB4{background-color:#EDF0FD;}
		.d2-2778515517 .background-color-AB5{background-color:#F7F8FE;}
		.d2-2778515517 .color-N1{color:#0A0F25;}
		.d2-2778515517 .color-N2{color:#676C7E;}
		.d2-2778515517 .color-N3{color:#9499AB;}
		.d2-2778515517 .color-N4{color:#CFD2DD;}
		.d2-2778515517 .color-N5{color:#DEE1EB;}
		.d2-2778515517 .color-N6{color:#EEF1F8;}
		.d2-2778515517 .color-N7{color:#FFFFFF;}
		.d2-2778515517 .color-B1{color:#0D32B2;}
		.d2-2778515517 .color-B2{color:#0D32B2;}
		.d2-2778515517 .color-B3{color:#E3E9FD;}
		.d2-2778515517 .color-B4{color:#E3E9FD;}
		.d2-2778515517 .color-B5{color:#EDF0FD;}
		.d2-2778515517 .color-B6{color:#F7F8FE;}
		.d2-2778515517 .color-AA2{color:#4A6FF3;}
		.d2-2778515517 .color-AA4{color:#EDF0FD;}
		.d2-2778515517 .color-AA5{color:#F7F8FE;}
		.d2-2778515517 .color-AB4{color:#EDF0FD;}
		.d2-2778515517 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-2778515517);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-2778515517);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-2778515517);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-2778515517);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-2778515517);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-2778515517);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-2778515517);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-2778515517);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g class="TExNIEFQSQ=="><g class="shape" ><rect x="0.000000" y="0.000000" width="99.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="49.500000" y="38.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">LLM API</text></g><g class="RXhwcmVzcyBTZXJ2ZXI="><g class="shape" ><rect x="225.000000" y="0.000000" width="150.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="300.000000" y="38.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Express Server</text></g><g class="QnJvd3Nlcg=="><g class="shape" ><rect x="501.000000" y="0.000000" width="104.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="553.000000" y="38.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Browser</text></g><g class="KExMTSBBUEkgLSZndDsgRXhwcmVzcyBTZXJ2ZXIpWzBd"><marker id="mk-d2-2778515517-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" fill="#0D32B2" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 101.000000 33.000000 C 149.399994 33.000000 174.600006 33.000000 221.000000 33.000000" stroke="#0D32B2" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2778515517-3488378134)" mask="url(#d2-2778515517)" /><text x="162.000000" y="39.000000" fill="#676C7E" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">SSE</text></g><g class="KEV4cHJlc3MgU2VydmVyIC0mZ3Q7IEJyb3dzZXIpWzBd"><path d="M 377.000000 33.000000 C 425.399994 33.000000 450.600006 33.000000 497.000000 33.000000" stroke="#0D32B2" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-2778515517-3488378134)" mask="url(#d2-2778515517)" /><text x="438.000000" y="39.000000" fill="#676C7E" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">SSE</text></g><mask id="d2-2778515517" maskUnits="userSpaceOnUse" x="-101" y="-101" width="807" height="268">
<rect x="-101" y="-101" width="807" height="268" fill="white"></rect>
<rect x="147.000000" y="23.000000" width="30" height="21" fill="black"></rect>
<rect x="423.000000" y="23.000000" width="30" height="21" fill="black"></rect>
</mask></svg></svg>
</figure><p>On the server side, I use Axios with <code>responseType: 'stream'</code> to consume the LLM's SSE response as a Node.js readable stream, then call <code>res.write()</code> to push each event down to the browser.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token constant">LLM_ENDPOINT</span><span class="token punctuation">,</span> payload<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">responseType</span><span class="token operator">:</span> <span class="token string">'stream'</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

response<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'data'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">chunk</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> line <span class="token keyword">of</span> chunk<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>line<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'data: '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> raw <span class="token operator">=</span> line<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>raw <span class="token operator">===</span> <span class="token string">'[DONE]'</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> parsed <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>raw<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> content <span class="token operator">=</span> parsed<span class="token punctuation">.</span>generations<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">?.</span>text <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>content<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      buffer <span class="token operator">+=</span> content<span class="token punctuation">;</span>
      <span class="token keyword">const</span> partial <span class="token operator">=</span> <span class="token function">tryParsePartialJson</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>partial<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'partial'</span><span class="token punctuation">,</span> <span class="token literal-property property">data</span><span class="token operator">:</span> partial <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n\n</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

response<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'end'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> final <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
  res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'complete'</span><span class="token punctuation">,</span> <span class="token literal-property property">data</span><span class="token operator">:</span> final <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n\n</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The server also needs three headers:</p>
<ul>
<li><strong><code>Content-Type: text/event-stream</code></strong>: Standard SSE header. Tells the browser to treat it as a stream rather than a regular one-shot response body.</li>
<li><strong><code>Cache-Control: no-cache</code></strong>: Prevents proxies and the browser from caching the response. Without this, intermediaries might buffer the entire response before forwarding it.</li>
<li><strong><code>Connection: keep-alive</code></strong>: Instructs the underlying TCP connection to stay open. Without it, the connection could be closed after what looks like a complete HTTP response.</li>
</ul>
<blockquote>
<p>❗️If you're using a reverse proxy, this can break production. Reverse proxies buffer HTTP responses by default, waiting to accumulate enough data before forwarding. SSE events held in a buffer don't reach the browser until the buffer is flushed, defeating the whole point.</p>
<p>For Nginx, the fix is one header: <code>res.setHeader('X-Accel-Buffering', 'no')</code>. This is an Nginx directive that can be set via a response header. Setting it to <code>no</code> tells Nginx to pass each write through immediately. Other reverse proxies have their own equivalent mechanisms.</p>
</blockquote>
<h2>Out of the SSE Box</h2>
<p>SSE was originally meant for live feeds and stock tickers - much lighter use cases. With AI applications, the requirements go further. But there are common patterns and practices already.</p>
<h3><code>fetch()</code> Instead of <code>EventSource</code></h3>
<p>The browser has a native <code>EventSource</code> API for SSE. You'd think to use it here, but there's a catch: <code>EventSource</code> only supports GET requests. My endpoint needs a request body (the user's prompt, some config), so I can't use GET.</p>
<p>The standard approach across LLM apps and SDKs is using <code>fetch()</code> with the Streams API.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/api/ai/suggest/stream'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">'POST'</span><span class="token punctuation">,</span>
  <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'Content-Type'</span><span class="token operator">:</span> <span class="token string">'application/json'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span> prompt<span class="token punctuation">,</span> pricebookId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token literal-property property">signal</span><span class="token operator">:</span> abortController<span class="token punctuation">.</span>signal
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> reader <span class="token operator">=</span> response<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">getReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> decoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> buffer <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>


<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token punctuation">{</span> done<span class="token punctuation">,</span> value <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> reader<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>done<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>

  buffer <span class="token operator">+=</span> decoder<span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">stream</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> lines <span class="token operator">=</span> buffer<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  buffer <span class="token operator">=</span> lines<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// keep the incomplete last line</span>

  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> line <span class="token keyword">of</span> lines<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>line<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'data: '</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>line<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">handleEvent</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>The <code>buffer = lines.pop()</code> line is the important bit. A TCP chunk can arrive mid-event. The <code>data:</code> line might be split across two <code>reader.read()</code> calls. Keeping the last (potentially incomplete) line in the buffer and prepending it to the next chunk handles this correctly.</p>
<p>The <code>AbortController</code> signal gives you cancellation for free: pass it to <code>fetch</code>, and calling <code>abort()</code> tears down the connection. I use this when the user navigates away or explicitly cancels.</p>
<h3>Streaming Structured Data</h3>
<p>Streaming plain text is straightforward. My case had the LLM generating structured JSON. When formed completely, this JSON would fill up a table on the front-end. But I wanted to stream this table, which would involve parsing the half-baked JSON as it poured in and putting the values in the right rows and column.</p>
<p>The problem is that JSON is only valid once it's complete. A mid-stream buffer like <code>{&quot;items&quot;:[{&quot;name&quot;:&quot;Cloud S</code> will fail <code>JSON.parse</code>.</p>
<p>I used a library called <a href="https://www.npmjs.com/package/partial-json"><code>partial-json</code></a> for this. It parses whatever is valid so far in an incomplete JSON string. So <code>{&quot;items&quot;:[{&quot;name&quot;:&quot;Cloud S</code> becomes <code>{ items: [{ name: &quot;Cloud S&quot; }] }</code>. It handles open arrays, open objects, truncated strings.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> parse <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'partial-json'</span><span class="token punctuation">;</span>

<span class="token comment">// On each chunk from the LLM:</span>
buffer <span class="token operator">+=</span> newContent<span class="token punctuation">;</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> partial <span class="token operator">=</span> <span class="token function">parse</span><span class="token punctuation">(</span>buffer<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>partial <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> partial <span class="token operator">===</span> <span class="token string">'object'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token function">onChunk</span><span class="token punctuation">(</span>partial<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// send to frontend</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>_<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// buffer not yet parseable, keep accumulating</span>
<span class="token punctuation">}</span></code></pre>
<p>This lets the UI show suggestions appearing one by one as the LLM generates them, even though the final response is a single JSON object.</p>
<h3>One Stream, Multiple Concerns</h3>
<p>A streaming LLM call isn't just one thing happening. Before the LLM even runs, my server does some prep work (extracting context, building a plan). I wanted the UI to reflect each of these phases, not just sit on a spinner until the first chunk arrived.</p>
<p>Instead of opening multiple connections, I used a typed event protocol over the same SSE stream:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// Server emits these as the pipeline progresses:</span>
<span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'phase'</span><span class="token punctuation">,</span>    <span class="token literal-property property">phase</span><span class="token operator">:</span> <span class="token string">'extracting_context'</span> <span class="token punctuation">}</span>
<span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'phase'</span><span class="token punctuation">,</span>    <span class="token literal-property property">phase</span><span class="token operator">:</span> <span class="token string">'building_plan'</span> <span class="token punctuation">}</span>
<span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'partial'</span><span class="token punctuation">,</span>  <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>      <span class="token comment">// LLM streaming</span>
<span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'complete'</span><span class="token punctuation">,</span> <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">items</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>       <span class="token comment">// final result</span>
<span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'error'</span><span class="token punctuation">,</span>    <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">'...'</span> <span class="token punctuation">}</span>               <span class="token comment">// on failure</span></code></pre>
<p>The client switches on <code>data.type</code> and updates the UI accordingly, changing the status message during the pipeline phases, then rendering incremental suggestions as <code>partial</code> events arrive.</p>
<p>Using JSON payloads over a single typed channel is simpler than SSE's native <code>event:</code> field, because you can carry arbitrary structured data alongside the type. And since you're already parsing lines manually via <code>fetch()</code>, the native <code>event:</code> field would just be one more thing to parse out.</p>
<h2>Error Handling</h2>
<p>Errors during streaming are awkward: you've already sent a <code>200 OK</code> status and started writing the body, so you can't send an HTTP error code anymore. The solution is to send an error event over the stream and close it.</p>
<pre class="language-js"><code class="language-js">res<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'error'</span><span class="token punctuation">,</span> <span class="token literal-property property">message</span><span class="token operator">:</span> err<span class="token punctuation">.</span>message <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">\n\n</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>On the client, the <code>error</code> event triggers the same cleanup and user-facing message as any other error path. The important thing is to always call <code>res.end()</code>. Leaving the connection open after an error will hang the client.</p>
<h2>The Before/After</h2>
<p>Streaming is becoming an increasingly common pattern in AI applications, and for good reason. The non-streaming version was a regular <code>async/await</code> call: send request, wait several seconds, return JSON. Straightforward to write, but the UX was a fixed spinner with no indication of progress.</p>
<p>This created an impression of a slow application, with users saying they might just do it manually.</p>
<p>The streaming version is more moving parts but the result is that the user sees each phase as it happens and suggestions start appearing well before the LLM finishes. For a feature people are waiting on, that matters.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Cut Save Time Using Salesforce&#39;s Composite Graph API</title>
    <link href="https://chns.in/posts/2026-06-02-cut-save-time-composite-graph-api/" />
    <id>https://chns.in/posts/2026-06-02-cut-save-time-composite-graph-api/</id>
    <updated>2026-01-12T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>One of the tools I work on allows customizing hundreds of products via AI, all of them being saved to a Salesforce org. Each entry required a <code>Product2</code> record plus <code>PricebookEntry</code> records for the standard and selected pricebooks. The existing logic handled this with the standard Composite API, which worked fine till the product customizations were manual and limited. Once we introduced AI into the mix, it allowed for bulk customization, requiring a 'save' for all the products. For larger sets, the save was noticeably slow, sometimes several seconds.</p>
<figure class="diagram"><?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-d2-version="v0.7.1" preserveAspectRatio="xMinYMin meet" viewBox="0 0 631 461"><svg class="d2-1474869633 d2-svg" width="631" height="461" viewBox="-101 -101 631 461"><rect x="-101.000000" y="-101.000000" width="631.000000" height="461.000000" rx="0.000000" fill="#FFFFFF" class=" fill-N7" stroke-width="0" /><style type="text/css"><![CDATA[
.d2-1474869633 .text {
	font-family: "d2-1474869633-font-regular";
}
@font-face {
	font-family: d2-1474869633-font-regular;
	src: url("data:application/font-woff;base64,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");
}
.d2-1474869633 .text-bold {
	font-family: "d2-1474869633-font-bold";
}
@font-face {
	font-family: d2-1474869633-font-bold;
	src: url("data:application/font-woff;base64,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");
}
.d2-1474869633 .text-italic {
	font-family: "d2-1474869633-font-italic";
}
@font-face {
	font-family: d2-1474869633-font-italic;
	src: url("data:application/font-woff;base64,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");
}]]></style><style type="text/css"><![CDATA[.shape {
  shape-rendering: geometricPrecision;
  stroke-linejoin: round;
}
.connection {
  stroke-linecap: round;
  stroke-linejoin: round;
}
.blend {
  mix-blend-mode: multiply;
  opacity: 0.5;
}

		.d2-1474869633 .fill-N1{fill:#0A0F25;}
		.d2-1474869633 .fill-N2{fill:#676C7E;}
		.d2-1474869633 .fill-N3{fill:#9499AB;}
		.d2-1474869633 .fill-N4{fill:#CFD2DD;}
		.d2-1474869633 .fill-N5{fill:#DEE1EB;}
		.d2-1474869633 .fill-N6{fill:#EEF1F8;}
		.d2-1474869633 .fill-N7{fill:#FFFFFF;}
		.d2-1474869633 .fill-B1{fill:#0D32B2;}
		.d2-1474869633 .fill-B2{fill:#0D32B2;}
		.d2-1474869633 .fill-B3{fill:#E3E9FD;}
		.d2-1474869633 .fill-B4{fill:#E3E9FD;}
		.d2-1474869633 .fill-B5{fill:#EDF0FD;}
		.d2-1474869633 .fill-B6{fill:#F7F8FE;}
		.d2-1474869633 .fill-AA2{fill:#4A6FF3;}
		.d2-1474869633 .fill-AA4{fill:#EDF0FD;}
		.d2-1474869633 .fill-AA5{fill:#F7F8FE;}
		.d2-1474869633 .fill-AB4{fill:#EDF0FD;}
		.d2-1474869633 .fill-AB5{fill:#F7F8FE;}
		.d2-1474869633 .stroke-N1{stroke:#0A0F25;}
		.d2-1474869633 .stroke-N2{stroke:#676C7E;}
		.d2-1474869633 .stroke-N3{stroke:#9499AB;}
		.d2-1474869633 .stroke-N4{stroke:#CFD2DD;}
		.d2-1474869633 .stroke-N5{stroke:#DEE1EB;}
		.d2-1474869633 .stroke-N6{stroke:#EEF1F8;}
		.d2-1474869633 .stroke-N7{stroke:#FFFFFF;}
		.d2-1474869633 .stroke-B1{stroke:#0D32B2;}
		.d2-1474869633 .stroke-B2{stroke:#0D32B2;}
		.d2-1474869633 .stroke-B3{stroke:#E3E9FD;}
		.d2-1474869633 .stroke-B4{stroke:#E3E9FD;}
		.d2-1474869633 .stroke-B5{stroke:#EDF0FD;}
		.d2-1474869633 .stroke-B6{stroke:#F7F8FE;}
		.d2-1474869633 .stroke-AA2{stroke:#4A6FF3;}
		.d2-1474869633 .stroke-AA4{stroke:#EDF0FD;}
		.d2-1474869633 .stroke-AA5{stroke:#F7F8FE;}
		.d2-1474869633 .stroke-AB4{stroke:#EDF0FD;}
		.d2-1474869633 .stroke-AB5{stroke:#F7F8FE;}
		.d2-1474869633 .background-color-N1{background-color:#0A0F25;}
		.d2-1474869633 .background-color-N2{background-color:#676C7E;}
		.d2-1474869633 .background-color-N3{background-color:#9499AB;}
		.d2-1474869633 .background-color-N4{background-color:#CFD2DD;}
		.d2-1474869633 .background-color-N5{background-color:#DEE1EB;}
		.d2-1474869633 .background-color-N6{background-color:#EEF1F8;}
		.d2-1474869633 .background-color-N7{background-color:#FFFFFF;}
		.d2-1474869633 .background-color-B1{background-color:#0D32B2;}
		.d2-1474869633 .background-color-B2{background-color:#0D32B2;}
		.d2-1474869633 .background-color-B3{background-color:#E3E9FD;}
		.d2-1474869633 .background-color-B4{background-color:#E3E9FD;}
		.d2-1474869633 .background-color-B5{background-color:#EDF0FD;}
		.d2-1474869633 .background-color-B6{background-color:#F7F8FE;}
		.d2-1474869633 .background-color-AA2{background-color:#4A6FF3;}
		.d2-1474869633 .background-color-AA4{background-color:#EDF0FD;}
		.d2-1474869633 .background-color-AA5{background-color:#F7F8FE;}
		.d2-1474869633 .background-color-AB4{background-color:#EDF0FD;}
		.d2-1474869633 .background-color-AB5{background-color:#F7F8FE;}
		.d2-1474869633 .color-N1{color:#0A0F25;}
		.d2-1474869633 .color-N2{color:#676C7E;}
		.d2-1474869633 .color-N3{color:#9499AB;}
		.d2-1474869633 .color-N4{color:#CFD2DD;}
		.d2-1474869633 .color-N5{color:#DEE1EB;}
		.d2-1474869633 .color-N6{color:#EEF1F8;}
		.d2-1474869633 .color-N7{color:#FFFFFF;}
		.d2-1474869633 .color-B1{color:#0D32B2;}
		.d2-1474869633 .color-B2{color:#0D32B2;}
		.d2-1474869633 .color-B3{color:#E3E9FD;}
		.d2-1474869633 .color-B4{color:#E3E9FD;}
		.d2-1474869633 .color-B5{color:#EDF0FD;}
		.d2-1474869633 .color-B6{color:#F7F8FE;}
		.d2-1474869633 .color-AA2{color:#4A6FF3;}
		.d2-1474869633 .color-AA4{color:#EDF0FD;}
		.d2-1474869633 .color-AA5{color:#F7F8FE;}
		.d2-1474869633 .color-AB4{color:#EDF0FD;}
		.d2-1474869633 .color-AB5{color:#F7F8FE;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.sketch-overlay-B1{fill:url(#streaks-darker-d2-1474869633);mix-blend-mode:lighten}.sketch-overlay-B2{fill:url(#streaks-darker-d2-1474869633);mix-blend-mode:lighten}.sketch-overlay-B3{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-B4{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-B5{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-B6{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-AA2{fill:url(#streaks-dark-d2-1474869633);mix-blend-mode:overlay}.sketch-overlay-AA4{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-AA5{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-AB4{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-AB5{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-N1{fill:url(#streaks-darker-d2-1474869633);mix-blend-mode:lighten}.sketch-overlay-N2{fill:url(#streaks-dark-d2-1474869633);mix-blend-mode:overlay}.sketch-overlay-N3{fill:url(#streaks-normal-d2-1474869633);mix-blend-mode:color-burn}.sketch-overlay-N4{fill:url(#streaks-normal-d2-1474869633);mix-blend-mode:color-burn}.sketch-overlay-N5{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-N6{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.sketch-overlay-N7{fill:url(#streaks-bright-d2-1474869633);mix-blend-mode:darken}.light-code{display: block}.dark-code{display: none}]]></style><g class="UHJvZHVjdDI="><g class="shape" ><rect x="0.000000" y="3.000000" width="109.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="54.500000" y="41.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">Product2</text></g><g class="UHJpY2Vib29rRW50cnk="><g class="shape" ><rect x="99.000000" y="193.000000" width="156.000000" height="66.000000" stroke="#0D32B2" fill="#F7F8FE" class=" stroke-B1 fill-B6" style="stroke-width:2;" /></g><text x="177.000000" y="231.500000" fill="#0A0F25" class="text-bold fill-N1" style="text-anchor:middle;font-size:16px">PricebookEntry</text></g><g class="UHJpY2Vib29rMg=="><g class="shape" ><rect x="169.000000" y="0.000000" width="260.000000" height="72.000000" stroke="#0A0F25" fill="#FFFFFF" class="shape stroke-N1 fill-N7" style="stroke-width:2;" /><rect x="169.000000" y="0.000000" width="260.000000" height="36.000000" fill="#0A0F25" class="class_header fill-N1" /><text x="179.000000" y="25.750000" fill="#FFFFFF" class="text fill-N7" style="text-anchor:start;font-size:24px">Pricebook2</text><text x="179.000000" y="59.000000" fill="#0D32B2" class="text fill-B2" style="text-anchor:start;font-size:20px">Type</text><text x="241.000000" y="59.000000" fill="#676C7E" class="text fill-N2" style="text-anchor:start;font-size:20px">Standard or Custom</text><text x="419.000000" y="59.000000" fill="#4A6FF3" class="text fill-AA2" style="text-anchor:end;font-size:20px" /><line x1="169.000000" x2="429.000000" y1="72.000000" y2="72.000000" stroke="#0A0F25" class=" stroke-N1" style="stroke-width:2" /></g></g><g class="KFByb2R1Y3QyIC0mZ3Q7IFByaWNlYm9va0VudHJ5KVswXQ=="><marker id="mk-d2-1474869633-3488378134" markerWidth="10.000000" markerHeight="12.000000" refX="7.000000" refY="6.000000" viewBox="0.000000 0.000000 10.000000 12.000000" orient="auto" markerUnits="userSpaceOnUse"> <polygon points="0.000000,0.000000 10.000000,6.000000 0.000000,12.000000" fill="#0D32B2" class="connection fill-B1" stroke-width="2" /> </marker><path d="M 54.500000 70.500000 C 54.500000 119.699997 70.099998 144.699997 129.349128 191.035856" stroke="#0D32B2" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-1474869633-3488378134)" mask="url(#d2-1474869633)" /><text x="70.000000" y="150.000000" fill="#676C7E" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">has</text></g><g class="KFByaWNlYm9vazIgLSZndDsgUHJpY2Vib29rRW50cnkpWzBd"><path d="M 299.000000 73.500000 C 299.000000 120.300003 283.399994 144.699997 224.150872 191.035856" stroke="#0D32B2" fill="none" class="connection stroke-B1" style="stroke-width:2;" marker-end="url(#mk-d2-1474869633-3488378134)" mask="url(#d2-1474869633)" /><text x="282.500000" y="151.000000" fill="#676C7E" class="text-italic fill-N2" style="text-anchor:middle;font-size:16px">lists</text></g><mask id="d2-1474869633" maskUnits="userSpaceOnUse" x="-101" y="-101" width="631" height="461">
<rect x="-101" y="-101" width="631" height="461" fill="white"></rect>
<rect x="56.000000" y="134.000000" width="28" height="21" fill="black"></rect>
<rect x="267.000000" y="135.000000" width="31" height="21" fill="black"></rect>
</mask></svg></svg>
</figure><h2>Too Many Round-Trips</h2>
<p>The standard Composite API (<code>/services/data/v*/composite</code>) has a hard limit of 25 subrequests per request.</p>
<p>For each product we need:</p>
<ul>
<li>1 subrequest for Product2</li>
<li>1 subrequest for the Standard PricebookEntry</li>
<li>N subrequests for custom pricebook entries</li>
</ul>
<p>So with 5 custom pricebooks, each product uses 7 subrequests: fitting only 3 products per batch (25 ÷ 7 ≈ 3). Saving 30 products meant 10 sequential round-trips, each adding its own network latency.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> subrequestsPerProduct <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">+</span> customPricebooks<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
<span class="token keyword">const</span> batchSize <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span><span class="token number">25</span> <span class="token operator">/</span> subrequestsPerProduct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Often 3-8 products</span></code></pre>
<h2>Composite Graph API + sObject Collections</h2>
<p>We moved to the Composite Graph API (<code>/services/data/v56.0/composite/graph</code>) for Product2 creation, and sObject collections (<code>/composite/sobjects</code>) for PricebookEntries.</p>
<ul>
<li><strong>Product2</strong>: one graph per product, all sent in a single API call. Products are created in parallel without sharing the 25-subrequest limit.</li>
<li><strong>PricebookEntries</strong>: sObject collections support up to 200 records per request, so all Standard and Selected PricebookEntries go in one or two calls.</li>
</ul>
<pre class="language-js"><code class="language-js"><span class="token comment">// 1) Create all Product2s via composite/graph (parallel graphs)</span>
<span class="token keyword">const</span> graphResp <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">sendRequest</span><span class="token punctuation">(</span>req<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/services/data/v56.0/composite/graph</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">POST</span><span class="token punctuation">,</span> graphPayload<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 2) Create all Standard PricebookEntries (up to 200 per request)</span>
<span class="token keyword">await</span> <span class="token function">sendRequest</span><span class="token punctuation">(</span>req<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/services/data/v56.0/composite/sobjects</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">POST</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">records</span><span class="token operator">:</span> stdRecords <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 3) Create Selected PricebookEntries (if different from standard)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>shouldCreateSelectedPbe<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> <span class="token function">sendRequest</span><span class="token punctuation">(</span>req<span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/services/data/v56.0/composite/sobjects</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">POST</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">records</span><span class="token operator">:</span> selRecords <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>2-3 API calls total, regardless of how many products.</p>
<h2>Time Savings</h2>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Old (Composite)</th>
<th>New (Graph + sObject)</th>
</tr>
</thead>
<tbody>
<tr>
<td>10 products, 0 custom pricebooks</td>
<td>2 round-trips</td>
<td>2 round-trips</td>
</tr>
<tr>
<td>10 products, 5 custom pricebooks</td>
<td>4 round-trips</td>
<td>2 round-trips</td>
</tr>
<tr>
<td>30 products, 5 custom pricebooks</td>
<td>10 round-trips</td>
<td>2-3 round-trips</td>
</tr>
<tr>
<td>50 products</td>
<td>~17 round-trips</td>
<td>2-3 round-trips</td>
</tr>
</tbody>
</table>
<p>Each round-trip adds roughly 100-300 ms. For 30 products, that's about 2-3 seconds saved per save.</p>
<h2>A Few Design Choices Worth Noting</h2>
<p><strong>Separate graphs per product</strong>: each product gets its own graph so one failure doesn't roll back the others. Users still get partial success when some products fail.</p>
<p><strong>sObject collections with <code>allOrNone: false</code></strong>: lets us create up to 200 PricebookEntries per call while still handling per-record failures gracefully.</p>
<p><strong>Only the selected pricebook</strong>: we create PricebookEntries for the standard pricebook and the selected one, not every custom pricebook. Keeps things simple for the main use case.</p>
<h2>Takeaways</h2>
<p>The Composite Graph API removes the 25-subrequest ceiling by running graphs in parallel. Combined with sObject collections for bulk child records, you go from N round-trips to 2-3 regardless of scale. If you're building similar batch operations on Salesforce, it's worth the switch.</p>
]]></content>
  </entry>
  
  <entry>
    <title>My experience with Summarization models</title>
    <link href="https://chns.in/posts/2025-08-03-my-experience-with-summarization-models/" />
    <id>https://chns.in/posts/2025-08-03-my-experience-with-summarization-models/</id>
    <updated>2025-08-03T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>I recently tested a few summarization models for a project I’m working on.<br>
The goal was to generate article summaries that followed specific tone and formatting instructions, stayed factually accurate. It also involved a minor reasoning task to pick the most impactful stories from a list.</p>
<p>I used free models on <a href="https://openrouter.ai/">Open Router</a> since I was just building an MVP and wasn’t willing to pay just yet.</p>
<p>Following are some of the models I tested and the results. Keep in mind that all of these are free variants of the models.</p>
<h4><strong><a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B">DeepSeek: R1 Distill Llama 70B</a></strong></h4>
<p>This is a distilled LLM based on Llama-3.3-70B-Instruct model. This is the model I tested for the longest since it was one of the few available free models a while ago. This also gave me the best summaries out of the available free models at the time, both in terms of adherence to instructions and accuracy.</p>
<p>The only issue with it was the consistency. 1 in 10 summaries went wrong, quite frequently. Sometimes the format would be unexpected, other times it would outright deny being able to summarize. Occasionally it would also return some random response. For example, some tags for the article instead of summary. It fared well on accuracy though. Never gave any incorrect or outside information.</p>
<p>To be fair I didn’t try adjusting the temperature since I wanted somewhat witty output and I was afraid fiddling with temperature might hamper that. Also, the <a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Llama-70B">hugging face page</a> for the model explicitly states not to give the model any system prompts, which I did anyway. I guess, I didn’t give it a fair chance.</p>
<h4><strong><a href="https://moonshotai.github.io/Kimi-K2/">MoonshotAI: Kimi K2</a></strong></h4>
<p>Since this was a mixture-of-experts model, I figured why not give it a chance as my use case required summarization of articles from different domains.</p>
<p>This gave good results overall. I was not completely satisfied with the impactful article selection. The summaries and their titles lacked clarity as well. Although, it didn’t give me any buggy data.</p>
<h4><a href="https://huggingface.co/deepseek-ai/DeepSeek-V3-0324">DeepSeek V3-0324</a></h4>
<p>I abandoned it soon since it called Trump, ‘former’ POTUS. While not entirely wrong, but still a red flag for my use-case. Also some summaries were too small.</p>
<h4><a href="https://www.aimodels.fyi/models/huggingFace/dolphin-mistral-24b-venice-edition-cognitivecomputations">Dolphin Mistral 24B - Venice Edition</a></h4>
<p>This is a collaborative project between <a href="https://aimodels.fyi/creators/huggingFace/cognitivecomputations">cognitivecomputations</a> and Venice.ai to create an uncensored version of Mistral 24B.</p>
<p>I included this in my testing since this is an uncensored model, which could be particularly useful for my use case of summarizing news articles. But the impactful article selection was not great so I moved along.</p>
<h4><a href="https://huggingface.co/deepseek-ai/DeepSeek-R1-0528">DeepSeek R1 0528</a></h4>
<p>This was the best one until this point in summarization, but it was incredibly, dare I say, even painfully slow. That would not be a deal breaker for my use case since I’m not summarizing in a user-facing application at runtime. Only issue could be that it would make testing it a bit difficult and time consuming.</p>
<p>Another issue was adherence to the format. With the title generation for the summary, it provided an “Alternative title”. I could fix this via prompt, but I decided to keep it aside and give other models a try.</p>
<h4><a href="https://qwenlm.github.io/blog/qwen3-coder/">Qwen3 Coder</a></h4>
<p>Because why not? Its the state-of-the-art coding model right now, and I thought it might be fun to see how a coding model performs as a journalist summarizing articles. Not so surprisingly, this model was fastest one of the bunch. The issue however, was that it didn’t stick to the tone I asked for, which was a bit witty and clever. It also included some html tags like <code>&lt;div&gt;</code> in the response along with some inline styling.</p>
<p>Its exactly what you’d expect from a coder though.</p>
<h4><a href="https://techcommunity.microsoft.com/blog/machinelearningblog/introducing-mai-ds-r1/4405076">Microsoft: MAI DS R1</a></h4>
<p>This is a post-trained variant of DeepSeek-R1 developed by the Microsoft AI team improve the model’s responsiveness on previously blocked topics while enhancing its safety profile. Safety was an important parameter for me and I already had good experience with DeepSeek R1 models, so I decided to give it a try.</p>
<p>This was the best performing model without a doubt. It adhered to all the instructions, remained factually correct and covered all the important points while summarizing while maintaining the tone and format I was going for.</p>
<p>Needless to say, this was the one I ended up using for my use-case.</p>
<p>If you’re exploring summarization for your own project, I hope this gives you a head start. Let me know if you’ve found a model that worked well for you. I’d love to hear.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Notes on the &quot;RAG Paper&quot;</title>
    <link href="https://chns.in/posts/2025-07-16-notes-on-the-rag-paper/" />
    <id>https://chns.in/posts/2025-07-16-notes-on-the-rag-paper/</id>
    <updated>2025-07-16T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>These are my notes for the paper '<a href="https://arxiv.org/abs/2005.11401v4">Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (2005.11401v4)</a>' published in 2020.</p>
<p>My intention with these notes is not to go too deep into the mathematics involved. Where required, some mathematical information is included. The assumption is also that you are familiar with high-school level mathematics. Maybe you don't remember all of it, but if you hear something and you can recognize the concept, it should be enough to understand these largely. I also assume that you understand how a model is trained at a very basic level.</p>
<p>I’d also recommend reading this alongside the original paper. It helps to refer to the figure and notations directly as you go.</p>
<p><img src="https://substack-post-media.s3.amazonaws.com/public/images/6425d9f4-8810-4193-b484-8a96cda9a162_1368x584.png" alt=""><br>
Figure from the paper illustrating the approach</p>
<h2>Terminology</h2>
<ul>
<li><strong>Upstream Tasks</strong>: Tasks which affect the model -&gt; tasks which help the model make sense of the unlabelled data. (not that important for this paper)</li>
<li><strong>Downstream Tasks</strong> mean the specific language tasks we want to solve after pretraining a language, like QnA, sentiment analysis, summarization etc.</li>
<li><strong>Pretraining</strong> refers to the initial training phase where model learns general language patterns by processing large amount of unlabelled data.</li>
<li><strong>Parametric memory</strong>: Trained part of the model. Parameters of the model are fixed after training phase. This can't be changed without training.</li>
<li><strong>Non-parametric memory</strong>: Things stored outside the model, fetched via a 'retriever'.</li>
<li><strong>seq2seq model</strong>: sequence-to-sequence model. maps one sequence to another for tasks of taking one sequence as input and emitting other as output.</li>
<li><strong>Extractive downstream tasks</strong>: Extracting information from documents.</li>
<li><strong>Differentiable access mechanism</strong>: Mechanism to retrieve the relevant documents.</li>
</ul>
<h2>Overview</h2>
<ul>
<li>Pretrained models with just parameterized knowledge base are fixed. Its difficult to revise their memory.</li>
<li>You can't see where the predictions are coming from -&gt; prone to hallucinations</li>
<li><strong>Solution</strong>: Hybrid models combining parameterized and non-parameterised memory
<ul>
<li>knowledge can be revised and expanded</li>
<li>accessed knowledge can also be inspected</li>
</ul>
</li>
<li>REALM and ORQA are two such models but they only extract the information.</li>
<li>Components
<ul>
<li>Parametric memory: pretrained seq2seq transformer (BART)</li>
<li>Non-parametric memory: Dense vector index of Wikipedia</li>
<li>Retriever: Dense Passage Retriever (DPR)</li>
</ul>
</li>
<li>Top-K approximation used for the document retrieval</li>
<li>Previous models with non-parametric memory for were trained for specific tasks. This paper just uses pre-trained memory components.</li>
<li>These are tasks which even humans can't perform without access to an external knowledge source.</li>
<li>These RAG models achieve remarkable results on multiple benchmarks also outperforming other approaches in one.</li>
<li>Models generate more factual, specific and diverse responses than BART baseline.</li>
<li>In a fact verification benchmark, model performs within 4.3% of other state of the art models.</li>
<li>Changing the memory is also demonstrated.</li>
</ul>
<h2>Methods</h2>
<h4>Mathematical Sidenote</h4>
<p>The notation <strong>pₖ(y|x)</strong> is used to represent conditional probability distribution of <em><strong>y</strong></em>, for a given <em><strong>x</strong></em>. <strong>k</strong> represents the parameters of the distribution.</p>
<p><strong>Latent variables</strong> are unknown variables which can't be observed directly, but they affect the outcome.</p>
<p><strong>Marginalization</strong> is way to calculate the total probability of something by considering all possible values of the unkown (latent) variables. Since latent variables are unobserved, we can't pick one, so all of them are included.</p>
<h4>Components and Notation</h4>
<ul>
<li><em><strong>x</strong></em> : input sequence</li>
<li><em><strong>y</strong></em> : output/target sequence</li>
<li><em><strong>z</strong></em> : text documents</li>
</ul>
<p>Based on above, the components are denoted as:</p>
<ol>
<li>Retriever
<ul>
<li>Retriever is supposed to fetch the documents <em><strong>z</strong></em>, given the input sequence <em><strong>x</strong></em>.</li>
<li>It does that by determining probability for different documents, based on <em><strong>x</strong></em>.</li>
<li>The distribution is parameterized by η. These are the parameters within the model which are tuned as part of the training.</li>
</ul>
</li>
<li>Generator
<ul>
<li>Generator is supposed to generate tokens based on the input sequence <em><strong>x</strong></em>, documents <em><strong>z</strong></em> and the previous tokens.</li>
<li>This again depends on the probability distribution of the tokens.</li>
<li>θ refers to the parameters of the model, tuned via training.</li>
<li>Generators have been using this technique of predicting the next token for a while. This wasn't the breakthrough of 'Attention is all you need'.</li>
</ul>
</li>
</ol>
<p>The documents are treated as latent variables.</p>
<p>'Marginalizing the document to get the generated sequence' just means calculating the probability for output sequence using the identified relevant documents, each weighted by relevance.</p>
<h4>Models</h4>
<p>As discussed before, two different ways are used to generate the output sequence:</p>
<ol>
<li><strong>RAG-Sequence Model</strong>
<ul>
<li>Uses same retrieved document to generate the complete sequence.</li>
<li>Retrieved document treated as a single latent variable</li>
<li>The probability of getting output for a given input is denoted by:</li>
<li>The output depends on the token coming before each, so if we write in terms of each $i$:</li>
<li>Since the document <strong>z</strong> is going to be common for all tokens, <em><strong>p(z|x)</strong></em> doesn't need to be part of the term for each i. Its kept separate from the product.</li>
</ul>
</li>
<li><strong>RAG-Token Model</strong>
<ul>
<li>Draw from a different latent documents for each target token.</li>
<li>The probability of output for a given input is denoted as:</li>
<li>For each token, the document is retrieved. So this time $p(z|x)$ is part of the product term.</li>
</ul>
</li>
</ol>
<p>The summation in both cases is performed for the top-K documents retrieved for the input.</p>
<p>For sequence classification case, where target sequence of length one is considered, both models are equivalent.</p>
<h4>Components</h4>
<p>Both RAG models, RAG-Sequence and RAG-Token use the same retriever (DPR) and generator (BART). Its the manner of their usage, which differs.</p>
<p><strong>Retriever - DPR</strong></p>
<ul>
<li>The documents are first encoded by BERT (base) document encoder. This converts the documents into vector embeddings. This is denoted as <em><strong>d(z)</strong></em>.</li>
<li>The query (input) is also encoded by the same. This is denoted as <em><strong>q(z)</strong></em>.</li>
<li>DPR retrieves the top-k documents with highest probabilty using MIPS</li>
<li>MIPS (Maxiumum Inner Product Search) is a similarity search method. It retrieves the documents whose embeddings have the highest dot (inner) product with a query embedding. This is represented in the paper as: pη​(z∣x)∝exp(d(z)⊤q(x))</li>
</ul>
<p><strong>Generator - BART</strong></p>
<ul>
<li>BART (large) is used here. Its a seq2seq transformer with 400M parameters</li>
<li>Any encoder-decoder could have been used for this purpose
<ul>
<li>The model selection depends on what's state-of-the-art at the time and also what's more accessible. Since this research originated at Facebook (now, Meta), using BERT made sense since it was already state-of-the-art at that time, and since it was a model from facebook, they would have more understanding and more control over tweaking it.</li>
</ul>
</li>
<li>When predicting the next token, it is based on what has been predicted till now. To do this, the retrieved content and input is concatenated.</li>
<li>This model has previously also achieved state-of-the-art results on various generation tasks and outperformed similar models at the time.</li>
<li>BART generators internal parameters (parametric memory) is denoted as θ.</li>
</ul>
<h4>Training</h4>
<p>This is quite interesting. So the mechanism requires the documents to be encoded via a document encoder. But if you train the document encoder again, the previous embeddings become meaningless, and you'd have to perform the encoding again.</p>
<p>This was attempted by another model REALM during pre-training and obviously turned out to be quite costly. Instead, here the document encoder is kept fixed along with the encoded index.</p>
<p>Only the query encoder and the generator are fine tuned.</p>
<p>Now the purpose of any learning is to minimize a loss function. The minimization of the 'negative marginal log-likelihood' is just a standard for seq2seq models. Basically, you want to tune the parameters in such a way that you maximize the probability of the correct answer getting picked. You can also turn it around and say, you want to minimize the negative probability of the correct answer getting picked.</p>
<p>Training is done via gradient descent and most optimizers (used for training) support this downhill roll, rather than an uphill climb, which is why its flipped. You keep moving downhill, until you're in the valley's deepest point (the minimum).</p>
<p>Logarithmic just makes the gradient more stable and turns product into sum. If you keep multiplying the probabilities for each pair, it could limit zero.</p>
<h4>Decoding</h4>
<p>Decoding process of both models is different.</p>
<p><strong>RAG-Token</strong></p>
<p>The per-token nature of generation was established as a standard up until that time. So, RAG-Token model simply used that. It does that using beam search.</p>
<p>Beam search predicts possibility of next-token from its vocabulary, takes the top-k tokens, predicts the next possible token for each of those $k$ tokens, and continues to do so until end-of-sequence.</p>
<p>In RAG-Token, the vocabulary is the entire document index. So this can be used directly.</p>
<p><strong>RAG-Sequence</strong></p>
<p>In RAG-sequence though, the vocabulary can't be the entire document index. It needs to pick a single document which is most relevant. Thus, beam search is performed for each document. This results in a set of hypotheses <em><strong>Y</strong></em>. These are the set of output sequences from each document.</p>
<p>Thorough decoding: The decoder is re-run for each hypotheses <strong>y</strong>, against each document <em><strong>z</strong></em> except for the one it was generated from.</p>
<p>Fast decoding: Additional pass is skipped for longer output sequences for efficiency. In this case, its assumed that the probability for y, against each document it was not generated from is 0. That is:</p>
<p>These give the probability of how likely <em><strong>y</strong></em> is, if model had access to just <em><strong>z</strong></em>. Each such term is multiplied with the <em><strong>p(z|x)</strong></em>, which is the probability of <em><strong>z</strong></em> being the right document for the input <em><strong>x</strong></em>. Combining both (summation) gives us the marginal probability of <em><strong>y</strong></em> emerging from <em><strong>x</strong></em>.</p>
<p>After this, the hypotheses with the highest probability is selected as the output sequence.</p>
<p>The experiments, results, model comparison etc will be covered in a follow-up.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Vast Intellect Thought Experiment</title>
    <link href="https://chns.in/posts/2025-02-20-vast-intellect-thought-experiment/" />
    <id>https://chns.in/posts/2025-02-20-vast-intellect-thought-experiment/</id>
    <updated>2025-02-20T00:00:00Z</updated>
    <content type="html"><![CDATA[<p><em>Disclaimer: The following is a completely useless discussion on determinism, predictability, physics, and God. Read only if you're out of better things to do — or if you enjoy philosophical rabbit holes.</em></p>
<p><img src="https://substack-post-media.s3.amazonaws.com/public/images/1bde94f1-f1ab-4e95-9b1a-78125dbf9fec_4000x2250.jpeg" alt=""></p>
<blockquote>
<p>We may regard the present state of the universe as the effect of its past and the cause of its future. An intellect which at a certain moment would know all forces that set nature in motion, and all positions of all items of which nature is composed, if this intellect were also vast enough to submit these data to analysis, it would embrace in a single formula the movements of the greatest bodies of the universe and those of the tiniest atom; for such an intellect nothing would be uncertain and the future just like the past could be present before its eyes.</p>
<p>— Pierre Simon Laplace</p>
</blockquote>
<p>Laplace introduced this concept of a “vast intellect” while talking about determinism in classical physics. Here we take it a step further and apply it to literally everything (and see if we can create God).</p>
<p>There have been arguments against this demon based on the non-deterministic nature of quantum mechanics or thermodynamic systems. But we don’t really care about determinism, reversibility, or the past, since we are trying to create God. A being of such perception would already know what has already happened. We are mainly concerned with predictability. If this being even knows all the possible futures, that should be sufficient. There have also been arguments citing the unpredictability of chaotic systems or free will acts. However, I think (in my limited knowledge) that this “unpredictability” is a result of our limited human precision. Such a creature would have unlimited precision, making what seems unpredictable to us entirely predictable to it.</p>
<p>Suppose such a vast intellect exists, having access to all the parameters and knowledge necessary to determine at any point in time how things will unfold. It would require an enormous amount of processing power. Is it possible? There is, in fact, a computational limit of the universe defined by Bremermann's limit, which places an upper bound on the computational capacity of a finite system. Since computation is a physical process, it is bound by the constraints of the physical universe. There is obviously limited mass and energy in the universe, hence, a limit to computations. While this limit is far beyond human capability, it still is a limitation. However, since this is a thought experiment, let’s hold that thought for now and assume it has the necessary computational power. Perhaps everything played in its favor (like human evolution), leading to its emergence over time without self-destruction (unlike humans). It could have neurons—or some equivalent processing units—billions of times more numerous than those in human brains. The sheer scale of such an entity is incomprehensible. Imagine the sensory input required to gather that much data, process it based on the corresponding knowledge, and determine its consequences.</p>
<p>Talking about senses - since its not just the processing, but also the ingestion of this data, The creature might possess a multitude of senses, allowing it to perceive reality at both the most microscopic and macroscopic levels, spanning atomic interactions to universal-scale events. Such a creature would be able to see past, present and future alike. Since I mentioned past, present and future, this creature might even be a higher-dimensional being. This simply means it can observe the <a href="https://youtu.be/GwzN5YwMzv0?si=KlrKLN_BsIIcFL1W">(block) universe</a> from any coordinate system, such that it can view what is past or future from our point of view. For this entity, past and future would be as real and coexisting with present as “forward” and “behind” are to us three-dimensional beings. Making even the calculation unnecessary because past present and future exist simultaneously for Him. It could also “interfere” in the system from time to time to tweak the events according to its will.</p>
<p>Its almost scary seeing this vastness. But assuming this creature does have intellect, it would be fair to assume that he has understanding of Game theory, and would want to keep the system stable and would want to induce balance in order to protect it from collapsing.</p>
<p>Interestingly, such a being also wouldn’t need to approximate. If you think about it, physics and chemistry (largely) are approximations of particle and quantum physics, economics is an approximation os individual psychology, medicine is an approximation of molecular and cellular biology. Such segregations won’t exist for this entity. Segregations only exists to make it comprehensible to our limited human perception.</p>
<p>In such a vast universe though, would it matter to this creature whether humans live or die? Maybe its not as vast as we imagined then. Still baffling.</p>
<p>Remember how we talked about a limit to the computational capability of this being. It still would be smart enough to figure out a way of delegating responsibilities and appointing lesser beings to oversee specific domains. Or, if it is advanced enough, it might generate smaller versions of itself to manage different aspects of existence.</p>
<p>Who or what is this higher entity then? Is the universe itself alive? Or is this an entity bigger/higher than the universe itself? Is it Bradley Cooper?</p>
<p>Who is to say? I just created God and every concept associated with Him sitting in my bedroom. Human mind and intellect itself is puzzling.</p>
<p>Maybe God is indeed a thought experiment.<br>
(typical human arrogance)</p>
]]></content>
  </entry>
  
  <entry>
    <title>Living life by Greedy Method</title>
    <link href="https://chns.in/posts/2024-12-11-living-life-by-greedy-method/" />
    <id>https://chns.in/posts/2024-12-11-living-life-by-greedy-method/</id>
    <updated>2024-12-11T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>Greedy method is an algorithmic paradigm used in problems where we need to find an optimal solution. But it does not guarantee an optimal solution.</p>
<p>Wait!</p>
<p>That sounds counterintuitive. And it is.</p>
<p>It is particularly useful in scenarios where there could be multiple optimal solutions or multiple right answers. But since you are greedy, you just go with one — whichever one is best for you.</p>
<p>For example, consider the following scenario:</p>
<p><img src="https://substack-post-media.s3.amazonaws.com/public/images/1a41891f-eac9-4cac-bb0f-3db81f1d2431_1156x610.png" alt=""></p>
<p>You want to find the least possible cost to go from A to B. There are two paths here which give you the least cost — two right answers. But the problem is not to identify the path, but to get the least cost. So you go with any one of them.</p>
<p>Another characteristic of a greedy algorithm is that it makes <strong>locally optimal choices</strong> — assuming it will eventually lead to a <strong>globally optimal result</strong>.</p>
<p>I am fascinated by how much this method applies to real life and how well it applies to our decision making. Of course, in real life, there are lot more input parameters and lot more outputs which you are trying to optimize. You might often use this method without even realizing it. When deciding which exercises to select for your particular goal, when deciding which car you need to buy, in fact, it applies to almost every decision of your life.</p>
<p>It might even yield better results if we apply it consciously. Take task prioritization as an example. (I am going to try this from my next sprint).</p>
<p>Write down the tasks you are assigned and score it according to the benefit that the task offers. This benefit could be personal (since we are being greedy) or for your product or team. Now write the deadline for each task. For each task, you want to maximize the profit, but you also want to pick the task which is more urgent first.</p>
<p>Final Score = Benefit/Deadline</p>
<p>Sort your tasks in descending order of their final score and you have a rough order for taking up these tasks. It’s a <a href="https://en.wikipedia.org/wiki/Knapsack_problem">knapsack problem</a>!</p>
<p>At each point in your life, you make or rather, should make the “locally optimal choice”. And in the end you hope, if you make enough locally optimal choices — you will end up with a globally optimal life!</p>
]]></content>
  </entry>
  
  <entry>
    <title>The only way of dealing with runtime dependencies</title>
    <link href="https://chns.in/posts/2024-12-02-the-only-way-of-dealing-with-runtime-dependencies/" />
    <id>https://chns.in/posts/2024-12-02-the-only-way-of-dealing-with-runtime-dependencies/</id>
    <updated>2024-12-02T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>I've always been drawn to the idea of simplifying complex concepts into core principles. For me, it's like holding onto a &quot;root node&quot; from which I can navigate the many branches of a problem. Recently, I've found a way to generalize one of the trickiest aspects of software design: dependencies—specifically, runtime dependencies. This article shares some insights and strategies for dealing with them effectively. If you've encountered exceptions to these ideas, I'd love to hear about them!</p>
<h4>What Are Runtime Dependencies?</h4>
<p>At their core, runtime dependencies boil down to two categories:</p>
<ol>
<li><strong>Execution Order Dependencies</strong>: When one task must finish before another can start.</li>
<li><strong>Data or Resource Dependencies</strong>: When one task requires data or resources generated by another or when tasks share the same resource(s).</li>
</ol>
<p>These dependencies create challenges that need careful handling to avoid bottlenecks, resource contention, or cascading failures. Let’s dive into some strategies to manage them.</p>
<h3>Decoupling with a Third Component</h3>
<p>Whenever you’re dealing with runtime dependencies, the most reliable solution is to <strong>decouple them using a third component</strong>. This component acts as a mediator and can take various forms, such as a messaging queue, a buffer, or even a simple asynchronous function call.</p>
<p>The decoupling can happen in several ways:</p>
<ul>
<li>The third component might live within one of the dependent components.</li>
<li>It might be an entirely separate entity.</li>
</ul>
<p>Think of any runtime dependency you’ve resolved—chances are, the solution involved some flavor of this approach.</p>
<h3>Three Key Strategies to Handle Dependencies</h3>
<p>Runtime dependencies often manifest as &quot;X needs to happen before Y.&quot; Whether the dependency is about shared data or sequential execution, you generally have three options:</p>
<ol>
<li>
<p><strong>Make Y Wait for X:</strong> This is the simplest approach: Y patiently waits until X is done. While it works fine for systems with low concurrency needs, it’s not scalable. Waiting introduces bottlenecks, slowing down the overall system.</p>
</li>
<li>
<p><strong>Separate Independent Parts of Y:</strong> If only some parts of Y depend on X, isolate the dependent elements. The independent parts of Y can proceed while the dependent ones wait. This minimizes delays and keeps your system moving as efficiently as possible.</p>
</li>
<li>
<p><strong>Precompute Multiple Instances of X:</strong> For systems that need to scale, this strategy is a game-changer. Execute X in advance, store the results, and let Y access them when needed. This approach:</p>
<ol>
<li>Decouples timing between X and Y.</li>
<li>Reduces single points of failure by spreading the workload.</li>
<li>Enables reusability: if multiple instances of Y rely on X, precomputed results avoid redundant processing.</li>
<li>Boosts fault tolerance: if Y fails, X’s results are still intact.</li>
</ol>
</li>
</ol>
<h3>Real-Life Example: A Buffer-Based System</h3>
<p>Here’s how I applied these principles to a real-world problem:</p>
<p>I was working on a service for generating and scheduling automated tests for some declarative Infrastructure-as-Code (IaC) services. These tests had both order and data dependencies. For instance, you can’t unblock something that hasn’t been blocked, and you can’t block something that hasn’t been set up yet.</p>
<h4>Initial Approach: Chained Scheduling</h4>
<p>We initially considered a straightforward approach: chain the test executions together. However, this quickly became problematic:</p>
<ul>
<li><strong>Tight Coupling</strong>: Delays in one test could cascade down the chain, impacting all dependent tests.</li>
<li><strong>Cascading Failures</strong>: If one service failed, it could take other tests down with it.</li>
<li><strong>Resource Contention</strong>: Multiple operations competing for shared resources caused conflicts.</li>
</ul>
<p><img src="https://substack-post-media.s3.amazonaws.com/public/images/0e72ea85-3f9f-42a2-9bf7-5377591a1888_601x764.png" alt=""></p>
<h4>Final Solution: A Buffer-Based System</h4>
<p>The solution was to decouple dependencies using a buffer. Each application had its own &quot;buffer group&quot; with individual queues for each test. This setup allowed tests to execute independently while filling the buffer with data for subsequent operations. If one test failed, its buffer data remained available for retries or debugging.</p>
<p>We opted to persist buffer data in a database rather than a queue. While some queue systems support persistence, databases provided greater flexibility for accessing and managing data in the long term. The principle, however, remains the same: decouple dependencies with a third component.</p>
<p><img src="https://substack-post-media.s3.amazonaws.com/public/images/9dd188f1-1c90-4c1d-ab25-6499d9fffce0_604x791.png" alt=""></p>
<h3>Wrapping Up</h3>
<p>While this article focused on technical solutions, the underlying principle of decoupling dependencies applies beyond software design. Do your part well asynchronously and bring in a mediator where things get out of hands.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Optimization Game - How Far Can You Go?</title>
    <link href="https://chns.in/posts/2024-11-05-optimization-game-how-far-can-you-go/" />
    <id>https://chns.in/posts/2024-11-05-optimization-game-how-far-can-you-go/</id>
    <updated>2024-11-05T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>I came across this question which was asked in some big tech on Leetcode, and thought I’d make a challenge out of it.</p>
<p>How can you optimize the below code?</p>
<pre class="language-java"><code class="language-java"><span class="token class-name">LinkedList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> list<span class="token punctuation">;</span>
<span class="token class-name">Long</span> sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token class-name">Long</span> value <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> 
  sum <span class="token operator">+=</span> value<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<h3>Identifying Initial Bottlenecks</h3>
<p>The first major issue with this code is the use of <code>list.get(i)</code>. Since <code>LinkedList</code> doesn’t support O(1) retrieval by index, each <code>get()</code> call iterates up to that index, making the overall complexity O(n²). To fix this, we can use an <code>Iterator</code> to traverse the <code>LinkedList</code> or switch to an <code>ArrayList</code>, which offers O(1) access time due to its contiguous memory structure.</p>
<p>Another inefficiency is the unnecessary auto-boxing when initializing <code>Long sum = 0</code>, which could be simplified. Let’s address these two points in our first optimization:</p>
<pre class="language-java"><code class="language-java"><span class="token class-name">LinkedList</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Integer</span><span class="token punctuation">></span></span> list<span class="token punctuation">;</span>
<span class="token class-name">Long</span> sum <span class="token operator">=</span> <span class="token number">0L</span><span class="token punctuation">;</span>

<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">Integer</span> value <span class="token operator">:</span> list<span class="token punctuation">)</span> <span class="token punctuation">{</span> 
  sum <span class="token operator">+=</span> value<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Using an enhanced for-loop with an <code>Iterator</code> or switching to an <code>ArrayList</code> ensures O(n) traversal. Additionally, initializing <code>sum</code> with <code>0L</code> avoids any auto-boxing.</p>
<p>At this point, we have an optimized, generic version of the code suitable for most situations. However, for the sake of experimentation, let’s explore further optimizations without focusing on edge cases. This exercise will give us insight into different performance trade-offs, touching on some core Java and computer science concepts along the way.</p>
<h3>Going Further with Arrays</h3>
<p>Instead of a <code>LinkedList</code>, we can use a primitive <code>int</code> array, which offers significant advantages:</p>
<h4>1. Cache Locality</h4>
<p>Arrays in Java are contiguous blocks of memory, which allows the CPU to load large chunks of the array into the cache after the first access. This enables faster access to subsequent elements due to <em>spatial locality</em>. Here’s a simplified memory layout comparison to illustrate:</p>
<pre><code>Address      Contents      | Address      Contents
ffff 0000    data[0]       | ffff 1000    l_data
ffff 0040    data[1]       |   ....
ffff 0080    data[2]       | ffff 3460    l_data-&gt;next
ffff 00c0    data[3]       |   ....
ffff 0100    data[4]       | ffff 8dc0    l_data-&gt;next-&gt;next
                           | ffff 8e00    l_data-&gt;next-&gt;next-&gt;next
                           |   ....
                           | ffff 8f00    l_data-&gt;next-&gt;next-&gt;next-&gt;next
</code></pre>
<p>When we access <code>data[0]</code>, the CPU loads a chunk of memory starting at <code>ffff 0000</code> into the cache, enabling quick access to <code>data[1]</code>, <code>data[2]</code>, and so on. By contrast, a <code>LinkedList</code> has scattered nodes, each pointing to the next. Accessing <code>l_data-&gt;next</code> requires loading a different part of memory, resulting in more cache misses and reduced efficiency.</p>
<h4>2. Dereferencing Overhead</h4>
<p>In a <code>LinkedList</code>, each element resides in a node with a pointer to the next element. This design causes:</p>
<ul>
<li><strong>Indirect Access</strong>: Accessing elements requires traversing pointers, adding multiple memory lookups and slowing access.</li>
<li><strong>Pointer Chasing</strong>: Nodes are scattered in memory, leading to frequent cache misses as the CPU fetches each pointer location separately, increasing access latency.</li>
<li><strong>Memory Overhead</strong>: Each node’s pointer and wrapper object (e.g., <code>Integer</code>) consume extra memory, which reduces cache efficiency and adds garbage collection overhead.</li>
</ul>
<p>In contrast, an <code>int[]</code> array stores values contiguously, allowing direct access without pointers, minimizing cache misses, and avoiding the extra memory and garbage collection costs. This results in faster, more efficient sequential access.</p>
<h3>Utilizing Parallel Streams</h3>
<p>Since we’re using Java, we can leverage parallel streams to speed up summation over large datasets. Here’s a quick overview of how parallel streams operate:</p>
<ol>
<li><strong>Splitting the Data Source</strong>: A parallel stream divides the data source (e.g., a collection or array) into chunks. Each chunk is assigned to a separate thread for concurrent processing, leveraging multiple CPU cores.</li>
<li><strong>Work-Stealing</strong>: Java’s <code>ForkJoinPool</code> powers parallel streams and supports work-stealing. If one thread finishes early, it can take over work from another thread that’s still processing.</li>
</ol>
<p>Parallel streams work best with large data sources, as the overhead of splitting and managing multiple threads is only worth it when there’s significant computation. In this example, we’ll assume a large dataset and implement parallel streams:</p>
<h3>Optimizing Further: Choosing <code>int</code> over long</h3>
<p>Finally, to squeeze out the last bits of performance, we can replace <code>long</code> with <code>int</code>, which has some subtle advantages:</p>
<ul>
<li><strong>Memory Efficiency</strong>: <code>int</code> uses 4 bytes, whereas <code>long</code> uses 8. For large datasets, this reduces memory footprint.</li>
<li><strong>Cache Efficiency</strong>: Smaller data types like <code>int</code> occupy less space in the cache, which may reduce cache misses when handling large arrays.</li>
<li><strong>Avoiding Type Promotion</strong>: In Java, mixing <code>int</code> and <code>long</code> in expressions promotes <code>int</code> values to <code>long</code>, introducing minor overhead. By keeping everything as <code>int</code>, we skip this conversion.</li>
</ul>
<p>With all these optimizations, here’s our final optimized code:</p>
<pre class="language-java"><code class="language-java"><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> array <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">/* your integers */</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> 

<span class="token keyword">int</span> sum <span class="token operator">=</span> <span class="token class-name"><span class="token namespace">java<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span>Arrays</span><span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span>array<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">parallel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sum</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Although the result itself is not of much use, the journey was worth it. We ventured through several fundamental concepts trying to optimize it to the extreme.</p>
<p>I think this is as far as you can go staying in bounds of the language. Can you go any further?</p>
]]></content>
  </entry>
  
  <entry>
    <title>The Only Time It&#39;s Okay to Remake a Classic</title>
    <link href="https://chns.in/posts/2024-11-04-the-only-time-its-okay-to-remake-a-classic/" />
    <id>https://chns.in/posts/2024-11-04-the-only-time-its-okay-to-remake-a-classic/</id>
    <updated>2024-11-04T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>Today, I learned that the <em>Bhool Bhulaiyaa</em> franchise has a new sequel. From what I’ve heard, it rehashes the same script without much novelty. At this point, Bollywood’s reputation for recycling hits is sadly expected.</p>
<p>I’ve come to accept Bollywood as a money-making machine, endlessly recycling anything that becomes a hit. So, when I stumbled upon <em><a href="https://www.youtube.com/watch?v=WpA8vg5PmuQ">Mere Dholna 3.0</a></em>, I was skeptical. While I have no plans to see the film, I decided to give the track a chance—it’s Sonu Nigam, after all.</p>
<p>This version of the song is set in a different context than the original, exploring themes of betrayal and hatred instead of romance. It starts off well, showcasing a classic Sonu Nigam style—though initially, it bears little resemblance to the original. However, as soon as we reach the chorus (which remains the same as in the original), the emotion shifts dramatically, altering the cadence as well. At that point, I could no longer hear Sonu Nigam; it felt more like Kishore Kumar. While Sonu often draws inspiration from legends like Md. Rafi and Kishore Kumar, this interpretation didn’t resonate with me. It made me question Pritam’s choice to feature Sonu Nigam for this version.</p>
<p>Unless…</p>
<p>Around the 2:50 mark, the song switches the raga—which was totally unexpected since I had assumed it would be the same song wrapped in new lyrics. The song transitions to using a minor second, augmented fourth, and minor sixth—the Hungarian minor scale. This darker raga elevates the song’s themes of betrayal and hatred, making it more than just a recycled melody.</p>
<p>This was a beautiful surprise and exactly how a remake should be done. Transitioning from the former, telling a story, and giving people something new (and I don’t mean just a new beat). Art should be anything but lazy.</p>
<iframe width="100%" height="315" src="https://www.youtube-nocookie.com/embed/2dh8C_HHE2g" frameborder="0" allowfullscreen loading="lazy"></iframe>]]></content>
  </entry>
  
  <entry>
    <title>Understanding How Push Notifications Work</title>
    <link href="https://chns.in/posts/2024-10-21-understanding-how-push-notifications-work/" />
    <id>https://chns.in/posts/2024-10-21-understanding-how-push-notifications-work/</id>
    <updated>2024-10-21T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>Push notifications were first introduced by Apple for iOS in 2009. Now we can’t imagine building any application without these.</p>
<p>Any push notification flow is mainly orchestrated using two entities, <strong>push providers and push service</strong>. The complexity of how you manage these notification subscriptions or how the events are queued or handled could vary. But these core concepts remain the same.</p>
<p><img src="https://substack-post-media.s3.amazonaws.com/public/images/6dc6d0e2-6aaa-478a-9de6-7ea457635dae_506x241.png" alt=""></p>
<p>A push notification system involves:</p>
<ul>
<li><strong>Service Worker</strong>: The worker (just a JavaScript file) which runs in the background even when the application is closed, to receive the notifications.</li>
<li><strong>Client</strong>: The application or browser/OS receiving the notification (usage is contextual at least in this article).</li>
<li><strong>Push service</strong>: The service provided by the browser/mobile OS to register clients, receive and authenticate notifications and forward them to the designated client.</li>
<li><strong>Push provider</strong>: The service responsible for receiving a call from your application and sending notification to the push service.</li>
<li><strong>Application Server</strong>: Your application’s backend.</li>
</ul>
<h1><strong>Registering with Push Service</strong></h1>
<p>When an application which intends to send notifications opens up for the first time, it requests permission to send notifications. This does a bunch of things:</p>
<ul>
<li>Grants the application, permissions on browser and OS level to send the notification</li>
<li>The application registers a service worker with the browser which would run in the background even when application is closed, listening for any “push”.</li>
<li>The application also “subscribes” to the push service using PushManager API provided by the browser or mobile OS.</li>
</ul>
<p>(All these have to be handled by the application, of course).</p>
<p>Now app permissions and background workers are topics of their own. For push notifications, we’ll mainly be focusing on the Push Service registration — the last step, which generates a subscription object. This contains an endpoint along with some authentication keys.</p>
<pre class="language-json"><code class="language-json"><span class="token punctuation">{</span>
  endpoint<span class="token operator">:</span> <span class="token string">"https://fcm.googleapis.com/fcm/send/dFDkl2U:APA91bGJ14dMIPffpA118U9P9DZ_zFDF3Ag_Ce8XdGQ78FB8OXX5cmBwr"</span><span class="token punctuation">,</span>
  expirationTime<span class="token operator">:</span> <span class="token null keyword">null</span><span class="token punctuation">,</span>
  keys<span class="token operator">:</span> <span class="token punctuation">{</span>  
    p256dh<span class="token operator">:</span> <span class="token string">"BIa2ZOsi4-hZ2A1VH-gSwTiEQVcR8CxbBeUG0t6...."</span><span class="token punctuation">,</span>
    auth<span class="token operator">:</span> <span class="token string">"Reh74y36Laj9873hCJWyUA"</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>This subscription object is unique to each subscription and needs to be persisted to the application server along with the client/user information to send the notification to this particular client.</p>
<p>Notice the subscription object mentioned above, provides a Firebase Cloud Messaging (FCM) endpoint. This is because this endpoint was requested using Chrome. The push service being used depends on the client(browser/mobile OS). If it was Edge, the endpoint would look something like “<a href="https://wns2-pn1p.notify.windows.com/w/?token=BQYAAADjsm1etl">https://wns2-pn1p.notify.windows.com/w/?token=BQYAAADjsm1etl</a>…”.</p>
<h1><strong>Sending Notification via Push Provider</strong></h1>
<p>Push provider is part of the backend which is responsible to actually send the notifications. It could be a managed service or a custom one. Most cloud platforms offer a push provider service. But if you want more control, or don’t use a cloud provider — you will need to build a custom notification service.</p>
<p>To send the notification, when any notification-worthy event happens, you need to use the subscription object persisted in the previous section.</p>
<p>Libraries like <a href="https://www.npmjs.com/package/web-push">web-push</a> make sending the notifications easier.</p>
<pre class="language-js"><code class="language-js">webpush
<span class="token punctuation">.</span><span class="token function">sendNotification</span><span class="token punctuation">(</span>subscription<span class="token punctuation">,</span> payload<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">"Notification sent successfully:"</span><span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">;</span>
  res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">201</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">message</span><span class="token operator">:</span> <span class="token string">"Notification sent"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>    
  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Error sending notification:"</span><span class="token punctuation">,</span> error<span class="token punctuation">)</span><span class="token punctuation">;</span>  
  res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"Failed to send notification"</span><span class="token punctuation">,</span> <span class="token literal-property property">cause</span><span class="token operator">:</span> error<span class="token punctuation">.</span>body <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Internally, it uses the endpoint from the subscription object to send the request with ‘payload’ as the body.</p>
<p>This request is sent to the push service, which in turn notifies the client. If the client is offline at that moment, the push service stores and queues the notification for some duration (maybe few weeks), and notifies the client once online. The push event is then caught by the service worker — which can handle it or show it as a notification to the user.</p>
<p>There’s one missing piece in what we have talked about up until now — is this secure? Letting anyone subscribe and push to your machine can have dangerous consequences — someone not authorized to send notifications can send you the notification if the person gets hold of the subscription object. That’s where VAPID comes into picture.</p>
<h1><strong>VAPID Keys</strong></h1>
<p>VAPID (<strong>V</strong>oluntary <strong>Ap</strong>plication Server <strong>Id</strong>entification) is used by the push provider to identify and authenticate your application server. You can say it’s your application’s identifier.</p>
<p>From abstract of <a href="https://datatracker.ietf.org/doc/html/draft-ietf-webpush-vapid-01">VAPID specification</a>:</p>
<blockquote>
<p><em>An application server can voluntarily identify itself to a push service using the described technique. This identification information can be used by the push service to attribute requests that are made by the same application server to a single entity. This can used to reduce the secrecy for push subscription URLs by being able to restrict subscriptions to a specific application server. An application server is further able to include additional information that the operator of a push service can use to contact the operator of the application server.</em></p>
</blockquote>
<p>If you’re using a cloud push provider, it should offer you some way to get the VAPID keys associated with your push provider instance. If you are implementing a custom push provider, here’s how to generate VAPID keys:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> webpush <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'web-push'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> vapidKeys <span class="token operator">=</span> webpush<span class="token punctuation">.</span><span class="token function">generateVapidKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'VAPID Public Key:'</span><span class="token punctuation">,</span> vapidKeys<span class="token punctuation">.</span>publicKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'VAPID Private Key:'</span><span class="token punctuation">,</span> vapidKeys<span class="token punctuation">.</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Run this for once and save them (in an environment variable maybe). You can also use some <a href="https://vapidkeys.com/">VAPID key generator service</a>.</p>
<p>These VAPID keys are used at two junctions of the above notification flow:</p>
<ul>
<li><strong>When registering a client with the push provider</strong>: The request for subscription is made with the <strong>VAPID public key</strong>. This lets the push provider know, that the client requesting the subscription is associated with your app — and it stores this information.</li>
</ul>
<pre><code>navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
  serviceWorkerRegistration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array('&lt;Your Public VAPID Key&gt;')
  })
  .then(function(subscription) {
    console.log('Received PushSubscription: ', JSON.stringify(subscription));
    // Send the subscription to your server to save it in your database
  });
});
</code></pre>
<ul>
<li><strong>When sending the push notifications:</strong> The push provider signs the notification being sent to push provider with the VAPID private key. Upon receiving this request, the push provider uses the VAPID public key it already has to verify that the request is actually coming from your application using <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">asymmetric cryptography</a>. It also verifies that your application is authorized to send notification to the specified client.</li>
</ul>
<p>To summarize:</p>
<ul>
<li>Application/push provider generates VAPID keys for itself.</li>
<li>Application requests permission from user to send notifications.</li>
<li>If user accepts:<br>
Application registers a service worker with the browser.<br>
Application requests subscription via PushManager from the Push service of the underlying browser/OS along with the VAPID public key.</li>
<li>Push service stores the VAPID public key corresponding to the client and application information and grants a subscription object with an endpoint to send notifications and some auth keys.</li>
<li>Subscription is persisted on the application’s backend along with the user and client information.</li>
<li>When notification needs to be sent, application notifies a push provider.</li>
<li>The push provider sends request to the “subscription” endpoint persisted on application side. This request is signed with the VAPID private key.</li>
<li>Push service verifies using the VAPID public key it stored earlier that the request is legit and the application is authorized to send notification to the said client.</li>
<li>Push service triggers a “push” event on the service worker.</li>
<li>Service worker handles or displays the notification.</li>
</ul>
]]></content>
  </entry>
  
  <entry>
    <title>JavaScript&#39;s module mess</title>
    <link href="https://chns.in/posts/2024-06-12-javascripts-module-mess/" />
    <id>https://chns.in/posts/2024-06-12-javascripts-module-mess/</id>
    <updated>2024-06-12T00:00:00Z</updated>
    <content type="html"><![CDATA[<p><em>Disclaimer: If you are using bun runtime, then you don’t need to worry about this mess. Can’t comment on other aspects of it since I haven’t used it, but Bun has cleared this mess in its world.</em></p>
<hr>
<p>In the beninging, there were no modules.</p>
<p>Then, BANG! ✨Common JS ✨</p>
<p>Then, BANG! ✨ES Modules ✨</p>
<p>It has been constant bangs since.</p>
<hr>
<p>JavaScript was not intended to be used on such a scale as it is today. For this reason, it had minimal features. One feature which was missing from the language was <em>modules</em>. As the usage and code base of the language grew, fueled by node JS, libraries like Common JS popped up providing this option. The ECMA guys, who release newer JavaScript versions, thought this was a cool idea too, and decided to support it natively starting JavaScript version ES6. This led to a whole bunch of mess and clashes which hasn’t stopped since then.</p>
<p>Here’s why —</p>
<h3>Differences and Incompatibility</h3>
<p>The common JS library uses the <code>require</code> and <code>module.exports</code> syntax for importing and exporting modules, while the ES module system uses <code>import</code> and <code>export</code> syntax.</p>
<p>Common JS importing mechanism is synchronous, while ES modules are imported asynchronously. Which is one reason ECMA didn’t use the already existing <code>require</code> syntax.</p>
<p>The problem, however, arose with the incompatibility of the two. While you can import Common JS modules in an ES project, it doesn’t work the other way round. So, there is an interoperability problem at its core, which cascaded to cause conflicts.</p>
<p>ES did decide to keep itself backwards compatible to allow for gradual adoption of its module system. Keeping ES as default would mean rewriting or transpiling all that code. Keeping CommonJS as default maintains compatibility with existing projects.</p>
<p>What went wrong is —</p>
<ul>
<li>CommonJS not allowing to import ES modules.</li>
<li>Limited browser support for Common JS: Most modern browsers natively support ES Modules, but they have limited to no support for CommonJS module. So if your ES code is going to be used in a browser and it imports a CommonJS module, it will break.</li>
</ul>
<p>The incompatibility goes both ways.</p>
<h3>Co-existence?</h3>
<p>What if both Common JS and ES Modules could co-exist. It would be great for everyone, and JavaScript developers could live happily ever after quarrelling over other conflicts in the JS land.</p>
<p>To date, there are only some workarounds to achieve this in node JS and browsers, but there are still gaps because of the incompatibility.</p>
<p>If you’re shipping a package, you have to be careful. Depending on the consumers of your package, you have to ship the versions or even different versions to cater to both kinds of users.</p>
<p>Shipping different versions is one option. But again, if your consumers somehow end up importing both versions of your package, this could cause <a href="https://github.com/GeoffreyBooth/dual-package-hazard">Dual Package Hazard</a>.</p>
<h3>The One True King</h3>
<p>ESM is the one module system which works everywhere. All node versions with long term support are now using ESM.</p>
<p>If you’re starting with a project, or even have an existing project, it’s time you adopt ES Modules and make the JavaScript world a little less complex place to code.</p>
<h5>WARNING: What you emit is who you really are</h5>
<p>Bundlers offer a sort of workaround for this, by converting all your code to a single module system. But <em>the bundler lies</em>.</p>
<p>You might be thinking that you are using ES module system, but it is possible your bundler is transpiling your modules into common JS.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Proving Tit-for-Tat Works - Mathematically</title>
    <link href="https://chns.in/posts/2024-06-04-proving-tit-for-tat-works-mathematically/" />
    <id>https://chns.in/posts/2024-06-04-proving-tit-for-tat-works-mathematically/</id>
    <updated>2024-06-04T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>Tit for tat is an age-old proverb which we have heard since childhood. But let’s try to mathematically prove its effectiveness.</p>
<h2>Game Theory</h2>
<p>Any scenario where there are two or more “players” with ability to <strong>think</strong> and which involves some payoffs can be treated as a game.</p>
<p>Game theory studies how these players interact with each other. If you loan a bowl of sugar to your neighbor, do they return the favor or exploit your kindness. If the Khaleesi launches a dragon attack on King’s Landing, will King’s Landing retaliate with a nuclear attack? Game theory explores these scenarios.</p>
<p>It's a great way to understand strategy and decision making in all aspects of life, from personal decisions to decisions at national level.</p>
<h2>Prisoner’s Dilemma</h2>
<p>To understand it better, we take a classic thought experiment from game theory called <em>Prisoner’s Dilemma</em>.</p>
<p>This game can be formulated or described in many ways, but the basic structure of the game is that there are two prisoners. Let's call them A and B. Both are accused of some crime. Now, both prisoners are taken into separate rooms and questioned.</p>
<p>Neither A, nor B knows how the other one is going to act. They can choose to cooperate or defect. Based on how they choose to act, the prisoners will get <em>Payoffs.</em></p>
<p>Now the payoff could be reduced sentence for cooperation with the police or defection against the other prisoner. But let’s consider it to be points for simplicity.</p>
<p>Here’s a representation of the game in form of (points for A, points for B) in each scenario:<br>
<img src="https://substack-post-media.s3.amazonaws.com/public/images/017a670b-3106-43ee-ba98-c82e9c63dce8_442x139.png" alt=""></p>
<p>The dilemma for prisoners is to choose whether to defect or to cooperate.</p>
<h2>One-shot</h2>
<p>Of course, the best outcome for both players in this case will be if both choose to cooperate.</p>
<p>But what if the other person defects, in that case you will be stuck with zero points while the other person gets all the rewards.</p>
<p>But if you defect, and the other person cooperates, you get all the reward.</p>
<p>Therefore, no matter what, the dominant Strategy seems to be, to always defect.</p>
<p>If your opponent is rational, they will also reach the same conclusion and always defect.</p>
<p>In such cases, both agents are stuck with the suboptimal reward 1-1, where they could both have gotten 3-3 each if they chose to cooperate.</p>
<h2><strong>Series</strong></h2>
<p>Most real-world problems are not a single prisoner's dilemma. It's a series of such games. If you defect and your opponent cooperates for the first time, your opponent will know you defected last time and that can change their behavior. You have to be long-sighted when deciding your strategy.</p>
<p>In 1980, Robert Axelrod hosted a computer competition to determine this. He pitted multiple computer programs based on different strategies against each other in a series of Prisoner’s Dilemma. Here are some of the participating strategies:</p>
<ul>
<li><em>Friedman</em>: It starts by cooperating, but if its opponent defects even once, it defects for the rest of the game.</li>
<li><em>Joss</em>: It starts by cooperating and copies what its opponent did in the last move. It also sneaks in a defection 10% of the time.</li>
<li><em>Graaskamp</em>: Same as Joss but it defects on the 50% round.</li>
<li><em>Tit-for-tat</em>: Starts by cooperating and copies what its opponent did in the last move. No random or probabilistic defections.</li>
</ul>
<h2>Effectiveness of TFT</h2>
<p>To assess the effectiveness of tit-for-tat, let’s compare it with two extreme strategies of ‘always defect’ and ‘always cooperate’ and against itself.</p>
<p>Let the respective payoffs for always-cooperate, always-defect and tit-for-tat be:</p>
<h5>Case 1: TFT vs. Always Cooperate</h5>
<p>When Tit for Tat plays against a strategy that always cooperates:</p>
<ul>
<li>Both players always cooperate after the first round.</li>
<li>Payoff per round: (3, 3)</li>
<li>Over n rounds:</li>
</ul>
<h5>Case 2: TFT vs. Always Defect</h5>
<p>When Tit for Tat plays against a strategy that always defects:</p>
<ul>
<li>Round 1: TFT cooperates, opponent defects. Payoff: (0, 5)</li>
<li>Subsequent rounds: Both defect. Payoff per round: (1, 1)</li>
<li>Over n rounds:</li>
</ul>
<h5>Case 3: TFT vs. TFT</h5>
<p>When Tit for Tat plays against another Tit for Tat:</p>
<ul>
<li>Both players always cooperate after the first round.</li>
<li>Payoff per round: (3, 3)</li>
<li>Over n rounds:</li>
</ul>
<p>Comparing the averages:<br>
<img src="https://substack-post-media.s3.amazonaws.com/public/images/bce8f8a6-2737-4e57-bc83-ec43f10bdaec_787x360.png" alt=""></p>
<p>Any guesses on which strategy won Axelrod’s competition? Although, it seemed like the craziest thing at the time, the simplest strategy ended up winning, there’s a reason tit-for-tat does so good against all strategies.</p>
<p>Defecting strategies like Always defect, seemed to be good strategy in one-shot to get a payout of 5 over your opponent’s 0. But when both parties always defect, they end up getting lower payout of (1,1), which isn’t good for either of them.</p>
<p>Always cooperate would be great in an ideal world, with both players winning (3,3), but it performs poorly due to the fact that there can be nasty players who will sneak in a defection and even continue to do so if they see cooperation from the other side. Always cooperate is overly nice.</p>
<p>TFT is at the sweet spot, where its nice enough to cooperate first, but it punishes the opponent in the next round if they defect. However, it doesn’t keep defecting, it forgives the opponent as soon as they get back to cooperating and they both get a higher payout of (3,3) instead of (1,1).</p>
<p>Axelrod found that all the best performing strategies shared these qualities:</p>
<ol>
<li><strong>They’re nice:</strong> They start with cooperation, setting a precedence for a positive, cooperative relationship.</li>
<li><strong>They’re provokable</strong>: They mimic the opponent's previous move, directly punishing defection and rewarding cooperation, fostering a stable environment where mutual cooperation can thrive.</li>
<li><strong>They’re forgiving</strong>: They don't hold grudges and only respond to the last move. A player who defects once and then returns to cooperation will be met with cooperation, preventing a downward spiral of mutual defection.</li>
</ol>
<p>There is a misconception that TFT can never do better than its opponent.</p>
<p>By design, TFT can only loose or at best have a draw with its opponent.</p>
<p>On the other hand, the &quot;Always deflect&quot; strategy will always draw or win.</p>
<p>While this is true in a one-on-one game series with a single opponent, most of life is not like that. When there are multiple agents involved with different strategies and different noises at play, the results are totally different. When most people think of games, or in general anything in life, they think someone has to lose in order for them to win. But that’s not the case with Prisoner’s Dilemma and certainly not the case in real life.</p>
<p>In such games, winning is not as important as securing the greatest number of points. Since you are not getting your reward from the other player, but from a third-party. And TFT eventually ends up gaining most points even by just drawing most games. With mutual cooperation we can ensure a win-win outcome for everyone. Tit-for-tat does exactly that.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Fermat&#39;s Last Theorem: A story of endless pursuit</title>
    <link href="https://chns.in/posts/2024-05-19-fermats-last-theorem-a-story-of-endless-pursuit/" />
    <id>https://chns.in/posts/2024-05-19-fermats-last-theorem-a-story-of-endless-pursuit/</id>
    <updated>2024-05-19T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>I have discovered a truly marvelous proof, which this margin is too narrow to contain.</p>
<p>These words were written by the mathematician, Pierre de Fermat, in the margin of a book he was reading, after coming up with a theorem. He died shortly after, and with him, the proof to his theorem, which would remain unsolved for over 350 years!</p>
<p>Pierre de Fermat actually used to work as a judge in 17th Century France and would study Mathematics as a hobby.</p>
<p>One evening, he looked at the famous Pythagoras theorem — x² + y² = z². There are a lot of whole number solutions to this equation.</p>
<p>But what if we change the equation a little. Instead of the squared, what if we changed the exponentials to 3. For example, x³ + y³ = z³.</p>
<p>He couldn’t find any solutions to this. Not only that, but he also believed he had found a proof, that shows that there are no three positive integers a, b, and c that satisfy the equation a^n + b^n = c^n for any integer value of n greater than 2.</p>
<p>And he wrote in the margin of a book he was reading - <a href="https://en.wikipedia.org/wiki/Diophantus_II.VIII">Diophantus's Arithmetica</a> —</p>
<blockquote>
<p>Cuius rei demonstrationem mirabilem sane detexi. Hanc marginis exiguitas non caperet.</p>
</blockquote>
<p>Translation: <em>I have discovered a truly marvelous proof of this, which this margin is too narrow to contain.</em></p>
<p>And then he drops dead.</p>
<p>After his death, his son discovered this book, which was full of such notes. His son published a new version of this book with all his notes. People began looking for these proofs, and in every case where Fermat said he had a proof - there did exist a proof. Except this one secret proof.</p>
<p>It is called Fermat’s Last Theorem because it is the last one which anybody could prove.</p>
<p>But the story has a happy ending.</p>
<p>It starts with a 10-year-old child, Andrew Wiles, who was reading a book one day. The book was The Last Problem by ET Bell. It was all about Fermat’s Last Theorem.</p>
<p>And little Andrew decides he is going to solve that problem. A bright 10-year-old can in fact understand the problem. It's the solution which takes a PhD.</p>
<p>In his late 30s, he comes across Taniyama-Shimura Conjecture (an idea which is not proven), which was proposed in the 50s. Somebody had proven there is a link between this conjecture and the Last theorem. That is, if you could prove the conjecture, it would automatically prove Fermat’s Last Theorem.</p>
<p>Wiles’ childhood passion is reignited. But since, it was such an absurd challenge, Wiles worked on it in complete secrecy for 7 years!</p>
<p>At the end of it, he did find a proof. He became an overnight star in the mathematics world. But someone found a mistake in his proof. Fermat’s Last Theorem was fighting back.</p>
<p>It was a great embarrassment for Wiles.</p>
<p>He worked for another year, with another mathematician, Richard Taylor. Finally, he managed to fix the proof.</p>
<p>Wiles' proof of Fermat's Last Theorem marked a significant milestone in the history of mathematics. It not only solved a centuries-old problem but also demonstrated the power of endless perseverance and the depth of human intellect in unraveling the mysteries of the universe.</p>
<p>Here’s is Andrew Wiles talking about it —</p>
<iframe width="100%" height="315" src="https://www.youtube-nocookie.com/embed/GS7CxAtV5Ks" frameborder="0" allowfullscreen loading="lazy"></iframe>]]></content>
  </entry>
  
  <entry>
    <title>How topological sort is a natural outcome of reversing Postorder DFS</title>
    <link href="https://chns.in/posts/2024-02-15-topological-sort-postorder-dfs/" />
    <id>https://chns.in/posts/2024-02-15-topological-sort-postorder-dfs/</id>
    <updated>2024-02-15T00:00:00Z</updated>
    <content type="html"><![CDATA[<h2>What is topological sort?</h2>
<p>Topological sort for a directed acyclic graph is a way of sorting the graph in an order, where for any edge u -&gt; v, u always comes before v in the final sorting, where u and v are the nodes of the graph.</p>
<p>You can think of the directed acyclic graph as a sequence of tasks or a process, where different nodes are tasks. If a task X has some prerequisites, those prerequisites will point towards X, and if X is a prerequisite for any other tasks, X will point to those tasks.</p>
<h2>Depth First Search, but postorder</h2>
<p>Depth first search as the name suggest takes a node and starts going to its depth visiting nodes along the way. This is preorder traversal, where you visit or process a node as you are moving along the depth.</p>
<p>In postorder DFS, we go to the extreme depth of a node and visit or process a node once we reach the dead-end or the leaf-nodes.</p>
<h2>Its only obvious</h2>
<p>The tasks with no prerequisites are actually these leaf nodes, since they won't be pointing to any other node. When we do a postorder DFS, these leaf nodes come first and we trace back our steps from there. While in a topological sort, these leaf nodes come last. And this order is maintained throughout the traversal. It only seems natural that reversing the result of a postorder search will result in a topological sort.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Data purging: A good use-case for database triggers</title>
    <link href="https://chns.in/posts/2024-01-28-data-purging-db-trigger/" />
    <id>https://chns.in/posts/2024-01-28-data-purging-db-trigger/</id>
    <updated>2024-01-28T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>All organizations have data retention policies which define how long specific types of data should be retained, when it should be deleted, and under what circumstances it can be archived or purged. If yours doesn't, it should.</p>
<p>In my team, it was being implemented via background jobs. This GDPR job would filter out data based on certain status fields and delete them.</p>
<p>There are however certain cases where it doesn't make sense to introduce a 'status' just for data retention. For example, we had a database table created just to store some parameters related to another object. Once that object is deleted by the job based on its status, there was no point keeping the parameters. In such cases, database triggers could come in as a sweet alternative.</p>
<p>Database triggers are a little piece of procedural codes that are automatically executed in response to events occurring within a database. The event can be data manipulation event like insert, delete or update, or it could be a system event like login/logout. The trigger code can perform a variety of actions, such as updating other tables, enforcing business rules, or logging information.</p>
<p>Triggers can be created via some database management tool or via SQL commands.</p>
<p>For example, in this specific scenario -</p>
<pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TRIGGER</span> delete_parameter
<span class="token keyword">AFTER</span> <span class="token keyword">DELETE</span> <span class="token keyword">ON</span> plans
<span class="token keyword">FOR EACH ROW</span>
<span class="token keyword">BEGIN</span>
<span class="token keyword">DELETE</span> customers
<span class="token keyword">WHERE</span> customer_id <span class="token operator">=</span> plans<span class="token punctuation">.</span>customer_id<span class="token punctuation">;</span>
<span class="token keyword">END</span><span class="token punctuation">;</span></code></pre>
<p>Thats it, the entries in the dependent table get deleted as soon as the entries in the main table are deleted.</p>
]]></content>
  </entry>
  
  <entry>
    <title>Why you should almost never use Abstract Classes</title>
    <link href="https://chns.in/posts/2024-01-07-abstract-class/" />
    <id>https://chns.in/posts/2024-01-07-abstract-class/</id>
    <updated>2024-01-07T00:00:00Z</updated>
    <content type="html"><![CDATA[<blockquote>
<p>Imagine rules like this one defining a landscape, where maintainable code lives in the valleys. As you add new behavior, it’s like rain landing on your code. Initially you put it wherever it lands. Then you refactor to allow the forces of good design to push the behavior around until it all ends up in the valleys.</p>
</blockquote>
<p>I once had to design classes for a configuration and resource manager, where there are different kinds of resources or configurations all sharing a similar structure and behavior with a little difference in some fields. But mostly the functionality would be same for all these resources.</p>
<p>Considering the pros, cons and ideal use cases for interfaces and abstract classes; and what was more suitable for this scenario, I went with abstract class as the base for these classes, since these were a group of closely related classes with some behavioral differences.</p>
<p>I was almost through with the implementation, when I came across <a href="https://stackoverflow.com/questions/243274/how-to-unit-test-abstract-classes-extend-with-stubs">this post</a> from Stack overflow (as we all do at some point in our day). Basically, the answers all advised to not use Abstract classes at all. And the answer from Nigel Thorne almost led me to refactor my code. But the same answer also made me wait and let my code age.</p>
<h2>The only two uses of Abstract class</h2>
<p>Abstract classes are used in one-of-two or a combination of the following two ways.</p>
<h3>Abstract Classes for providing flexible behavior for related classes</h3>
<p>If the child classes have a common interface, it means you are using inheritance to change certain behavior. It is usually advisable to prefer Composition over Inheritance.</p>
<p>Thus, you should try strategy pattern for tweaking the behavior.</p>
<h3>Abstract Classes as a helper class</h3>
<p>If it is some common functionality which you want to share among these classes, it is better to move this functionality to a concrete class instead.</p>
<h2>Abstract Class as a Stepping Stone</h2>
<p>Going back to my use-case from the beginning of this blog post. Even though I was using Abstract Classes for providing flexible behavior for related classes and also Abstract Classes as a helper class, a little. If I went with implementing a third class as a utility to be consumed in all these classes, I would have more useless code for initializing these objects and calling the methods instead of the logic. Hence, I decided to go with the Abstract class.</p>
<p>As the code and requirements evolved, I made the base class as concrete and more dynamic and removed the child classes. I now only have to use a child class for very specific scenarios. But that might change too.</p>
<p>Sometimes on this journey towards perfection, bad code can lead to good code.</p>
<h2>A beautiful excerpt from <a href="https://stackoverflow.com/a/2947823/14318926">Nigel Thorne’s answer to How to Unit Test Abstract Classes?</a></h2>
<blockquote>
<p>Update 2: Abstract Classes as a stepping stone (2014/06/12)</p>
<p>I had a situation the other day where I used abstract, so I’d like to explore why.</p>
<p>We have a standard format for our configuration files. This particular tool has 3 configuration files all in that format. I wanted a strongly typed class for each setting file so, through dependency injection, a class could ask for the settings it cared about.</p>
<p>I implemented this by having an abstract base class that knows how to parse the settings files formats and derived classes that exposed those same methods but encapsulated the location of the settings file.</p>
<p>I could have written a “SettingsFileParser” that the 3 classes wrapped, and then delegated through to the base class to expose the data access methods. I chose not to do this yet as it would lead to 3 derived classes with more delegation code in them than anything else.</p>
<p>However, as this code evolves and the consumers of each of these settings classes become clearer. Each settings user will ask for some settings and transform them in some way (as settings are text, they may wrap them in objects of convert them to numbers etc.). As this happens, I will start to extract this logic into data manipulation methods and push them back onto the strongly typed settings classes. This will lead to a higher-level interface for each set of settings, that is eventually no longer aware it’s dealing with ‘settings’.</p>
<p>At this point the strongly typed settings classes will no longer need the “getter” methods that expose the underlying ‘settings’ implementation.</p>
<p>At that point I would no longer want their public interface to include the settings accessor methods; so, I will change this class to encapsulate a settings parser class instead of deriving from it.</p>
<p>The Abstract class is therefore: a way for me to avoid delegation code at the moment, and a marker in the code to remind me to change the design later. I may never get to it, so it may live a good while… only the code can tell.</p>
<p>I find this to be true with any rule… like “no static methods” or “no private methods”. They indicate a smell in the code… and that’s good. It keeps you looking for the abstraction that you have missed… and lets you carry on providing value to your customer in the meantime.</p>
<p>I imagine rules like this one defining a landscape, where maintainable code lives in the valleys. As you add new behavior, it’s like rain landing on your code. Initially you put it wherever it lands. Then you refactor to allow the forces of good design to push the behavior around until it all ends up in the valleys.</p>
</blockquote>
]]></content>
  </entry>
  
  <entry>
    <title>क्या बच गया है ये</title>
    <link href="https://chns.in/posts/2022-01-11-kya-bach-gaya/" />
    <id>https://chns.in/posts/2022-01-11-kya-bach-gaya/</id>
    <updated>2022-01-11T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>जीवन के पंख काट कर<br>
ख़ुशियों के बादल छाँट कर<br>
ग़म की भरी दुपहरी में<br>
मरघट पर बैठी गिलहरी से<br>
जब छीन लिया काजू का टुकड़ा</p>
<p>सपने के शमशान पे बैठा<br>
एक अकेला काँच का पुतला</p>
]]></content>
  </entry>
  
  <entry>
    <title>निशाँ नहीं</title>
    <link href="https://chns.in/posts/2018-07-03-nishaan-nahi/" />
    <id>https://chns.in/posts/2018-07-03-nishaan-nahi/</id>
    <updated>2018-07-03T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>शहर बसता था यहाँ पर कभी,<br>
सुबह-सुबह समंदर आया,<br>
अब कोई निशाँ नहीं</p>
<p>फिर रात भर बारिश हुई,<br>
सुबह सूरज निकल आया,<br>
अब कोई निशाँ नहीं</p>
<p>रात भर रोया <em>बुग्याल</em>,<br>
सुबह यूँ मुस्कुराया,<br>
अब कोई निशाँ नहीं</p>
]]></content>
  </entry>
  
  <entry>
    <title>है बात कुछ और</title>
    <link href="https://chns.in/posts/2018-04-29-hai-baat-kuchh-aur/" />
    <id>https://chns.in/posts/2018-04-29-hai-baat-kuchh-aur/</id>
    <updated>2018-04-29T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>यहाँ बारिश नहीं होती,</p>
<p>है ये बंजर ज़मीन,<br>
यहाँ  बारिश नहीं होती<br>
है ये नफ़रतों में लीन,<br>
हमदर्दी की नुमाइश नहीं होती</p>
<p>रहते हैं बस हिंदू-मुस्लिम, दलित-पंडित, काले-गोरे, मर्द-औरत<br>
यहाँ, अब इंसानों की रिहाइश नहीं होती</p>
<p>यहाँ बारिश नहीं होती</p>
<p>अब माँगें तो क्या-क्या माँगें,<br>
ख़ुदा से करने को अब,<br>
कुछ ख़्वाहिश भी नहीं होती</p>
<p>है बात और कुछ<br>
(कोई समझता नहीं,<br>
और समझने की गुंजाइश भी नहीं होती)</p>
]]></content>
  </entry>
  
  <entry>
    <title>रंग</title>
    <link href="https://chns.in/posts/2018-04-24-rang/" />
    <id>https://chns.in/posts/2018-04-24-rang/</id>
    <updated>2018-04-24T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>रंग देता हूँ कागज़ को,<br>
कभी रंगों से,<br>
कभी ख़यालों से,<br>
कभी सवालों से</p>
<p>बह रही हाथों से रोशनाई,<br>
कई दिनों से,<br>
महीनों से,<br>
कई सालों से</p>
<p>रुकते नहीं,<br>
ये ख़्वाब,<br>
ये ख़याल,<br>
ये सवाल</p>
<p>ना सुझावों से,<br>
ना जवाबों से,<br>
ना अज़ाबों से</p>
]]></content>
  </entry>
  
  <entry>
    <title>Lullaby</title>
    <link href="https://chns.in/posts/2017-06-08-lullaby/" />
    <id>https://chns.in/posts/2017-06-08-lullaby/</id>
    <updated>2017-06-08T00:00:00Z</updated>
    <content type="html"><![CDATA[]]></content>
  </entry>
  
  <entry>
    <title>Light</title>
    <link href="https://chns.in/posts/2017-06-06-light/" />
    <id>https://chns.in/posts/2017-06-06-light/</id>
    <updated>2017-06-06T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>Wrote this piece after watching Deathnote.</p>
]]></content>
  </entry>
  
  <entry>
    <title>कोलाहल</title>
    <link href="https://chns.in/posts/2017-06-05-kolahal/" />
    <id>https://chns.in/posts/2017-06-05-kolahal/</id>
    <updated>2017-06-05T00:00:00Z</updated>
    <content type="html"><![CDATA[]]></content>
  </entry>
  
  <entry>
    <title>Sikkim</title>
    <link href="https://chns.in/posts/2017-06-05-sikkim/" />
    <id>https://chns.in/posts/2017-06-05-sikkim/</id>
    <updated>2017-06-05T00:00:00Z</updated>
    <content type="html"><![CDATA[]]></content>
  </entry>
  
  <entry>
    <title>Warzone</title>
    <link href="https://chns.in/posts/2016-03-31-warzone/" />
    <id>https://chns.in/posts/2016-03-31-warzone/</id>
    <updated>2016-03-31T00:00:00Z</updated>
    <content type="html"><![CDATA[<p>Don't wait for me to explain it to you</p>
<p>'cause I am not going to</p>
<p>No, I don't need you</p>
<p>to judge me for what I did</p>
<p>'cause you can't see the war from my side</p>
<p>the wars fought on the inside</p>
<p>the wars that I've fought alone for years</p>
<p>with the monsters in my head</p>
<p>the nights I've spent, shedding tears</p>
<p>shivering on my bed</p>
<p>I might be evil personified</p>
<p>on your side of the border</p>
<p>But I'm an angel on my side</p>
<p>bringing peace and order</p>
<p>Look inside my kaleidoscope</p>
<p>see the colours that hide</p>
<p>behind its dirty lens</p>
<p>come, let me show you your dark side</p>
<p>a dove with a gun in its nib</p>
<p>flying on the other side of the fence</p>
<p>When you're far</p>
<p>and I don't care</p>
<p>Notice how free I feel</p>
<p>when I talk,</p>
<p>Do you see freedom,</p>
<p>in the way I walk?</p>
<p>But everytime I attack, a child dies</p>
<p>can't see another mother with tears in her eyes</p>
<p>So go away! Don't make it bad for me</p>
<p>lets just part ways</p>
<p>let my people live in peace</p>
<p>I'm going back to my land</p>
<p>out of this warzone</p>
<p>where people sleep</p>
<p>where people don't feel alone</p>
<p>I'll now leave you behind, spread my wings to fly</p>
<p>and let this be my final good bye.</p>
]]></content>
  </entry>
  
</feed>
