<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Fandy Aditya Dev Blog]]></title><description><![CDATA[Insights and Tutorials on Software Development by Fandy Aditya: Join Me on My Learning Journey]]></description><link>https://dev.fandyaditya.com</link><generator>RSS for Node</generator><lastBuildDate>Sun, 13 Sep 2026 21:12:13 GMT</lastBuildDate><atom:link href="https://dev.fandyaditya.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Create Canva Design From Hashnode Blog at Ease]]></title><description><![CDATA[Introduction
The motivation for creating Hashnode Canva Integration is bringing Hashnode Article to wider audiences by converting it to a different format. I think many of you sometimes see PowerPoint-like content on social media like Instagram, TikT...]]></description><link>https://dev.fandyaditya.com/create-canva-design-from-hashnode-blog-at-ease</link><guid isPermaLink="true">https://dev.fandyaditya.com/create-canva-design-from-hashnode-blog-at-ease</guid><category><![CDATA[APIHackathon]]></category><category><![CDATA[API Hackathon]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Fri, 02 Feb 2024 14:41:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706885061186/d0fb40d7-8415-4cfa-bfe3-28a9b222eacc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>The motivation for creating Hashnode Canva Integration is bringing Hashnode Article to wider audiences by converting it to a different format. I think many of you sometimes see PowerPoint-like content on social media like Instagram, TikTok, or LinkedIn. Something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706859012660/87840e55-78d9-4d51-afcf-45c5484ddc01.gif" alt class="image--center mx-auto" /></p>
<p>You have been working hard on publishing a good article here. Why stop there? Bring your Hashnode's article to more audiences by start distributing it as Microblog or Digital Booklet format.</p>
<p>Introducing: Hashnode Canva Integration</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/_qGBmosH9iQ">https://youtu.be/_qGBmosH9iQ</a></div>
<p> </p>
<h1 id="heading-why-canva">Why Canva</h1>
<p>Canva is one of many graphic design platforms, if not the best one right now. The easy-to-use, to-the-point, rich template and elements make Canva good for every use case. I believe we at least used Canva once to create cover image of our article</p>
<p>Integration capabilities of Canva, and then GraphQL API from Hashnode, make this possibility become reality. And here we are, Hashnode Canva Integraion is Born.</p>
<h1 id="heading-on-development">On Development</h1>
<p>I want to make this as simple as possible but keep the functionality intact. The main function is to get article data that can be added as an element to Canva Design. The final application flow is like this:</p>
<ol>
<li><p>Get data from the URL using Hashnode's GraphQL API</p>
</li>
<li><p>Mapping the content</p>
</li>
<li><p>Drag to Canvas as Design Element</p>
</li>
<li><p>Done!</p>
</li>
</ol>
<p>Simple.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706799288558/0baf0fc7-2539-4c6f-a9f4-264947a6caee.gif" alt class="image--center mx-auto" /></p>
<p>From GraphQL API we can get the content on <code>html</code> <code>markdown</code> or <code>text format</code>. I choose <code>markdown</code> because it is easier to parse. Also, many markdown parsers out there are ready to use.</p>
<p>Canva has <a target="_blank" href="https://www.canva.dev/docs/apps/">SDK</a> that makes it easy to develop on their side. Turning the article content into Canva Element is quite straightforward using it.</p>
<p>The apps extract the content of the article and group them into 5 separate tabs that you can use as elements on canvas design. The tabs are:</p>
<ul>
<li><p>Meta</p>
<p>  This tab contains metadata such as:</p>
<ul>
<li><p>Background Image</p>
</li>
<li><p>Author Name</p>
</li>
<li><p>Author Profile Picture</p>
</li>
<li><p>Published Date</p>
</li>
<li><p>Title</p>
</li>
<li><p>Subtitle</p>
</li>
</ul>
</li>
<li><p>Content</p>
<p>  This tab contains the content of the article. Including the header, text content, code, and images.</p>
</li>
<li><p>Links</p>
<p>  This tab contains links that appear on the article</p>
</li>
<li><p>Image</p>
<p>  This tab contains all images in the article</p>
</li>
<li><p>Code</p>
<p>  This tab contains codes that appear in the article</p>
</li>
</ul>
<p>Every element of the tab can be used as an element of Canva design.</p>
<h1 id="heading-obstacle">Obstacle</h1>
<p>The actual obstacle is the time limit. 15 Days. With full-day working on weekdays, so I need a little sacrifice on my sleep. Also, publishing new Integration to Canva takes a <a target="_blank" href="https://www.canva.dev/docs/apps/submission-checklist/">long process of submission</a>. This app is still in review and hopefully, it can accepted as soon as possible.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706859183929/0eaef1a5-17b7-4972-9f12-d64221b42330.png" alt="Still in review" class="image--center mx-auto" /></p>
<p>I will update the status of the Apps here. So Stay Tune!</p>
<h1 id="heading-in-closing">In Closing</h1>
<p>As Always, Thank you to Hashnode for organizing the hackathon! Makes my coding habit still going, and bringing little impact here and there.</p>
<p>Hackathon is a great source of push if you want to build something but always procrastinate. Given timebox, our mind will try to find the most efficient to finish what we started. Driving by incentives, and your motivation is hard to deplet. Give it a try.</p>
<p>Thank you for reading!</p>
<p>#APIHackaton</p>
]]></content:encoded></item><item><title><![CDATA[YoutubeAsk: Ask Any YouTube Video]]></title><description><![CDATA[For the past few months, I have already had in mind, what if we can ask a video. Instead of watching for hours, we can ask about only certain topics that we are interested in. We are in the podcast era. There are tons of 3-hour-long videos and of cou...]]></description><link>https://dev.fandyaditya.com/youtubeask-ask-any-youtube-video</link><guid isPermaLink="true">https://dev.fandyaditya.com/youtubeask-ask-any-youtube-video</guid><category><![CDATA[mindsdb]]></category><category><![CDATA[MindsDBHackathon]]></category><category><![CDATA[#MindsDB #MindsDBHackathon]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Mon, 15 Jan 2024 02:57:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705249961297/0f861343-dc70-4b10-87d2-e6c8545fe65e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For the past few months, I have already had in mind, what if we can ask a video. Instead of watching for hours, we can ask about only certain topics that we are interested in. We are in the podcast era. There are tons of 3-hour-long videos and of course, it needs dedicated time to watch the full episode. So, asking about the content of the video, and going deeper on the topic that we truly want to know is an efficient way to gain knowledge or information. With <a target="_blank" href="https://mindsdb.com/">Hackaton by MindsDB</a>, this is the chance to actually build something cool. So here it is: YoutubeAsk. Telegram Bot to ask any YouTube video.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtube.com/shorts/7Tz-mpMWKHk?si=2b45YYAjkfMygK00">https://youtube.com/shorts/7Tz-mpMWKHk?si=2b45YYAjkfMygK00</a></div>
<p> </p>
<h1 id="heading-about-mindsdb-hackathon">About MindsDB Hackathon</h1>
<p>I've been following MindsDB since the last hackathon on April 2023. But, when at that time, I underestimated the true challenge of the hackathon. The true challenge is to finish. Because every time I see, hundreds or thousands that registered, only few that submit. So, I didn't want to miss it again, this hackathon is a chance to actually build and ship.</p>
<p>What I expect from this hackathon is two things. Building the idea that I already have in mind, and exploring &amp; contributing to the hottest topic of the year: Generative AI, and the tools that support to make it easy and possible.</p>
<h1 id="heading-mindsdb-the-experience">MindsDB, The Experience</h1>
<p><a target="_blank" href="https://mindsdb.com/">MindsDB</a> is an open source, AI Engine, that makes it easy to build AI tools. With hundreds of databases, and apps integration ready to use using SQL - Like Syntax and HTTP Rest APIs. In today's era, where AI is the hottest topic you can't miss MindsDB. Making AI Tools is easy because you can leave the heavy work to MindsDB. Also, the team and communities are so active in adding new integrations every day.</p>
<p>The experience is new. This is my first time exploring deeply an open source and actually understand what I read haha. Finding something that is not documented, finding a gem, a new way to do something that actually possible by only reading the code and trial-error so we can get. This is really new experience.</p>
<p>And of course, MindsDB makes the heavy part of building the YoutubeAsk easy. Almost 70% of the work is done with the MindsDB feature and integration. What I did was glue it all together and build things. The possibilities are endless. Like, you can create thousands of combinations of apps and functionalities, only with MindsDB. Pretty cool!</p>
<p>My flow when working on this project is like this:</p>
<ul>
<li><p>Read the documentation on the web.</p>
</li>
<li><p>If not find what I want, go to the source code, to the integration handler module</p>
</li>
<li><p>Read README on the integration handler</p>
</li>
<li><p>If not satisfied, read the unit test</p>
</li>
<li><p>If not available, directly read the handler code.</p>
</li>
</ul>
<p>The deeper I find, the higher the satisfaction of finding something. Like finding a hidden gem.</p>
<h1 id="heading-prerequisite">Prerequisite</h1>
<p>MinsdDB is the core of YoutubeAsk. But there are some other tools needed.</p>
<h2 id="heading-other-tools">Other Tools</h2>
<ul>
<li><p><strong>Express JS</strong></p>
<p>  This project uses Express JS as a simple web server.</p>
</li>
<li><p><strong>Telegram bot</strong></p>
<p>  Why Telegram Bot?</p>
<ol>
<li><p>I use Telegram,</p>
</li>
<li><p>I think Telegram has good bot APIs</p>
</li>
</ol>
</li>
</ul>
<p>    Create a Telegram Bot first. What we need is:</p>
<ul>
<li><p>Register via Bot Father</p>
</li>
<li><p>Save the bot token</p>
</li>
</ul>
<p>    Follow this <a target="_blank" href="https://core.telegram.org/bots/tutorial">tutorial</a> on how to register a telegram bot.</p>
<ul>
<li><p><strong>Ngrok</strong></p>
<p>  For development, we need to register webhook to telegram bot APIs, so telegram knows, where it "forwards" the chat message. We can get what chat data is sent to the bot, and process it in our server.</p>
<p>  In the local machine, we need some kind of tunneling. After a little research, I found this tool: <a target="_blank" href="https://ngrok.com/">Ngrok</a>.</p>
<p>  With ngrok, we can create a secure tunnel from my local laptop to the internet. With this, I can register my local development server as the webhook to the telegram bot.</p>
<p>  Follow the instructions <a target="_blank" href="https://ngrok.com/docs/getting-started/">here</a></p>
</li>
</ul>
<h2 id="heading-mindsdb"><strong>MindsDB</strong></h2>
<p>MindsDB will be the core of the apps that we want to make. MindsDB currently can create a Knowledge Base. It's like (Correct Me If I'm wrong Lol) AI storage that can answer any Natural Language query based on the knowledge that is inserted to them. So this is the core of the YoutubeAsk. We need a Knowledge Base based on video YouTube content that asked/registered. Next, where i can get the YouTube data to give feed to the Knowledge Base? Magically, Mindsdb is already have the integrations!</p>
<h3 id="heading-youtube-integration"><strong>Youtube Integration</strong></h3>
<p>MindsDB have YouTube API Integrations that already can get comments, video info, and channel info based on the id given. For YoutubeAsk, what I want to get is video info, to feed the knowledgebase. Gladly MindDB already has it. So convenient!</p>
<p>Oh, also you need a YouTube API key. Follow this <a target="_blank" href="https://blog.hubspot.com/website/how-to-get-youtube-api-key">tutorial</a></p>
<h3 id="heading-vector-databases"><strong>Vector Databases</strong></h3>
<p>Vector Databases is the core of Knowledge Base. Because every data inserted, is inserted as vector, and queried as matching vector to understand the queries and find the related content. This process called Semantic Search. Knowledge Base wrap this into APIs that easy to use. So we just like query to some SQL databases. There's some vector databases that can be integrated to MindDB. I choose the familiar one: ChromaDB.</p>
<p>OpenAI Integration is used in model on this project. Go to <a target="_blank" href="https://platform.openai.com/">OpenAI</a> to get one.</p>
<h3 id="heading-installing-mindsdb-on-a-local-machine"><strong>Installing MindsDB on a Local Machine</strong></h3>
<p>I'm quite familiar with docker. So when there are Docker images of MindsDB, I just use it on my local machine. But the original images do not come with the dependency of YouTube and Chromadb integration, so I need to do a little tweak. Creating custom Dockerfile, with MindsDB images as bases and adding extra dependencies:</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Use the mindsdb/mindsdb base image</span>
<span class="hljs-keyword">FROM</span> mindsdb/mindsdb

<span class="hljs-comment"># install mindsdb youtube plugin</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install mindsdb[youtube]</span>

<span class="hljs-comment"># install mindsdb chromadb plugin</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install mindsdb[chromadb]</span>
<span class="hljs-comment"># RUN pip install pysqlite3-binary</span>

<span class="hljs-comment"># This create folder is needed for chromadb integration can run</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p /usr/<span class="hljs-built_in">local</span>/lib/python3.10/site-packages/google/colab</span>

<span class="hljs-comment"># Set the entry point with the installation and MindsDB command</span>
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"sh"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"python -m mindsdb --config=/root/mindsdb_config.json --api=http,mysql,mongodb"</span>]</span>
</code></pre>
<p>Yes, installing YouTube and ChromaDB Mindsdb integration plugin. There some <code>mkdir</code> process, that will explained on the next section.</p>
<p>To build the images, run:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Build docker</span>
docker build -t my_mindsdb .
</code></pre>
<p>Run the images</p>
<pre><code class="lang-bash">docker run -p 47334:47334 -p 47335:47335 my_mindsdb
</code></pre>
<p>Go to browser and access <a target="_blank" href="http://localhost:47734"><code>localhost:47734</code></a>, it will take some time after docker container successfully runs. You will see Mindsdb Admin UI.</p>
<p>For more info on docker installation, go <a target="_blank" href="https://docs.mindsdb.com/setup/self-hosted/docker">here</a>.</p>
<p>Next, we go to the development.</p>
<h1 id="heading-development">Development</h1>
<p>I want to make it pretty basic and simple, yet deliver. So the bot becomes like this:</p>
<ul>
<li><p><code>/ask [youtube-link]</code>: to set the video that you want to ask</p>
</li>
<li><p><code>/session</code>: to check the current video</p>
</li>
</ul>
<p>After ready then start asking like regular chat. Simple.</p>
<h2 id="heading-how-the-apps-work">How the apps work</h2>
<p>With the help of MindsDB, building something like this is pretty straightforward. Here is the flow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705250885159/910a3712-e42d-4d4e-9e06-70dbbfd4a589.jpeg" alt class="image--center mx-auto" /></p>
<p>Let's dive into it</p>
<ul>
<li><p><strong>Get Youtube Transcript</strong></p>
<p>  First, we need to initialize YouTube integration.</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> youtubedb
  <span class="hljs-keyword">WITH</span> <span class="hljs-keyword">ENGINE</span> = <span class="hljs-string">'youtube'</span>,
  <span class="hljs-keyword">PARAMETERS</span> = {
    <span class="hljs-string">"youtube_api_token"</span>: <span class="hljs-string">"AIzxxx"</span> <span class="hljs-comment">--  your youtube api token</span>
  };
</code></pre>
<p>  Then to get the video transcript, like this:</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SELECT</span> transcript <span class="hljs-keyword">FROM</span> youtubedb.videos <span class="hljs-keyword">WHERE</span> video_id = <span class="hljs-string">'${id}'</span>
</code></pre>
<p>  Done! We get the transcript. The data are like this:</p>
<pre><code class="lang-json">  [
      {
          <span class="hljs-attr">"text"</span>: <span class="hljs-string">"This is some transcript"</span>
          <span class="hljs-string">"start"</span>: <span class="hljs-number">1.0</span>,
          <span class="hljs-attr">"duration"</span>: <span class="hljs-number">3.6</span>,
      },
      {
          <span class="hljs-attr">"text"</span>: <span class="hljs-string">"This is another transcript"</span>
          <span class="hljs-string">"start"</span>: <span class="hljs-number">4.7</span>,
          <span class="hljs-attr">"duration"</span>: <span class="hljs-number">3.9</span>,
      },
      ...
  ]
</code></pre>
</li>
<li><p><strong>Chunk the transcript</strong></p>
<p>  We need to chunk the transcript so we can search it. Because it is already on a JSON array, I chunk it based on 10 items. Every item is an average of 5 seconds clip. So if we chunk every 10 items, we get ~50 seconds of clip for each chunk.</p>
</li>
<li><p><strong>Insert to Knowledge Base</strong></p>
<p>  Before we can insert into it, we need some preparation. Create the ML Engine, Model, and Knowledge Base:</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-comment">--Setup ml engine</span>
<span class="hljs-keyword">CREATE</span> ML_ENGINE embedding <span class="hljs-keyword">FROM</span> langchain_embedding;

<span class="hljs-keyword">CREATE</span> ML_ENGINE openai_engine
<span class="hljs-keyword">FROM</span> openai
<span class="hljs-keyword">USING</span>
    api_key = <span class="hljs-string">'sk-xxxx'</span>; <span class="hljs-comment">-- your openai api key</span>


<span class="hljs-comment">--Create embedding model</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MODEL</span> openai_embedding_model
PREDICT embeddings
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">engine</span> = <span class="hljs-string">"embedding"</span>,
    <span class="hljs-keyword">class</span> = <span class="hljs-string">"openai"</span>,
    api_key = <span class="hljs-string">"sk-xxxx"</span>, <span class="hljs-comment">-- your openai api key</span>
    input_columns = [<span class="hljs-string">"content"</span>];


<span class="hljs-comment">-- Create qna model using openai</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MODEL</span> q_n_a_model
PREDICT answer
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">engine</span> = <span class="hljs-string">'openai_engine'</span>,
    prompt_template = <span class="hljs-string">'Use the following pieces of video context to answer the question at the end. If you do not know the answer, just say that you do not know, do not try to make up an answer. Dont answer outside of the context given.
                        Video context: {{context}}
                        Question: {{question}}
                        Helpful Answer:'</span>,
    model_name= <span class="hljs-string">'gpt-3.5-turbo-16k'</span>,
    <span class="hljs-keyword">mode</span> = <span class="hljs-string">'default'</span>,
    max_tokens = <span class="hljs-number">1500</span>;

<span class="hljs-comment">-- Connect to Chroma</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> chromadb
<span class="hljs-keyword">WITH</span> <span class="hljs-keyword">ENGINE</span> = <span class="hljs-string">"chromadb"</span>,
<span class="hljs-keyword">PARAMETERS</span> = {
    <span class="hljs-string">"persist_directory"</span>: <span class="hljs-string">"chromadb/"</span>
};

<span class="hljs-comment">-- create youtube_ask table, we need to spesify init dummy data so chromadb collection can be created</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> chromadb.youtube_ask (
  <span class="hljs-keyword">select</span> <span class="hljs-keyword">content</span>, <span class="hljs-string">'{"data": "init data"}'</span> <span class="hljs-keyword">as</span> metadata, embeddings 
  <span class="hljs-keyword">from</span> openai_embedding_model
  <span class="hljs-keyword">where</span> <span class="hljs-keyword">content</span> = <span class="hljs-string">'init content'</span>
);


<span class="hljs-comment">--Create knowledge base</span>
<span class="hljs-keyword">CREATE</span> KNOWLEDGE_BASE youtube_ask_kb
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">model</span> = openai_embedding_model,
    <span class="hljs-keyword">storage</span> = chromadb.youtube_ask;
</code></pre>
<ul>
<li><p>I followed <a target="_blank" href="https://docs.mindsdb.com/agents/knowledge-bases">Knowledge Base</a> and <a target="_blank" href="https://docs.mindsdb.com/nlp/question-answering-inside-mysql-with-openai">Q&amp;A</a> documentation for preparing The Knowledge Base, and Q&amp;A model, with a little tweak:</p>
<ul>
<li><p>Changing Embedding Model using OpenAI</p>
</li>
<li><p>Add parameter <code>model_name</code> of Q&amp;A Model to <code>gpt-3.5-turbo-16k</code>, <code>max_tokens</code> to <code>1500</code> and <code>mode</code> to <code>default</code> . Changing model name to that, so we can send 16k token context to the OpenAI. Max token to 1500 so the response is not truncated. And it can all work, when I use mode=default.</p>
</li>
</ul>
</li>
</ul>
<p>    Now we can insert to the knowledge base using:</p>
<pre><code class="lang-sql">    <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> youtube_ask_kb (<span class="hljs-keyword">content</span>, metadata) 
    <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'chunked content'</span>, <span class="hljs-string">'{"link": "http://you.tube/link"}'</span>)
</code></pre>
<p>    With <code>content</code> is the chunked content, and the <code>metadata.link</code> is the YouTube video URL. We need the link so we can query the context based on the video given.</p>
<ul>
<li><p><strong>Query the knowledge base</strong></p>
<p>  Querying knowledge base is pretty straightforward:</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">content</span> 
  <span class="hljs-keyword">FROM</span> youtube_ask_kb 
  <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">content</span> = <span class="hljs-string">'Question from user'</span> 
  <span class="hljs-keyword">AND</span> youtube_ask_kb.metadata.link = <span class="hljs-string">'http://you.tube/link'</span>
</code></pre>
</li>
<li><p><strong>Give the queried result to</strong> <code>q_n_a_model</code></p>
<p>  Now with data that we get from Knowledge Base, we want to feed LLM so it can give a natural response to the users. Based on the question, and context from the knowledge base data.</p>
<pre><code class="lang-sql">  <span class="hljs-keyword">SELECT</span> answer 
  <span class="hljs-keyword">FROM</span> q_n_a_model 
  <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">context</span> = <span class="hljs-string">'Data from queried knowledge base'</span> 
  <span class="hljs-keyword">AND</span> question = <span class="hljs-string">'Question from user'</span>
</code></pre>
<p>  We will get something like this:</p>
<blockquote>
<p>Q: What kind of technique the video suggest to mapping out my week?</p>
<p>A: The video suggests the technique of writing down every single thing you are going to do for the next week. This includes your morning routine, focused work routine, other tasks and meetings, and nightly routine. By writing everything down, you can reduce the friction of making better decisions and spot problems in your days. The video also mentions the importance of experimenting and iterating on your plan week after week to refine your system.</p>
</blockquote>
<p>  Nice!</p>
</li>
</ul>
<h1 id="heading-when-things-didnt-go-as-planned">When Things Didn't Go as Planned</h1>
<p>I can guarantee that when building things, there are some something that didn't go as planned. For this project, here they are.</p>
<h2 id="heading-chromadb-installation">ChromaDB Installation</h2>
<p>Something happened on ChromaDB Installation. After some research, I found the issue. The default Sqlite from Python 3.10 is not compatible with the ChromaDB. So it needs to be installed separately.</p>
<p>I found <a target="_blank" href="https://gist.github.com/defulmere/8b9695e415a44271061cc8e272f3c300">this</a> post, and scrolling to the comment, and solved it like this:</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment">#Dockerfile</span>

<span class="hljs-comment"># This create folder is needed for chromadb integration can run</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p /usr/<span class="hljs-built_in">local</span>/lib/python3.10/site-packages/google/colab</span>
</code></pre>
<p>By creating an empty Google Collab folder via Dockerfile, it downloaded separate Sqlite that is needed by the ChromaDB.</p>
<h2 id="heading-qampa-model">Q&amp;A Model</h2>
<p>The problem with Knowledge Base is when we query something general like: <code>What is the video about?</code> it can't get any data because it doesn't get any matching criteria for that kind of question. So, my fix is, just send all of the transcript to Q&amp;A Model and, let it consume all of the content of the video, and answer based on that.</p>
<p>The second problem occurs. When we use the default configuration for the Q&amp;A Model, it only supports 4k context windows, because it uses <code>text-davinci</code> OpenAI model. When we just send all of the transcript to the model, it error because pass over 4k context. We need to change the model. Because it isn't documented anywhere, I need to dive into the code to find if this is possible or not. Of course, it is possible, and changing to a model <code>gpt-3.5-turbo-16k</code> makes it work. Handling 20~ pages of context is possible now.</p>
<h1 id="heading-in-closing">In Closing</h1>
<p>Participating in this hackathon gave me new experience and knowledge. Hackaton gives a sense of urgency, with a driving incentive that gives me kind of motivation to explore new things, and actually build an idea that has been urged to be checked on my idea notes. Exploring new tools to create something awesome. Building something becomes easier and easier nowadays. The same applies to MindsDB, creating AI Apps so much easier and faster.</p>
<p>Oh, and also contributing to open source. This is my first time creating PR for Open Source.</p>
<ul>
<li><p>Update Documentation of <a target="_blank" href="https://github.com/mindsdb/mindsdb/pull/8621">YouTube Integration</a></p>
</li>
<li><p>Update Documentation of <a target="_blank" href="https://github.com/mindsdb/mindsdb/pull/8625">OpenAI Integration</a></p>
</li>
</ul>
<p>Well, maybe that's not much hahaha. But it's ok, extra documentation may save hours for others.</p>
<p>Thank you to MindsDB and <a target="_blank" href="https://hashnode.com/">Hashnode</a> for the amazing hackathon. Can't wait for the next hackathon and bringing the next idea a life!</p>
<p>You can find GitHub repo <a target="_blank" href="https://github.com/fandyaditya/youtube-ask">here</a></p>
<p>Also thank you for reading! Have a nice day!</p>
<p><a target="_blank" href="https://hashnode.com/n/MindsDB">#MindsDB</a> <a target="_blank" href="https://hashnode.com/n/mindsdbhackathon">#MindsDBHackathon</a></p>
]]></content:encoded></item><item><title><![CDATA[Privacy in Generated AI Era: redactd powered by Pangea Cloud]]></title><description><![CDATA[Introduction
Generated AI is one of the latest tech. The progress is swift, and it actually helpful for many jobs in every aspect of people's lives. The most groundbreaking tech discovery after spreadsheet :p. Everyone uses it.
LLM like ChatGpt, trai...]]></description><link>https://dev.fandyaditya.com/privacy-in-generated-ai-era-redactd-powered-by-pangea-cloud</link><guid isPermaLink="true">https://dev.fandyaditya.com/privacy-in-generated-ai-era-redactd-powered-by-pangea-cloud</guid><category><![CDATA[pangea]]></category><category><![CDATA[PangeaSecurathon]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Wed, 15 Nov 2023 13:52:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1700056320666/9eb3118e-a3db-4cc8-83d2-05e83d68b1b6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Generated AI is one of the latest tech. The progress is swift, and it actually helpful for many jobs in every aspect of people's lives. The most groundbreaking tech discovery after spreadsheet :p. Everyone uses it.</p>
<p>LLM like ChatGpt, trains using data on the Internet. But what if it also learns from the data prompt that we give them? And if that is true, sending sensitive data to it is not good because our sensitive data can be exploited, something that we don't want. Please don't risk it.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/immasiddtweets/status/1669721470006857729?lang=en">https://twitter.com/immasiddtweets/status/1669721470006857729?lang=en</a></div>
<p> </p>
<p>Maybe sending sensitive data to third parties is not really a problem for people from different backgrounds and demographics, because they need another important problem to take care of.</p>
<p>"We are on the Internet! No data is really private!"</p>
<p>But if we can slightly prevent it, by ourselves, why not?</p>
<p>The simplest way we can prevent giving any sensitive data to LLMs is to hide the specific sensitive data. Or on another term, <strong>redact</strong> it.</p>
<p>Yes, redact the sensitive information from the provided prompt, and un-redact the LLMs response to get the actual response back.</p>
<p>Just like that, I think the simplest way. And the simplest way to develop that is to use <a target="_blank" href="https://pangea.cloud">Pangea Cloud</a></p>
<h1 id="heading-what-is-pangea-cloud">What is Pangea Cloud</h1>
<p>Couple weeks ago, I discovered Pangea Cloud. Based on the description on their landing page:</p>
<blockquote>
<h1 id="heading-security-services-for-developers"><strong>Security Services for Developers</strong></h1>
<p>Get the APIs you need to deliver a secure customer experience, all in one place!</p>
</blockquote>
<p>Yes, it is security as a service, so we, developers can deliver apps with high-quality security out of the box.</p>
<p>Based on that, now we can create simple web apps to redact prompts with the Redact API provided by Pangea Cloud.</p>
<p>Introducing: <a target="_blank" href="https://redactd.vercel.app/">redactd</a></p>
<p><a target="_blank" href="https://redactd.vercel.app/">redactd</a> works really simple, like diagram below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700031922957/1e243660-fd67-4dbc-8555-734f2c576c97.jpeg" alt class="image--center mx-auto" /></p>
<ol>
<li><p>Redact the prompt using Pangea Redact API</p>
</li>
<li><p>Got redacted prompt</p>
</li>
<li><p>Copy-paste the redacted prompt to LLM</p>
</li>
<li><p>Got response</p>
<ol>
<li><p>If the response is redacted, then Un-redacted it first</p>
</li>
<li><p>Got actual response</p>
</li>
<li><p>Done!</p>
</li>
</ol>
</li>
<li><p>Done!</p>
</li>
</ol>
<p>Now let's try it!</p>
<p>The prompt:</p>
<pre><code class="lang-markdown">Budi Dena have savings in banks $5000 with 3% yearly interest 

Selene Sofiro have savings on index funds $3000 with 10% interest yearly 

Within 10 years, who have more savings?
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700041960644/50ab3105-b742-4135-a9c6-81a3df6d7ae4.png" alt="Inputted prompt on redactd" class="image--center mx-auto" /></p>
<p>It will redacting the person's name:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700042069550/2ee37a75-2125-4bf8-a3be-45e9827830b7.png" alt="The person's name is redacted" class="image--center mx-auto" /></p>
<p>Now copy-paste the redacted prompt to ChatGpt</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700042265555/3c000d1f-e057-4173-ab57-aad1a1325d49.png" alt class="image--center mx-auto" /></p>
<p>As we can see, ChatGPT keeps mentioning the redacted text, now we want the un-redact version of the response. Copy the response to un-redact input field:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700042447922/ba31c65e-a4de-4bf4-bd04-00263543f237.png" alt class="image--center mx-auto" /></p>
<p>Voila, it works!</p>
<pre><code class="lang-markdown">Using the formula for compound interest:

\[ \text{Future Value} = \text{Principal} \times \left(1 + \frac{\text{Interest Rate}}{100}\right)^{\text{Time}} \]

\[ \text{Future Value} = 5000 \times \left(1 + \frac{3}{100}\right)^{10} \]

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Selene</span> <span class="hljs-attr">Sofiro</span> &gt;</span></span>:
Principal amount (initial savings): $3000
Interest rate: 10% per year
Time: 10 years

Using the same formula:

\[ \text{Future Value} = \text{Principal} \times \left(1 + \frac{\text{Interest Rate}}{100}\right)^{\text{Time}} \]

\[ \text{Future Value} = 3000 \times \left(1 + \frac{10}{100}\right)^{10} \]

Now, let's calculate these values to find out who has more savings after 10 years.

After calculating the above expressions, it is found that:

\[ \text{Future Value for <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Budi</span> <span class="hljs-attr">Dena</span> &gt;</span></span>} \approx \$6729.56 \]

\[ \text{Future Value for <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Selene</span> <span class="hljs-attr">Sofiro</span> &gt;</span></span>} \approx \$7912.85 \]

Therefore, after 10 years, <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Selene</span> <span class="hljs-attr">Sofiro</span> &gt;</span></span> would have more savings.
</code></pre>
<h1 id="heading-in-closing">In Closing</h1>
<p>Redacting text before send it to third parties is one of the way to keep privacy of our data. By using redactd, we have slight control on how we use data on the internet, specifically when using LLMs like ChatGPT</p>
<p>Thank you for reading, and shout out to <a target="_blank" href="https://pangea.cloud">Pangea Cloud</a> and <a target="_blank" href="https://hashnode.com">hashnode</a> for this hackathon!</p>
<p>Try it here: <a target="_blank" href="https://redactd.vercel.app/">https://redactd.vercel.app/</a></p>
<p>Github: <a target="_blank" href="https://github.com/fandyaditya/redactd">https://github.com/fandyaditya/redactd</a></p>
<p>#Pangea #PangeaSecurathon</p>
]]></content:encoded></item><item><title><![CDATA[Migrating Blog from Notion to Hugo]]></title><description><![CDATA[I have been using Notion for my personal blog. Start with using premium paid service Super.so when it still in the early bird. Super is great, it just works, that's what I want, but when it gets popular I don't have the luxury to pay 12$ per month fo...]]></description><link>https://dev.fandyaditya.com/migrating-blog-from-notion-to-hugo</link><guid isPermaLink="true">https://dev.fandyaditya.com/migrating-blog-from-notion-to-hugo</guid><category><![CDATA[notion]]></category><category><![CDATA[Hugo]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Fri, 04 Aug 2023 13:47:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691156638269/528b07bc-0012-460f-b860-1759f831879c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been using Notion for my personal blog. Start with using premium paid service <a target="_blank" href="https://super.so/">Super.so</a> when it still in the early bird. Super is great, it just works, that's what I want, but when it gets popular I don't have the luxury to pay 12$ per month for my 10-visitor-per-week blog. Then, I start searching for open-source alternatives and found <a target="_blank" href="https://github.com/transitive-bullshit/nextjs-notion-starter-kit">this repository</a> on Github. Just need an hour of configuration and my blog is live without paying a dime!</p>
<h2 id="heading-the-issue">The Issue</h2>
<p>The issue is because it is deployed on vercel free, and 500 pages on my personal blog, vercel 10s timeout limit background worker cannot fetch all the updated data because there to many pages. So, i need to trigger build everytime i post something new. Also with the 10-second timeout limit background process, the tools cannot generate sitemap.xml for the blog because the pages need more than 10-second to generate. Google never knew my blog.</p>
<p>After 1 year keep using it, for this month, I insist on migrating my blog to static-site generator. And my choice is Hugo.</p>
<h2 id="heading-why-hugo">Why Hugo?</h2>
<p>Apparently, I don't have any particular reason why I choose Hugo over another alternative like Gatsby.js and etc. Maybe it was just an impulsive decision because currently I'm working with Golang in my workplace (and I love it), so Hugo that builds using Golang become my choice. If you want to get any other good comparison, I think there is already good information out there.</p>
<h2 id="heading-migration-process">Migration Process</h2>
<p>So the process of migration we can separate into these step:</p>
<ol>
<li><p>Create Hugo project, and import themes</p>
</li>
<li><p>Configure the themes</p>
</li>
<li><p>Export Notion pages into markdown</p>
</li>
<li><p>Change every markdown metadata into Hugo format</p>
</li>
<li><p>Copy new formatted markdowns</p>
</li>
<li><p>Publish and done!</p>
</li>
</ol>
<p>For step 4, I create a Node.js script to do the job. You can steal the script here:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="54322abe0777bd8475e50cf2fe7a0e32"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/fandyaditya/54322abe0777bd8475e50cf2fe7a0e32" class="embed-card">https://gist.github.com/fandyaditya/54322abe0777bd8475e50cf2fe7a0e32</a></div><p> </p>
<p>Basically what the script does is replace all Notion markdown metadata and reform it into Hugo-supported metadata.</p>
<p>And it is done! My new blog has new skin, and it is blazing fast. Also super sleek layout thanks to this <a target="_blank" href="https://github.com/nodejh/hugo-theme-mini">theme</a>. Check my blog here:</p>
<p><a target="_blank" href="https://blog.fandyaditya.com/">https://blog.fandyaditya.com/</a></p>
<h2 id="heading-til">TIL</h2>
<p>After writing this post, I learned that there are already open-source solutions to convert Notion blog to Hugo flawlessly. Here are some of them, you can try it yourself!</p>
<ul>
<li><p><a target="_blank" href="https://github.com/HEIGE-PCloud/Notion-Hugo">https://github.com/HEIGE-PCloud/Notion-Hugo</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/nikvdp/notion-blog-exporter">https://github.com/nikvdp/notion-blog-exporter</a></p>
</li>
<li><p><a target="_blank" href="https://livingissodear.com/posts/write_in_notion_publish_with_hugo_introducing_n2h/">https://livingissodear.com/posts/write_in_notion_publish_with_hugo_introducing_n2h/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Navigating the Challenges of Third-Party API Integration: Lessons Learned from Personal Experience]]></title><description><![CDATA[Over the past three years, I have been working on aggregator apps. These apps essentially act as intermediaries between users and various systems. The main goal is to provide a seamless user experience by allowing them to interact with multiple syste...]]></description><link>https://dev.fandyaditya.com/navigating-the-challenges-of-third-party-api-integration-lessons-learned-from-personal-experience</link><guid isPermaLink="true">https://dev.fandyaditya.com/navigating-the-challenges-of-third-party-api-integration-lessons-learned-from-personal-experience</guid><category><![CDATA[ third party integration]]></category><category><![CDATA[Experience ]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Fri, 07 Jul 2023 16:22:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688746884537/ed197820-05e6-401a-afea-137c609d344f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past three years, I have been working on aggregator apps. These apps essentially act as intermediaries between users and various systems. The main goal is to provide a seamless user experience by allowing them to interact with multiple systems through our app. The core tasks involve proxying user requests to third-party APIs, mapping the responses, returning success or error messages to users, and storing the responses in our database.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688745465721/a1038168-66d9-4c6f-8c0d-d9f580850503.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-types-of-third-party-api-providers">Types of third-party API providers</h2>
<p>There are three types of third-party API providers that we can categorize:</p>
<ol>
<li><p>Seamless API Providers: These providers specialize in offering APIs for public use. Since this is their primary business, they pay extra attention to this area. They focus on making their APIs seamless and easy to use by providing excellent documentation and support. Users can often self-register and obtain their own authentication keys, such as apiKey or secretKey. Stripe is a good example of this category.</p>
</li>
<li><p>Semi-Seamless API Providers: These providers also offer public APIs, but with certain legal agreements in place. Users cannot self-register, and documentation is provided during the agreement process. Although this is not their main focus, they have dedicated teams and good support for their API offerings. Bank integrations are an example of this category.</p>
</li>
<li><p>APIs Under Development/Manual Integration: This is the most challenging category to integrate with. These providers are relatively new to offering third-party APIs and lack dedicated teams and support for integration. Agreements are often informal rather than legally binding. In most cases, support from these providers is limited. Businesses that already have established processes and want to expand into third-party API provision fall into this category.</p>
</li>
</ol>
<h2 id="heading-determining-who-needs-whom">Determining who needs whom</h2>
<p>In these collaborative efforts, the fundamental question is: <strong>Who needs whom?</strong> Ideally, both parties should benefit equally from the collaboration (mutualism). However, when this balance is absent, the party with the greater need is at a disadvantage. If a provider requires us to integrate their APIs rapidly into our system, they are likely to actively provide support and fulfill all requirements to ensure a fast and accurate integration. However, if we rely more on their APIs, the dynamics change. In such cases, we must proactively communicate our needs.</p>
<h2 id="heading-the-issues">The Issues</h2>
<p>Based on my experience, the following issues are likely to arise:</p>
<ol>
<li><p>Lack of Documentation</p>
<p> The lack of comprehensive documentation is a common problem. It may include issues such as unclear API requests, insufficient API responses, and unclear error conditions. Additionally, if there is no clear understanding of how an API works or which API needs to be integrated, valuable time is wasted seeking clarification.</p>
</li>
<li><p>Errors</p>
<p> Inadequate documentation makes every bit of support from the provider invaluable, especially during production. If there are any errors caused by their APIs, users may still blame us. Therefore, having a dedicated support team from the provider can save us from significant trouble.</p>
</li>
<li><p>Lack of Representative Test Data</p>
<p> The absence of representative test data makes end-to-end testing difficult in the development environment. Furthermore, when test data is available, discrepancies between the production and development environments often hinder reproduction of specific cases.</p>
</li>
<li><p>Breaking Changes</p>
<p> Unforeseen breaking changes in third-party APIs can occur without prior notification, causing error to our production system.</p>
</li>
</ol>
<h2 id="heading-what-went-well">What Went Well</h2>
<p>If in the future I need to develop this kind of apps again, this action that will be keep and repeated:</p>
<ol>
<li><p>Proactive Approach</p>
<p> Despite the lack of documentation, our team remains proactive by seeking clarification and initiating discussions to address any issues or problems encountered during the documentation and development process. While the provider's response may not always be immediate, it is essential to make the best of the available information and proceed accordingly.</p>
</li>
<li><p>Unified Communication</p>
<p> We have established dedicated WhatsApp groups for each party involved, including our team, the provider's team, and all stakeholders. This approach ensures transparency and enables us to share any errors, successes, problems, or smooth operations that occur.</p>
</li>
<li><p>Stakeholder Understanding</p>
<p> Our stakeholders comprehend that integrating with third-party APIs presents challenges, including slow development and occasional disruptions to production. Their understanding stems from the transparency we maintain through the WhatsApp groups. Moreover, if the provider's response is delayed, we can escalate our requests through higher channels. Based on my experience, this approach usually yields a higher response rate.</p>
</li>
</ol>
<h2 id="heading-lessons-learned">Lessons Learned</h2>
<p>If in the future I need to develop this kind of app again, I will do all this action:</p>
<ol>
<li><p>Extensive Unit Testing</p>
<p> Although my company follows a standard of achieving 80% code coverage with unit tests, I would emphasize the need for more comprehensive and tailored unit tests. This would enhance our confidence in the integration code and account for a wider range of possibilities.</p>
</li>
<li><p>Mock Data</p>
<p> We have faced delays in releases due to inadequate testing options. Leveraging tools like Postman for mocking responses proved to be beneficial. Although having real example data from the source is preferable, utilizing mocking tools can expedite the testing process.</p>
</li>
<li><p>Effective Communication</p>
<p> Maintaining effective communication channels between teams and stakeholders is crucial for successful collaboration and issue resolution.</p>
</li>
<li><p>Meaningful Logging</p>
<p> Employing well-structured logging mechanisms allows us to track and analyze system behavior effectively. We have done some logging and monitoring on production, but sometime some error still hard to trace. Good logging system will avoid some headaches.</p>
</li>
</ol>
<p>Finally, it may be worth reconsidering the necessity of being an aggregator of third-party APIs. Evaluating if our business can operate without reliance on such APIs might provide valuable insights and potential opportunities for change.</p>
<p>That covers the main points of the article. Hopefully, this article give you some insight on working with third-party APIs.</p>
]]></content:encoded></item><item><title><![CDATA[Unit Testing using Mocha, Sinon, and Chai in NodeJS]]></title><description><![CDATA[We use Mocha as the test framework, Chai for assertions, and Sinon.js for creating Doubles. Here's the code for installation:
$ npm install sinon mocha chai --save-dev

--save-dev is used because these modules are only needed during development. We d...]]></description><link>https://dev.fandyaditya.com/unit-testing-using-mocha-sinon-and-chai-in-nodejs</link><guid isPermaLink="true">https://dev.fandyaditya.com/unit-testing-using-mocha-sinon-and-chai-in-nodejs</guid><category><![CDATA[unit testing]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[mocha]]></category><category><![CDATA[chai]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Tue, 25 Apr 2023 15:09:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1682435873169/01653508-d7d6-4378-977e-ba594db7b32d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We use Mocha as the test framework, Chai for assertions, and Sinon.js for creating Doubles. Here's the code for installation:</p>
<pre><code class="lang-bash">$ npm install sinon mocha chai --save-dev
</code></pre>
<p><code>--save-dev</code> is used because these modules are only needed during development. We don't need these modules during production.</p>
<h1 id="heading-the-script">The Script</h1>
<p>The main thing to do when creating a script is to determine which function to test, along with what will be tested in that function.</p>
<p>For example, if we want to do unit testing on the following function:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/*number-lib.js*/</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addInteger</span>(<span class="hljs-params">x, y</span>) </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Number</span>.isInteger(x) &amp;&amp; <span class="hljs-built_in">Number</span>.isInteger(y)) {
        <span class="hljs-keyword">return</span> x + y   
    }
    <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'Number is not integer'</span>
    }
    <span class="hljs-keyword">return</span> 
}
<span class="hljs-built_in">module</span>.exports = {
    addInteger
}
</code></pre>
<p>The function above is a function to add integers x and y. If they are not integers, it will return a string saying that the input is not an integer.</p>
<p>First, we will determine our test scope as follows:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// number-list-test.spec.js</span>
<span class="hljs-keyword">const</span> numberLib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./number-lib.js'</span>)

describe(<span class="hljs-string">'Add integer'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should return 23'</span>, <span class="hljs-function">() =&gt;</span> {
       <span class="hljs-comment">// Arrange</span>
       ...
       <span class="hljs-comment">// Act</span>
       ...
       <span class="hljs-comment">// Assert</span>
       ...
    })
    it(<span class="hljs-string">'should return "Number is not integer", because input is not integer'</span>, <span class="hljs-function">() =&gt;</span> {
       <span class="hljs-comment">// Arrange</span>
       ...
       <span class="hljs-comment">// Act</span>
       ...
       <span class="hljs-comment">// Assert</span>
    })
})
</code></pre>
<p><code>describe()</code> and <code>it()</code> are functions of Mocha, where we will write our test script. In the <code>describe()</code> function, we determine our test scope. Inside each <code>it()</code> function, we determine the cases that we will perform.</p>
<p>In the case above, our scope is to test the <code>addInteger()</code> function with 2 agreed-upon cases, namely the function must return 23, and the function must return <code>"Number is not integer"</code> because the input we will give is not an integer.</p>
<p>In the <code>it()</code> function, there is an Arrange, Act, and Assert section. Here is an explanation:</p>
<ol>
<li><strong>Arrange</strong></li>
</ol>
<p>In the Arrange section, it usually contains input initialization and doubles initialization which aims to produce the desired output.</p>
<ol>
<li><strong>Act</strong></li>
</ol>
<p>In the Act section, we execute the function that we want to test using the input that we have initialized in the Arrange section.</p>
<ol>
<li><strong>Assert</strong></li>
</ol>
<p>In the Assert section, we check whether the result produced by the function in the Act section matches the expected output that we have initialized in the Arrange section.</p>
<p>Now we will add the code snippet above to make it complete.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// number-list-test.spec.js</span>
<span class="hljs-keyword">const</span> numberLib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./number-lib.js'</span>)
<span class="hljs-keyword">const</span> expect = <span class="hljs-built_in">require</span>(<span class="hljs-string">'chai'</span>).expect

describe(<span class="hljs-string">'Add integer'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should return 23'</span>, <span class="hljs-function">() =&gt;</span> {
       <span class="hljs-comment">// Arrange</span>
       <span class="hljs-keyword">let</span> x = <span class="hljs-number">11</span>
       <span class="hljs-keyword">let</span> y = <span class="hljs-number">12</span>
       <span class="hljs-comment">// Act</span>
       <span class="hljs-keyword">let</span> result = numberLib.addInteger(x, y)
       <span class="hljs-comment">// Assert</span>
       expect(result).to.equal(<span class="hljs-number">23</span>)
    })
    it(<span class="hljs-string">'should return "Number is not integer", because input is not integer'</span>, <span class="hljs-function">() =&gt;</span> {
       <span class="hljs-comment">// Arrange</span>
       <span class="hljs-keyword">let</span> x = <span class="hljs-number">1.1</span>
       <span class="hljs-keyword">let</span> y = <span class="hljs-number">1.2</span>
       <span class="hljs-comment">// Act</span>
       <span class="hljs-keyword">let</span> result = numberLib.addInteger(x, y)
       <span class="hljs-comment">// Assert</span>
       expect(result).to.equal(<span class="hljs-string">'Number is not integer'</span>)
    })
})
</code></pre>
<h2 id="heading-doubles">Doubles</h2>
<p>For example, let's update the function we want to test like this:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addNumber</span>(<span class="hljs-params">x, y</span>) </span>{
    <span class="hljs-keyword">if</span>(isInteger(x) &amp;&amp; isInteger(y))) {
        <span class="hljs-keyword">return</span> x + y
    }
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(isDecimal(x) &amp;&amp; isDecimal(y))) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.round(x) + <span class="hljs-built_in">Math</span>.round(y)
    }
    <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'The input is not integer nor decimal value'</span>
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isInteger</span>(<span class="hljs-params">x</span>) </span>{
    ...
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isDecimal</span>(<span class="hljs-params">x</span>) </span>{
    ...
}

modules.export = {
    addInteger
}
</code></pre>
<p>Correct. There is a function inside a function. The concept of Unit Testing is that we only test the function that we want to test. If the function calls another function, then we ignore the other function by making it a Double.</p>
<p>In our example, we will not care about the <code>isInteger()</code> and <code>isDecimal()</code> functions. Both of these functions will be made into doubles with the help of sinon.</p>
<p>In that case, it was agreed that there are 3 cases to be tested. Here are the tests:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> sinon = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sinon'</span>)
<span class="hljs-keyword">const</span> expect = <span class="hljs-built_in">require</span>(<span class="hljs-string">'chai'</span>).expect
<span class="hljs-keyword">const</span> numberLib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./number-lib'</span>)

describe(<span class="hljs-string">'Add Number'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should return 40'</span>, <span class="hljs-function">() =&gt;</span> {
    })
    it(<span class="hljs-string">'the input is decimal, should return 15'</span>, <span class="hljs-function">() =&gt;</span> {
    })
    it(<span class="hljs-string">'should return "The input is not integer nor decimal value"'</span>, <span class="hljs-function">() =&gt;</span> {
    })
})
</code></pre>
<p>We have determined the cases, now let's add the test script:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> sinon = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sinon'</span>)
<span class="hljs-keyword">const</span> expect = <span class="hljs-built_in">require</span>(<span class="hljs-string">'chai'</span>).expect
<span class="hljs-keyword">const</span> numberLib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./number-lib'</span>)

describe(<span class="hljs-string">'Add Number'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should return 40'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">//Create new stub for isInteger that always return true</span>
        <span class="hljs-keyword">let</span> isInteger = sinon.stub(numberLib, <span class="hljs-string">'isInteger'</span>).returns(<span class="hljs-literal">true</span>)
        <span class="hljs-keyword">let</span> x = <span class="hljs-number">20</span>
        <span class="hljs-keyword">let</span> y = <span class="hljs-number">20</span>
        <span class="hljs-keyword">let</span> result = numberLib(x, y)
        expect(result).to.equal(<span class="hljs-number">40</span>)
        isInteger.restore()
    })
    it(<span class="hljs-string">'the input is decimal, should return 15'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">//Create new stub for isInteger that always return false</span>
        <span class="hljs-comment">//stub for isDecimal always return true</span>
        <span class="hljs-keyword">let</span> isInteger = sinon.stub(numberLib, <span class="hljs-string">'isInteger'</span>).return(<span class="hljs-literal">false</span>)
        <span class="hljs-keyword">let</span> isDecimal = sinon.stub(numberLib, <span class="hljs-string">'isDecimal'</span>).returns(<span class="hljs-literal">true</span>)
        <span class="hljs-keyword">let</span> x = <span class="hljs-number">7.4</span>
        <span class="hljs-keyword">let</span> y = <span class="hljs-number">7.6</span>
        <span class="hljs-keyword">let</span> result = numberLib(x, y)
        expect(result).to.equal(<span class="hljs-number">15</span>)
        isInteger.restore()
        isDecimal.restore()
    })
    it(<span class="hljs-string">'should return "The input is not integer nor decimal value"'</span>, <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">//Create new stub for isInteger that always return false</span>
        <span class="hljs-comment">//stub for isDecimal always return false</span>
        <span class="hljs-keyword">let</span> isInteger = sinon.stub(numberLib, <span class="hljs-string">'isInteger'</span>).return(<span class="hljs-literal">false</span>)
        <span class="hljs-keyword">let</span> isDecimal = sinon.stub(numberLib, <span class="hljs-string">'isDecimal'</span>).returns(<span class="hljs-literal">false</span>)
        <span class="hljs-keyword">let</span> x = <span class="hljs-string">'anything'</span>
        <span class="hljs-keyword">let</span> y = <span class="hljs-string">'anything'</span>
        <span class="hljs-keyword">let</span> result = numberLib(x, y)
        expect(result).to.equal(<span class="hljs-string">'The input is not integer nor decimal value'</span>)
        isInteger.restore()
        isDecimal.restore()
    })
})
</code></pre>
<p>At the beginning of each case, we always initialize the stub for the <code>isInteger()</code> and <code>isDecimal()</code> functions, by changing its return value according to the needs of the test case. When <code>numberLib()</code> is called, if a stub function we made before is encountered during execution, that function will be changed to a stub.</p>
<p>At the end of each case, we must restore() the stubs we made so that the functions that became stubs can run their own content again. If we don't do a <code>restore()</code>, there is a possibility that the next test will fail because these functions are still stubs.</p>
<p>The above test script can still be improved with the following Mocha functions:</p>
<pre><code class="lang-javascript">...
const numberLib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./number-lib'</span>)
describe(<span class="hljs-string">'Add number'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> isInteger
    <span class="hljs-keyword">let</span> isDecimal
    beforeEach(<span class="hljs-function">() =&gt;</span> {
        isInteger = sinon.stub(numberLib, <span class="hljs-string">'isInteger'</span>)
        isDecimal = sinon.stub(numberLib, <span class="hljs-string">'isDecimal'</span>)
    })
    afterEach(<span class="hljs-function">() =&gt;</span> {
        isInteger.restore()
        isDecimal.restore()
    })
    it(<span class="hljs-string">'...'</span>, <span class="hljs-function">() =&gt;</span> {
        isInteger.returns(<span class="hljs-literal">true</span>)
        ...
    })
    it(<span class="hljs-string">'...'</span>, <span class="hljs-function">() =&gt;</span> {
        ...
    })
    it(<span class="hljs-string">'...'</span>, <span class="hljs-function">() =&gt;</span> {
        ...
    })
})
</code></pre>
<p><code>beforeEach()</code> is always called before each test case is executed, while <code>afterEach()</code> is always called after each test case is executed. With the help of <code>beforeEach()</code> and <code>afterEach()</code> we can avoid repetitive initialization code and make our test script more readable.</p>
<h2 id="heading-anonymous-function-cases">Anonymous function cases</h2>
<p>Sometimes we encounter something like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//my-route.js</span>
router.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-comment">//function that we want to test</span>
    ...
    ...
})
router.get(<span class="hljs-string">'/user'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-comment">//other function</span>
    ...
    ...
})
<span class="hljs-built_in">module</span>.exports = router
</code></pre>
<p>The code above is one of the examples to create a router on <code>express.js</code>.</p>
<p>How do we test that function, when that function can't be called from another function or so it is called Anonymous Function? We can test it with Sinon's help.</p>
<p>First, create the test script:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> route = (<span class="hljs-string">'./my-route'</span>)

describe(<span class="hljs-string">'Route test'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should ...'</span>, <span class="hljs-function">() =&gt;</span>{

    })
})
</code></pre>
<p>We can see from the function, <code>router</code> call <code>get</code> function that has 2 parameters. First parameter is the endpoint of the route, and the second is the function that we want to test. So we need to create spy on <code>get</code> function.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> route = <span class="hljs-built_in">require</span>(./my-route)
<span class="hljs-keyword">const</span> sinon = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sinon'</span>)

describe(<span class="hljs-string">'Route test'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should ...'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">//create spy on get</span>
      <span class="hljs-keyword">let</span> get = sinon.spy(route, <span class="hljs-string">'get'</span>)
      <span class="hljs-comment">// Arrange input </span>
      <span class="hljs-keyword">let</span> req = {}
      <span class="hljs-keyword">let</span> res = {}
      <span class="hljs-comment">//call the function</span>
      route.router()
      ..
    })
}
</code></pre>
<p>When <code>route.router()</code> called, all function (<code>router.get('/')</code> dan <code>router.get('/user')</code>) will be called. Because we only want to test <code>get('/')</code>, the test code become like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> route = <span class="hljs-built_in">require</span>(./my-route)
<span class="hljs-keyword">const</span> sinon = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sinon'</span>)

describe(<span class="hljs-string">'Route test'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should ...'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">let</span> get = sinon.spy(route, <span class="hljs-string">'get'</span>)

      <span class="hljs-keyword">let</span> req = {}
      <span class="hljs-keyword">let</span> res = {}

      route.router()
      <span class="hljs-comment">//Calling the `first` get and then trigger anonymous function on the parameter no 1</span>
      get.firstCall.callArgWith(<span class="hljs-number">1</span>, req, res)

      expect(...)
      get.restore()
    })
}
</code></pre>
<p><code>get.firstCall</code> will get only the function that is called first.</p>
<p><code>get.firstCall.callArgwith(1, req, res)</code> will be triggering the function on second parameter (Zero based). And then, we succesfully call the anonymous function, asserting could be done.</p>
<h1 id="heading-running">Running</h1>
<p>To run the test script, we need some modifications on <code>package.json</code></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// package.json</span>
<span class="hljs-string">"scripts"</span>: {  
    <span class="hljs-string">"test"</span>: <span class="hljs-string">"mocha test/**/*.spec.js"</span>
}
</code></pre>
<p>The goal is to running <code>mocha</code> command on all files in all folders that having <code>.spec.js</code> extension. So we need to name our test function with <code>.spec.js</code></p>
<p>Run the script:</p>
<pre><code class="lang-bash">npm <span class="hljs-built_in">test</span>
</code></pre>
<p>We can see the results in terminal:</p>
<pre><code class="lang-javascript">&gt; mocha test<span class="hljs-comment">/**/*.spec.js

  Add Integer
    ✓ should return 23
    ✓ should return "Number is not integer", because input is not integer

 2 passing (1s)</span>
</code></pre>
<h1 id="heading-reporting">Reporting</h1>
<p>We need to module for this. Mocha-junit-reporter for report unit-test, and nyc for code coverage</p>
<pre><code class="lang-bash">$ npm install mocha-junit-reporter nyc --save-dev
</code></pre>
<p>Modify <code>package.json</code> on <code>scripts</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-string">"report"</span>: <span class="hljs-string">"mocha test/**/*.spec.js --reporter mocha-junit-reporter"</span>,
    <span class="hljs-string">"coverage"</span>: <span class="hljs-string">"nyc --reporter=lcov --reporter=text-lcov npm test"</span>
},
</code></pre>
<p>Run it:</p>
<pre><code class="lang-bash">$ npm run report
$ npm run coverage
</code></pre>
<p>We will get <code>.xml</code> that we can show on any other application or tool like Jenkins</p>
<h1 id="heading-references">References</h1>
<p><a target="_blank" href="https://martinfowler.com/articles/mocksArentStubs.html">https://martinfowler.com/articles/mocksArentStubs.html</a></p>
<p><a target="_blank" href="https://www.sitepoint.com/sinon-tutorial-javascript-testing-mocks-spies-stubs/">https://www.sitepoint.com/sinon-tutorial-javascript-testing-mocks-spies-stubs/</a></p>
]]></content:encoded></item><item><title><![CDATA[Create Q&A from Essays and Twitter Threads, using Open AI Embedding and Pinecone]]></title><description><![CDATA[After some time of researching Open AI, Vector databases, and semantic search. For today, I tried to combine all of the knowledge I get and create Q&A based and search for 400 Twitter threads and essays of Illimitablemen IM
Luckily, I found this Paul...]]></description><link>https://dev.fandyaditya.com/create-qa-from-essays-and-twitter-threads-using-open-ai-embedding-and-pinecone</link><guid isPermaLink="true">https://dev.fandyaditya.com/create-qa-from-essays-and-twitter-threads-using-open-ai-embedding-and-pinecone</guid><category><![CDATA[Pinecone]]></category><category><![CDATA[langchain]]></category><category><![CDATA[openai]]></category><category><![CDATA[chatgpt]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Tue, 04 Apr 2023 15:19:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1680621164419/9cfc6bcb-9ca7-4614-9635-21d09c5538b4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After some time of researching Open AI, Vector databases, and semantic search. For today, I tried to combine all of the knowledge I get and create Q&amp;A based and search for 400 Twitter threads and essays of Illimitablemen <a target="_blank" href="https://twitter.com/TellYourSonThis">IM</a></p>
<p>Luckily, I found this <a target="_blank" href="https://github.com/mckaywrigley/paul-graham-gpt">Paul Graham GPT</a> on GitHub. This is basically what I want to build. Tweak some here and there. 60% of the work is already done.</p>
<p>What I need to do now is gather all the data, create the vector, insert it into Pinecone, then queried it.</p>
<blockquote>
<p>For better understading, please go to the series: <a target="_blank" href="https://dev.fandyaditya.com/series/openai-semantic-search">https://dev.fandyaditya.com/series/openai-semantic-search</a>.</p>
<p>Any step that seems missing will be found on that series.</p>
</blockquote>
<h1 id="heading-scrapping-the-data">Scrapping the data</h1>
<p>For the threads. Because I don't have access to the twitter APIs. I need to find another way.</p>
<p><a target="_blank" href="https://en.rattibha.com/">Rattibha</a> one of Threadsaver-bot, give me access to IM's 400++ threads. Which means, many twitter user use Rattibha to save IM's twitter-thread.</p>
<p>For the essay, I can got it from his blog <a target="_blank" href="https://illimitablemen.com">illimitablemen.com</a></p>
<p>Maybe because of the structure or I just suck at selecting HTML elements and using any HTML parser, it takes a long time for me to scrape and clean the data. I create a script for it, but I also use <a target="_blank" href="https://chrome.google.com/webstore/detail/web-scraper-free-web-scra/jnhgnonknehpejjnehehllkliplmbmhn?hl=en">Chrome extension web scraper tools</a> to collect some of the data. Manually cleaning it if the script or the tools not cleaning it well. I scrape the text content, link of the source tweet/essay, and the created date of the content and store it into JSON.</p>
<h1 id="heading-create-embedding-and-store-it">Create embedding and store it</h1>
<p>After data is gathered, now we create the embeddings. Before we embed, we chunk the data first. I use Langchain to do that.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { RecursiveCharacterTextSplitter } <span class="hljs-keyword">from</span> <span class="hljs-string">"langchain/text_splitter"</span>;

<span class="hljs-keyword">const</span> textSplitter = <span class="hljs-keyword">new</span> RecursiveCharacterTextSplitter({ <span class="hljs-attr">chunkSize</span>: <span class="hljs-number">1000</span>, <span class="hljs-attr">chunkOverlap</span>: <span class="hljs-number">0</span> });

<span class="hljs-keyword">const</span> fileJson = <span class="hljs-comment">//where the data stored</span>

<span class="hljs-keyword">const</span> splittedText = <span class="hljs-keyword">await</span> textSplitter.splitText(fileJSon.text);
</code></pre>
<p><code>RecursiveCharacterTextSplitter</code> is a Langchain module that makes chunking text very easy. I choose 1000 chunk size, which means I want the chunk consists of max 1000 characters each.</p>
<p>Now we need to create embedding and store the data to Pinecone. But before we do that, we need to specify the content of the metadata that we want to query later. For this project, we just need the text content, the title, and the link so we can navigate it to the source essay/tweet.</p>
<p>I use Langchain Pinecone Vector Store module to do this. We also need to convert the chunk into Document object.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { PineconeStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"langchain/vectorstores"</span>;
<span class="hljs-keyword">import</span> { OpenAIEmbeddings } <span class="hljs-keyword">from</span> <span class="hljs-string">"langchain/embeddings"</span>;

<span class="hljs-keyword">let</span> documents = []; 
splittedText.forEach(<span class="hljs-function">(<span class="hljs-params">txt</span>) =&gt;</span> {
            <span class="hljs-keyword">const</span> <span class="hljs-built_in">document</span> = <span class="hljs-keyword">new</span> Document({
                <span class="hljs-attr">pageContent</span>: txt,
                <span class="hljs-attr">metadata</span>: {
                    <span class="hljs-attr">title</span>: fileJSon.title,
                    <span class="hljs-attr">createdAt</span>: fileJSon.createdAt,
                    <span class="hljs-attr">link</span>: fileJSon.link.url
                }
            });
            documents.push(<span class="hljs-built_in">document</span>);
});

PineconeStore.fromDocuments(documents, <span class="hljs-keyword">new</span> OpenAIEmbeddings(), { <span class="hljs-attr">pineconeIndex</span>: pinecone.index(<span class="hljs-string">'indexname'</span>), <span class="hljs-attr">namespace</span>: <span class="hljs-string">'namespacename'</span> })
</code></pre>
<p>With <code>PineconeStore.fromDocuments</code> function, the chunks are automatically turned into embedding and inserted into the pinecone index.</p>
<p>Take note: <code>document.pageContent</code> will be converted into <code>metadata.text</code> in vector in pinecone db. This is important for querying later.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1680618567462/b6543e26-be44-4e49-b0a6-951e8572a266.png" alt="langchain document.pageContent become metadata.text in pinecone" class="image--center mx-auto" /></p>
<p>Now the data is stored and ready to use!</p>
<h1 id="heading-create-the-qandampa-apps">Create the Q&amp;A Apps</h1>
<p>As i mention before, I built this on top of this <a target="_blank" href="https://github.com/mckaywrigley/paul-graham-gpt">repo</a>. Need a little tweak. Because it use supabase, i need to change it into pinecone.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//pinecone.js</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> query = <span class="hljs-keyword">async</span> (embed) =&gt; {

    <span class="hljs-keyword">await</span> pinecone.init({
        <span class="hljs-attr">environment</span>: process.env.PINECONE_ENVIRONMENT,
        <span class="hljs-attr">apiKey</span>: process.env.PINECONE_API_KEY
    });

    <span class="hljs-keyword">const</span> index = pinecone.Index(<span class="hljs-string">'indexname'</span>);
    <span class="hljs-keyword">const</span> queryRequest = {
        <span class="hljs-attr">vector</span>: embed,
        <span class="hljs-attr">topK</span>: <span class="hljs-number">5</span>,
        <span class="hljs-attr">includeValues</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">includeMetadata</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">namespace</span>: <span class="hljs-string">'namespacename'</span>
    }
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> index.query({ queryRequest })
        <span class="hljs-keyword">const</span> mappedResponse = response.matches.map(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> {
            <span class="hljs-keyword">return</span> {
                <span class="hljs-attr">metadata</span>: item.metadata
            }
        })
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">data</span>: mappedResponse }
    }<span class="hljs-keyword">catch</span>(err) {
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">error</span>: err }
    }   
}
</code></pre>
<p>Then change for any code that calls Supabase query into this function.</p>
<p>And done!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/acedb101560242c5ba0eb796167347cf">https://www.loom.com/share/acedb101560242c5ba0eb796167347cf</a></div>
<p> </p>
<p>Try it here: <a target="_blank" href="https://illimitablemen-gpt.vercel.app/">https://illimitablemen-gpt.vercel.app/</a>.</p>
<h1 id="heading-special-thanks">Special Thanks</h1>
<p>Thanks to <a target="_blank" href="https://twitter.com/TellYourSonThis">IM</a> for the awesome writing. Very eye-opening, make me become a better person. I highly recommend to follow him and read his essays.</p>
<p>And also <a target="_blank" href="https://twitter.com/mckaywrigley">mckaywrigle</a> for Open Sourced <a target="_blank" href="https://github.com/mckaywrigley/paul-graham-gpt">Paul Graham GPT</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Semantic Search using Langchain, Pinecone DB in Node JS]]></title><description><![CDATA[Langchain is basically an interface and wrapper of LLM. The goal is to simplify, and also make it easier to do pre and post-processing of the LLM input-output so we can build the LLM applications easily. Here is a good start for Langchain:
https://la...]]></description><link>https://dev.fandyaditya.com/semantic-search-using-langchain-pinecone-db-in-node-js</link><guid isPermaLink="true">https://dev.fandyaditya.com/semantic-search-using-langchain-pinecone-db-in-node-js</guid><category><![CDATA[openai]]></category><category><![CDATA[langchain]]></category><category><![CDATA[Pinecone]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Mon, 27 Mar 2023 15:37:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679931290180/d338283f-bde2-44fd-ae74-5146f2235d07.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Langchain is basically an interface and wrapper of LLM. The goal is to simplify, and also make it easier to do pre and post-processing of the LLM input-output so we can build the LLM applications easily. Here is a good start for Langchain:</p>
<p><a target="_blank" href="https://langchainers.hashnode.dev/getting-started-with-langchainjs">https://langchainers.hashnode.dev/getting-started-with-langchainjs</a></p>
<p>My experience with Langchain, it works out of the box. We can cut many manual steps with Langchain. Also as new tools or libraries, there are some learning curves. Mainly because lack of documentation and tutorial (Especially for Node JS Client Library!)</p>
<p>For this project, we will re-create previous project, but use Langchain.</p>
<blockquote>
<p>Previous Project's Article:</p>
<p><a target="_blank" href="https://dev.fandyaditya.com/semantic-search-using-open-ai-embedding-pinecone-vector-db-and-node-js">https://dev.fandyaditya.com/semantic-search-using-open-ai-embedding-pinecone-vector-db-and-node-js</a></p>
</blockquote>
<h1 id="heading-prerequisite">Prerequisite</h1>
<h2 id="heading-database">Database</h2>
<p>Same as the previous trial, we will use pinecone. Create an account at <a target="_blank" href="http://pinecone.io"><strong>pinecone.io</strong></a> first.</p>
<p>And then at the console, create an Index.</p>
<pre><code class="lang-javascript">Index Name: whateveruwant (i named it<span class="hljs-string">"article"</span>)
<span class="hljs-attr">Dimensions</span>: <span class="hljs-number">1536</span>
<span class="hljs-attr">Metric</span>: cosine
Pod Type: S1 or P1
</code></pre>
<h2 id="heading-library">Library</h2>
<pre><code class="lang-javascript"><span class="hljs-string">"@pinecone-database/pinecone"</span>
<span class="hljs-string">"dotenv"</span>
<span class="hljs-string">"express"</span>
<span class="hljs-string">"langchain"</span>
<span class="hljs-string">"openai"</span>
</code></pre>
<pre><code class="lang-bash">npm install @pinecone-database/pinecone dotenv express langchain openai
</code></pre>
<h2 id="heading-env">ENV</h2>
<p>As usual, we need OpenAI API key, Pinecone ENV, and Pinecone API key</p>
<pre><code class="lang-javascript">OPENAI_API_KEY
PINECONE_ENVIRONMENT
PINECONE_API_KEY
</code></pre>
<h2 id="heading-article">Article</h2>
<p>Select some article from the internet, and put it in <code>article.txt</code>. I choose this article: <a target="_blank" href="https://jamesclear.com/saying-no">https://jamesclear.com/saying-no</a></p>
<h1 id="heading-code">Code</h1>
<p>Our project structure will look like this:</p>
<pre><code class="lang-javascript">node_modules
.env
article.txt
embed.js
package.json
server.js
pinecone.js
</code></pre>
<p>We will go to <code>pinecone.js</code> first:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//pinecone.js</span>

<span class="hljs-comment">/**
Read .env
**/</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
dotenv.config();


<span class="hljs-comment">/**
Init Pinecone
**/</span>
<span class="hljs-keyword">import</span> { PineconeClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@pinecone-database/pinecone'</span>;
<span class="hljs-keyword">const</span> pinecone = <span class="hljs-keyword">new</span> PineconeClient();

<span class="hljs-keyword">await</span> pinecone.init({
    <span class="hljs-attr">environment</span>: process.env.PINECONE_ENVIRONMENT,
    <span class="hljs-attr">apiKey</span>: process.env.PINECONE_API_KEY
});


<span class="hljs-comment">/**
Export the pinecone index function, named it based on your created index in pinecone. We will need this for Pinecone langchain wrapper.
**/</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> index = pinecone.Index(<span class="hljs-string">'article'</span>)
</code></pre>
<p>Now in <code>embed.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-comment">//embed.js</span>

<span class="hljs-comment">/**
Read .env file
**/</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>;
dotenv.config();


<span class="hljs-comment">/**
Open AI Embedding wrapper from langchain
**/</span>
<span class="hljs-keyword">import</span> { OpenAIEmbeddings } <span class="hljs-keyword">from</span> <span class="hljs-string">'langchain/embeddings'</span>;


<span class="hljs-comment">/**
Chunk text/text splitter function from langchain
**/</span>
<span class="hljs-keyword">import</span> { RecursiveCharacterTextSplitter } <span class="hljs-keyword">from</span> <span class="hljs-string">'langchain/text_splitter'</span>;


<span class="hljs-comment">/**
Pinecone wrapper from langchain
**/</span>
<span class="hljs-keyword">import</span> { PineconeStore } <span class="hljs-keyword">from</span> <span class="hljs-string">'langchain/vectorstores'</span>;


<span class="hljs-comment">/**
Init fs
**/</span>
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;


<span class="hljs-comment">/**
Create text splitter with chunksize 1000 character
**/</span>
<span class="hljs-keyword">const</span> textSplitter = <span class="hljs-keyword">new</span> RecursiveCharacterTextSplitter({ <span class="hljs-attr">chunkSize</span>: <span class="hljs-number">1000</span>, <span class="hljs-attr">chunkOverlap</span>: <span class="hljs-number">0</span>});


<span class="hljs-comment">/**
Init OpenAI Embeddings
**/</span>
<span class="hljs-keyword">const</span> embedder = <span class="hljs-keyword">new</span> OpenAIEmbeddings();


<span class="hljs-comment">/**
Get index from our pinecone.js
**/</span>
<span class="hljs-keyword">import</span> { index } <span class="hljs-keyword">from</span> <span class="hljs-string">'./pinecone.js'</span>;


(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-comment">//read article</span>
    <span class="hljs-keyword">const</span> article = <span class="hljs-keyword">await</span> fs.readFileSync(<span class="hljs-string">'article.txt'</span>, { <span class="hljs-attr">encoding</span>: <span class="hljs-string">'utf-8'</span> });
    <span class="hljs-comment">//split the text</span>
    <span class="hljs-keyword">const</span> splittedText = <span class="hljs-keyword">await</span> textSplitter.createDocuments([article]);

    <span class="hljs-comment">//store the splitted text to pinecone, in index "article" and namespace "langchain" (namespace is for filter purpose later, can be whatever you want)</span>
    PineconeStore.fromDocuments(splittedText, embedder, { <span class="hljs-attr">pineconeIndex</span>: index, <span class="hljs-attr">namespace</span>: <span class="hljs-string">'langchain'</span> });
})()
</code></pre>
<p>Now run:</p>
<pre><code class="lang-javascript">node embed.js
</code></pre>
<p>Check pinecone console. If this success, it will create vector data in Pinecone db</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679929857673/99c80f2e-75b3-4016-9547-1de4fbef2a34.png" alt="Vectors in &quot;langchain&quot;" class="image--center mx-auto" /></p>
<p>Now in <code>server.js</code>,create express app that pass query URL to search it in db using Langchain.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//server.js</span>

<span class="hljs-comment">/**
Import required langchain and pinecone library like in ./embed.js
**/</span>
<span class="hljs-keyword">import</span> { PineconeStore } <span class="hljs-keyword">from</span> <span class="hljs-string">'langchain/vectorstores'</span>;
<span class="hljs-keyword">import</span> { index } <span class="hljs-keyword">from</span> <span class="hljs-string">'./pinecone.js'</span>;
<span class="hljs-keyword">import</span> { OpenAIEmbeddings } <span class="hljs-keyword">from</span> <span class="hljs-string">'langchain/embeddings'</span>;
<span class="hljs-keyword">const</span> embedder = <span class="hljs-keyword">new</span> OpenAIEmbeddings();
<span class="hljs-keyword">const</span> pineconeStore = <span class="hljs-keyword">new</span> PineconeStore(embedder, { <span class="hljs-attr">pineconeIndex</span>: index, <span class="hljs-attr">namespace</span>: <span class="hljs-string">'langchain'</span> });


<span class="hljs-comment">/**
Init express app
**/</span>
<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> port = <span class="hljs-number">9000</span>;



app.get(<span class="hljs-string">'/'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> { q } = req.query;

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> pineconeStore.similaritySearch(q, <span class="hljs-number">5</span>);
        res.status(<span class="hljs-number">200</span>).send([...data])
    }<span class="hljs-keyword">catch</span>(err) {
        res.status(<span class="hljs-number">404</span>).send({ <span class="hljs-attr">message</span>: <span class="hljs-string">`<span class="hljs-subst">${q}</span> doesn't match any search`</span> });
    }
})

app.listen(port, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Example app listening on port <span class="hljs-subst">${port}</span>`</span>)
})
</code></pre>
<p>We tried to search based on vector similarity from Pinecone DB in index <code>article</code> and namespace <code>langchain</code> based on a passed query with 5 top similar results.</p>
<p>Now run:</p>
<pre><code class="lang-bash">node server.js
</code></pre>
<p>Tried to hit <code>http://localhost:9000</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679930247472/ecc79810-ae8b-4224-a8fe-288415b9bc44.png" alt class="image--center mx-auto" /></p>
<p>Good job! You success to do a semantic search using Langchain in NodeJS!</p>
<h1 id="heading-resources">Resources</h1>
<p>Github for this project: <a target="_blank" href="https://github.com/fandyaditya/semantic-search-langchain">https://github.com/fandyaditya/semantic-search-langchain</a></p>
]]></content:encoded></item><item><title><![CDATA[Semantic search using Open AI Embedding, Pinecone Vector DB, and Node JS]]></title><description><![CDATA[For the previous article, we use Postgres with pgvector extension to store the vector data of our article. Today let's try to change the Postgres with Pinecone Vector DB.

Previous Article:
https://dev.fandyaditya.com/semantic-search-using-openai-emb...]]></description><link>https://dev.fandyaditya.com/semantic-search-using-open-ai-embedding-pinecone-vector-db-and-node-js</link><guid isPermaLink="true">https://dev.fandyaditya.com/semantic-search-using-open-ai-embedding-pinecone-vector-db-and-node-js</guid><category><![CDATA[Pinecone]]></category><category><![CDATA[openai]]></category><category><![CDATA[semantic search]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Thu, 23 Mar 2023 16:39:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679589372827/df019089-afa5-40b0-ae0f-ba25c2f2e03a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For the previous article, we use Postgres with <code>pgvector</code> extension to store the vector data of our article. Today let's try to change the Postgres with Pinecone Vector DB.</p>
<blockquote>
<p>Previous Article:</p>
<p><a target="_blank" href="https://dev.fandyaditya.com/semantic-search-using-openai-embedding-and-postgres-vector-db-in-nodejs">https://dev.fandyaditya.com/semantic-search-using-openai-embedding-and-postgres-vector-db-in-nodejs</a></p>
</blockquote>
<h1 id="heading-why-pinecone">Why Pinecone?</h1>
<p>Many people across the internet mention pinecone are good for semantic search, because that is what pinecone is intended to build. After I do quick research (research for the <a target="_blank" href="https://www.pinecone.io/pricing/">pricing</a>, obviously :D), for today 23 march 2023 pinecone already has NodeJs client, also I think it is affordable and has a generous free tier. So why not?</p>
<h1 id="heading-prerequisite">Prerequisite</h1>
<p>For this topic, we will use 90% of code from <a target="_blank" href="https://dev.fandyaditya.com/semantic-search-using-openai-embedding-and-postgres-vector-db-in-nodejs">the previous trial</a>. Here are some additional work we need to do:</p>
<h2 id="heading-database">Database</h2>
<p>As we know, today we will use Pinecone. Create an account at pinecone.io first.</p>
<p>And then at the console, create an Index. Index is like a table in a relational DB.</p>
<pre><code class="lang-javascript">Index Name: whateveruwant (i named it<span class="hljs-string">"article"</span>)
<span class="hljs-attr">Dimensions</span>: <span class="hljs-number">1536</span>
<span class="hljs-attr">Metric</span>: cosine
Pod Type: S1/P1
</code></pre>
<p>We need to specify the <code>Dimensions</code> to 1536 because the model <code>text-embedding-ada-002</code> we used from OpenAI to create embedding returned 1536 vector dimension.</p>
<h2 id="heading-library">Library</h2>
<p>We need the Pinecone NodeJs library:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"@pinecone-database/pinecone"</span>
</code></pre>
<pre><code class="lang-bash">npm install @pinecone-database/pinecone
</code></pre>
<h2 id="heading-env">ENV</h2>
<p>We need the Pinecone API key and env</p>
<pre><code class="lang-javascript">PINECONE_API_KEY
PINECONE_ENV
</code></pre>
<p>Get them in the Pinecone console, after creating Index.</p>
<h2 id="heading-code">Code</h2>
<p>Our Project structure, with the previous code, will look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">node_modules</span>
<span class="hljs-string">.env</span>
<span class="hljs-string">article.txt</span>
<span class="hljs-string">embed.js</span>
<span class="hljs-string">openAi.js</span>
<span class="hljs-string">package.json</span>
<span class="hljs-string">server.js</span>
<span class="hljs-string">supabase.js</span>
<span class="hljs-string">pinecone.js(new)</span>
</code></pre>
<p>Init pinecone, and create upsert function. Based on <a target="_blank" href="https://docs.pinecone.io/docs/node-client">this documentation</a></p>
<pre><code class="lang-javascript"><span class="hljs-comment">//pinecone.js</span>

<span class="hljs-comment">//init</span>
<span class="hljs-keyword">const</span> { PineconeClient } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@pinecone-database/pinecone"</span>);
<span class="hljs-keyword">const</span> uuid = <span class="hljs-built_in">require</span>(<span class="hljs-string">'uuid'</span>).v4; 

<span class="hljs-keyword">const</span> pinecone = <span class="hljs-keyword">new</span> PineconeClient();

pinecone.init({
  <span class="hljs-attr">environment</span>: process.env.PINECONE_ENV,
  <span class="hljs-attr">apiKey</span>: process.env.PINECONE_API_KEY,
});

<span class="hljs-comment">//upsert function</span>
<span class="hljs-keyword">const</span> upsert = <span class="hljs-keyword">async</span> (data) =&gt; {
    <span class="hljs-keyword">const</span> index = pinecone.Index(<span class="hljs-string">'article'</span>);
    <span class="hljs-keyword">const</span> { content, content_tokens, embedding } = data;

    <span class="hljs-keyword">const</span> upsertRequest = {
        <span class="hljs-attr">vectors</span>: [
            {
                <span class="hljs-attr">id</span>: uuid(),
                <span class="hljs-attr">values</span>: embedding,
                <span class="hljs-attr">metadata</span>: {
                    content,
                    content_tokens
                }
            }
        ]
    }
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> upsertResponse = <span class="hljs-keyword">await</span> index.upsert({ upsertRequest });
        <span class="hljs-keyword">return</span> upsertResponse;
    }<span class="hljs-keyword">catch</span>(err) {
        <span class="hljs-keyword">return</span> err
    }

};

<span class="hljs-built_in">module</span>.exports = {
    upsert,
}
</code></pre>
<p>From <code>upsertRequest</code>, use <code>metadata</code> to store value others than the embedding to the database. From this project, we store <code>content</code> and <code>content_tokens</code></p>
<p>Now, to the <code>embed.js</code> call it.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//embed.js</span>
<span class="hljs-keyword">const</span> pineConeHelper = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./pinecone'</span>);
....
(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> article = <span class="hljs-keyword">await</span> fs.readFileSync(<span class="hljs-string">'article.txt'</span>, { <span class="hljs-attr">encoding</span>: <span class="hljs-string">'utf8'</span> });
    <span class="hljs-keyword">const</span> chunkedArticles = <span class="hljs-keyword">await</span> chunkArticle(article);

    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span> ; i &lt; chunkedArticles.length ; i++) {
        <span class="hljs-keyword">const</span> embedding = <span class="hljs-keyword">await</span> openAiHelper.createEmbedding(chunkedArticles[i].content);
        <span class="hljs-keyword">await</span> pineConeHelper.upsert({ 
            <span class="hljs-attr">content</span>: chunkedArticles[i].content,
            <span class="hljs-attr">content_tokens</span>: chunkedArticles[i].content_tokens,
            embedding
        });

        <span class="hljs-comment">//temporary disabling supabase from prev trial</span>

        <span class="hljs-comment">// const { data,error } = await supabaseHelper</span>
        <span class="hljs-comment">//     .from('semantic_search_poc')</span>
        <span class="hljs-comment">//     .insert({</span>
        <span class="hljs-comment">//         content: chunkedArticles[i].content,</span>
        <span class="hljs-comment">//         content_tokens: chunkedArticles[i].content_tokens,</span>
        <span class="hljs-comment">//         embedding</span>
        <span class="hljs-comment">// })</span>

        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {}, <span class="hljs-number">500</span>)
    }
})()
</code></pre>
<p>Run:</p>
<pre><code class="lang-bash">node embed.js
</code></pre>
<p>If this success, go to the Pinecone console, and you will see in the Index info, there will be data stored.</p>
<p>Now for the query, create a query function.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//pinecone.js</span>
...

const query = <span class="hljs-keyword">async</span> (embed) =&gt; {
    <span class="hljs-keyword">const</span> index = pinecone.Index(<span class="hljs-string">'article'</span>);
    <span class="hljs-keyword">const</span> queryRequest = {
        <span class="hljs-attr">vector</span>: embed,
        <span class="hljs-attr">topK</span>: <span class="hljs-number">10</span>,
        <span class="hljs-attr">includeValues</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">includeMetadata</span>: <span class="hljs-literal">true</span>
    }
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> index.query({ queryRequest })
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">data</span>: response }
    }<span class="hljs-keyword">catch</span>(err) {
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">error</span>: err }
    }

}

<span class="hljs-built_in">module</span>.exports = {
    upsert,
    query
}
</code></pre>
<p>The <code>vector</code> field will search the embedding with the top 10 values. We don't want to include the vector values and want the metadata only, so we set <code>includeMetadata=true</code></p>
<p>Now call it on <code>server.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> pineconeHelper = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./pinecone'</span>);
...
app.get(<span class="hljs-string">'/'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> { q } = req.query;

    <span class="hljs-keyword">const</span> embedding = <span class="hljs-keyword">await</span> openAiHelper.createEmbedding(q);

    <span class="hljs-comment">//temporary disabling supabase from prev trial</span>

    <span class="hljs-comment">// const { data, error } = await supabaseHelper.rpc('semantic_search', {</span>
    <span class="hljs-comment">//     query_embedding: embedding,</span>
    <span class="hljs-comment">//     similiarity_threshold: 0.5,</span>
    <span class="hljs-comment">//     match_count: 5</span>
    <span class="hljs-comment">// })</span>

    <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> pineconeHelper.query(embedding);

    <span class="hljs-keyword">if</span>(error) {
        res.status(<span class="hljs-number">404</span>).send({ <span class="hljs-attr">message</span>: <span class="hljs-string">`<span class="hljs-subst">${q}</span> doesn't match any search`</span> });
    } <span class="hljs-keyword">else</span> {
        res.status(<span class="hljs-number">200</span>).send({...data})
    }
})
</code></pre>
<p>Run the server, and try to hit <code>http://localhost:9000/q=ask something</code></p>
<pre><code class="lang-javascript">node server.js
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679588870733/75d75d40-792d-4695-ba51-3ecb5af30421.png" alt class="image--center mx-auto" /></p>
<p>It running smoothly!</p>
<h1 id="heading-resources">Resources</h1>
<p>GitHub for this project (branch "pinecone"): <a target="_blank" href="https://github.com/fandyaditya/semantic-search-poc/tree/pinecone">https://github.com/fandyaditya/semantic-search-poc/tree/pinecone</a></p>
]]></content:encoded></item><item><title><![CDATA[Semantic Search using OpenAI Embedding and Postgres Vector DB in NodeJS]]></title><description><![CDATA[This project is about implementing semantic search using OpenAI embedding and Postgres vector database in NodeJS. Semantic search is a search technique that uses natural language processing to understand the meaning of the query and returns results t...]]></description><link>https://dev.fandyaditya.com/semantic-search-using-openai-embedding-and-postgres-vector-db-in-nodejs</link><guid isPermaLink="true">https://dev.fandyaditya.com/semantic-search-using-openai-embedding-and-postgres-vector-db-in-nodejs</guid><category><![CDATA[openai]]></category><category><![CDATA[semantic search]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Tue, 21 Mar 2023 08:20:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679386565963/9041f1a2-dfab-42b6-803d-0709215bc56e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This project is about implementing semantic search using OpenAI embedding and Postgres vector database in NodeJS. Semantic search is a search technique that uses natural language processing to understand the meaning of the query and returns results that are semantically related to the query. OpenAI embedding is an API that will convert text into a numerical vector representation that can be used for semantic search. Combining it with the Postgres vector database we can use this to make a semantic search of any text or article we want.</p>
<p>For the example text, I choose <a target="_blank" href="https://jamesclear.com/why-facts-dont-change-minds">https://jamesclear.com/why-facts-dont-change-minds</a> for the article.</p>
<h1 id="heading-diagram">Diagram</h1>
<p>Here is the diagram for the API that we want to create. It is split into two functions.</p>
<ol>
<li>Create and store the embedding based on the article text</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679386644638/bf2196bc-eeca-4965-b15d-c774ad1f2cca.jpeg" alt class="image--center mx-auto" /></p>
<ol>
<li><p>Search the queried text</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679386660035/7bc0af46-4ead-4a83-b885-1b8cb43836e8.jpeg" alt class="image--center mx-auto" /></p>
</li>
</ol>
<h1 id="heading-prerequisite">Prerequisite</h1>
<h2 id="heading-database">Database</h2>
<p>For the Postgres database, we will be using <a target="_blank" href="https://supabase.com/">Supabase</a>. Supabase has a free tier, and we can create and use it instantly.</p>
<p>Let's install the extension for Postgres, open the Supabase SQL editor, and run:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> extension pgvector;
</code></pre>
<p>Notes: If this extension needs another extension to install, install that also.</p>
<h2 id="heading-library">Library</h2>
<p>For this project, we use some of the node-js libraries:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"@supabase/supabase-js"</span> <span class="hljs-comment">// supabase node-js client</span>
<span class="hljs-string">"dotenv"</span><span class="hljs-comment">// to read .env file</span>
<span class="hljs-string">"express"</span> <span class="hljs-comment">// we use express for rest api framework</span>
<span class="hljs-string">"gpt-3-encoder"</span> <span class="hljs-comment">// we need this for chunk the text</span>
<span class="hljs-string">"openai"</span> <span class="hljs-comment">// openai node-js client</span>
</code></pre>
<pre><code class="lang-javascript">npm install @supabase/supabase-js dotenv express gpt<span class="hljs-number">-3</span>-encoder openai
</code></pre>
<h2 id="heading-env">ENV</h2>
<p>For the environment variable, we need these 3 for this project:</p>
<pre><code class="lang-javascript">SUPABASE_PROJECT_URL= 
SUPABASE_SECRET_KEY=
OPENAI_API_KEY=
</code></pre>
<p>Get <code>SUPABASE_PROJECT_URL</code> and <code>SUPABASE_SECRET_KEY (service_role secret)</code> from creating a database from Supabase first, and go to API Settings.</p>
<p>The <code>OPENAI_API_KEY</code> you can get it <a target="_blank" href="https://platform.openai.com/account/api-keys">here</a>.</p>
<h1 id="heading-code">Code</h1>
<p>Our Project structure will look like this:</p>
<pre><code class="lang-less"><span class="hljs-selector-tag">node_modules</span>
<span class="hljs-selector-class">.env</span>
<span class="hljs-selector-tag">article</span><span class="hljs-selector-class">.txt</span>
<span class="hljs-selector-tag">embed</span><span class="hljs-selector-class">.js</span>
<span class="hljs-selector-tag">openAi</span><span class="hljs-selector-class">.js</span>
<span class="hljs-selector-tag">package</span><span class="hljs-selector-class">.json</span>
<span class="hljs-selector-tag">server</span><span class="hljs-selector-class">.js</span>
<span class="hljs-selector-tag">supabase</span><span class="hljs-selector-class">.js</span>
</code></pre>
<p><code>article.txt</code> is the content of the article that we want to search for. So make sure to copy the content of the article there first.</p>
<p>Let's create and store the article chunk's embedding first:</p>
<h2 id="heading-1get-article-data">1.Get Article Data</h2>
<p>We need to get the text from the <code>article.txt</code></p>
<p>Before that, let's import the necessary library first.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679383167141/f840fa92-57ce-41a1-bf70-4a3d3b25e8de.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679382238545/4fce2eef-0607-444f-905e-dc48901e595e.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-2chunk-the-text">2.Chunk the text</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679382277010/bbb7765b-7bd8-424e-8adc-1be4650c9d12.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679382338943/2e278d8f-c77e-4be9-aec3-704823edff5f.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-what-do-we-do-here">What do we do here?</h3>
<p>OpenAI has a token system. 1 token means +- 1 word. What we want now is we chunk the article, and split it into limited <code>CHUNK_LIMIT</code> (200 tokens) each. We chunk it, so we can search for it later.</p>
<p>The function first checks if the length of the encoded article is greater than the <code>CHUNK_LIMIT</code>. If so, it splits the article into individual sentences and then concatenates them into chunks that are less than or equal to the <code>CHUNK_LIMIT</code>. It also ensures that a sentence is not split across different chunks by checking if the last character of a sentence is alphanumeric.</p>
<p>Each chunk is represented as an object with four properties: <code>content</code> (the text content of the chunk), <code>content_length</code> (the length of the text content), <code>content_tokens</code> (the number of tokens in the content), and <code>embedding</code>. <code>embedding</code> will be added later.</p>
<p>If the resulting chunks are smaller than the <code>CHUNK_MINIMAL</code> (100 Tokens ), they are merged with the previous chunk. Finally, the function returns an array of the resulting chunks.</p>
<h2 id="heading-3create-chunks-embedding">3.Create Chunk's Embedding</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679383478586/4962895d-0c74-4c98-86fd-a6865cca2da6.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { Configuration, OpenAIApi } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'openai'</span>);
<span class="hljs-keyword">const</span> configuration = <span class="hljs-keyword">new</span> Configuration({
    <span class="hljs-attr">apiKey</span>: process.env.OPENAI_API_KEY,
});
<span class="hljs-keyword">const</span> openAi = <span class="hljs-keyword">new</span> OpenAIApi(configuration);

<span class="hljs-keyword">const</span> createEmbedding = <span class="hljs-keyword">async</span> (input) =&gt; {
    <span class="hljs-keyword">const</span> embeddingRes = <span class="hljs-keyword">await</span> openAi.createEmbedding({
        <span class="hljs-attr">model</span>: <span class="hljs-string">'text-embedding-ada-002'</span>,
        <span class="hljs-attr">input</span>: input
    });

    <span class="hljs-keyword">const</span> [{embedding}] = embeddingRes.data.data;
    <span class="hljs-keyword">return</span> embedding
}

<span class="hljs-built_in">module</span>.exports = {
    createEmbedding
}
</code></pre>
<p>We need OpenAI's API Create Embedding to create the embedding of the chunked text.</p>
<h2 id="heading-4store-them-in-postgres-db">4.Store them in Postgres DB</h2>
<p>Before we store them, let's create the table first via the Supabase SQL editor:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> semantic_search_poc (
  <span class="hljs-keyword">id</span> bigserial primary <span class="hljs-keyword">key</span>,
  <span class="hljs-keyword">content</span> <span class="hljs-built_in">text</span>,
  content_tokens <span class="hljs-built_in">bigint</span>,
  embedding vector (<span class="hljs-number">1536</span>)
);
</code></pre>
<p>You can name it whatever you want. I named it <code>semantic_search_poc</code> so our query will be like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679384053645/4ebcbb14-b1fd-42ef-a9e0-1318a8dc0160.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679384098136/7403d6b0-9c9e-4b3b-89a7-5c8b91d2b1f0.png" alt class="image--center mx-auto" /></p>
<p>Now run the script</p>
<pre><code class="lang-bash">node embed.js
</code></pre>
<p>If the process is going smoothly, we will able to see the data in the Supabase table view, something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679384322839/473557f2-9de6-4d30-b471-c6e22620d7d1.png" alt class="image--center mx-auto" /></p>
<p>Done! You have success to build the most crucial part of this project!</p>
<p>Now to the next step, queried the data.</p>
<h2 id="heading-5get-query-from-user">5.Get Query from User</h2>
<p>We use <code>Express JS</code> to create Rest API Server to get the query from the user. Create simple Express js server code:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679384708688/ce759a09-0885-4bd8-8d36-0958ac43452b.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-6create-embedding-based-on-query">6.Create embedding based on query</h2>
<pre><code class="lang-javascript"><span class="hljs-comment">//server.js</span>
app.get(<span class="hljs-string">'/'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> { q } = req.query;
    <span class="hljs-keyword">const</span> embedding = <span class="hljs-keyword">await</span> openAiHelper.createEmbedding(q);
})
</code></pre>
<h2 id="heading-7search-it-in-postgres">7.Search it in Postgres</h2>
<p>We use the Postgres function for this search function. So let's head out to the Supabase function editor.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">or</span> <span class="hljs-keyword">replace</span> <span class="hljs-keyword">function</span> semantic_search (
  query_embedding vector(<span class="hljs-number">1536</span>),
  similiarity_threshold <span class="hljs-built_in">float</span>,
  match_count <span class="hljs-built_in">int</span>
)

<span class="hljs-keyword">returns</span> <span class="hljs-keyword">table</span> (
  <span class="hljs-keyword">id</span> <span class="hljs-built_in">bigint</span>,
  <span class="hljs-keyword">content</span> <span class="hljs-built_in">text</span>,
  content_tokens <span class="hljs-built_in">bigint</span>,
  similiarity <span class="hljs-built_in">float</span>
)
<span class="hljs-keyword">language</span> plpgsql
<span class="hljs-keyword">as</span> $$
<span class="hljs-keyword">begin</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">query</span>
  <span class="hljs-keyword">select</span>
    semantic_search_poc.id,
    semantic_search_poc.content,
    semantic_search_poc.content_tokens,
    <span class="hljs-number">1</span> - (semantic_search_poc.embedding &lt;=&gt; query_embedding) <span class="hljs-keyword">as</span> similiarity
  <span class="hljs-keyword">from</span> semantic_search_poc
  <span class="hljs-keyword">where</span> <span class="hljs-number">1</span> - (semantic_search_poc.embedding &lt;=&gt; query_embedding) &gt; similiarity_threshold
  <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> semantic_search_poc.embedding &lt;=&gt; query_embedding
  <span class="hljs-keyword">limit</span> match_count;
<span class="hljs-keyword">end</span>;
$$;
</code></pre>
<p>We create a Postgres script that creates a function called "semantic_search". The function takes in a query embedding, similarity threshold, and match count as inputs and returns a table with columns for ID, content, content tokens, and similarity.</p>
<p>The function uses the "semantic_search_poc" table to find rows where the similarity between the query embedding and the content embedding is greater than the similarity threshold. It then orders the results by similarity and limits the output to the specified match count.</p>
<p>The Postgres function is done, now call it on the code</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679385248833/9a75e48b-58a8-44ff-9688-777f88253c51.png" alt class="image--center mx-auto" /></p>
<p>Run the server:</p>
<pre><code class="lang-javascript">node server.js
</code></pre>
<p>Now try to hit GET <code>/</code> with <code>q</code> params something that you want to ask.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679385456731/010b25e6-0a25-4248-a740-6b3e7a5a38f6.png" alt class="image--center mx-auto" /></p>
<p>Voila! The API returns the related content ordered by the most similar one.</p>
<p>I hope this article helps to understand how we can create semantic-search apps easily today.</p>
<h1 id="heading-resources">Resources</h1>
<p>GitHub for this project: <a target="_blank" href="https://github.com/fandyaditya/semantic-search-poc">https://github.com/fandyaditya/semantic-search-poc</a></p>
<p>References: <a target="_blank" href="https://github.com/mckaywrigley/paul-graham-gpt">https://github.com/mckaywrigley/paul-graham-gpt</a></p>
]]></content:encoded></item><item><title><![CDATA[SOLID Design Principles (+ Example in JAVA)]]></title><description><![CDATA[SOLID Design Principle. The things that we usually asked or ask in an interview. It is an acronym for words:

S: Single Responsibility Principle
O: Open/Closed Principle
L: Liskov Substitution Principle
I: Interface Segregation Principle
D: Dependenc...]]></description><link>https://dev.fandyaditya.com/solid-design-principles-example-in-java</link><guid isPermaLink="true">https://dev.fandyaditya.com/solid-design-principles-example-in-java</guid><category><![CDATA[Java]]></category><category><![CDATA[design principles]]></category><category><![CDATA[SOLID principles]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Fri, 17 Mar 2023 17:16:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679073095377/7e204da0-b501-4e79-a0bb-e0e53c1ae694.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>SOLID Design Principle. The things that we usually asked or ask in an interview. It is an acronym for words:</p>
<blockquote>
<p>S: Single Responsibility Principle</p>
<p>O: Open/Closed Principle</p>
<p>L: Liskov Substitution Principle</p>
<p>I: Interface Segregation Principle</p>
<p>D: Dependency Inversion Principle</p>
</blockquote>
<p>Each character defines a specific rule on how we can achieve a good code. Especially on Object Oriented Programming (OOP).</p>
<p>Today, I found my old notes from a couple of years ago about SOLID, and I think it is still good and relevant now. Here they are:</p>
<h1 id="heading-s-single-responsibility-principle">[S] Single Responsibility Principle</h1>
<p>A class should have just one reason to change.</p>
<h2 id="heading-what-s">What [S]</h2>
<p>When building <em>classes</em>, it's important to consider the context of the class itself. Don't add methods that don't align with the function and responsibility of the class.</p>
<h2 id="heading-why-s">Why [S]</h2>
<p>Reducing the complexity of a program is crucial. The more changes made to a program, the harder it becomes to maintain. One way to simplify the process is by creating well-organized classes with functions that align with the class name.</p>
<h2 id="heading-how-s">How [S]</h2>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
    String name;
    String email;

    User(String name, String email) {
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.email = email;
    }

    <span class="hljs-function">String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.name;
    }

    <span class="hljs-function">String <span class="hljs-title">getEmail</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.email;
    }

    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">makePurchase</span><span class="hljs-params">()</span> </span>{
        .....
    }
}
</code></pre>
<p><code>makePurchase</code> method not relevant to <code>User</code> class. By referring to the Single Responsibility Principle, we can transform it like this.</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
    String name;
    String email;

    User(String name, String email) {
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.email = email;
    }

    <span class="hljs-function">String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.name;
    }

    <span class="hljs-function">String <span class="hljs-title">getEmail</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.email;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Purchase</span> </span>{
    User user;
    Cart cart;
    Ship ship;

    Purchase(User user, Cart cart, Ship ship) {
        <span class="hljs-keyword">this</span>.user = user;
        <span class="hljs-keyword">this</span>.cart = cart;
        <span class="hljs-keyword">this</span>.ship = ship;
    }
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">checkout</span><span class="hljs-params">()</span> </span>{
        ...
    }
}
</code></pre>
<p><code>class Purchase</code> is added to do the purchasing stuff</p>
<h1 id="heading-o-openclosed-principle">[O] Open/Closed Principle</h1>
<p>Classes should be open for extension but closed for modification</p>
<h2 id="heading-what-o">What [O]</h2>
<p>When implementing updates to a program, it's important to create new classes that are related to the updates, rather than directly modifying existing code.</p>
<h2 id="heading-why-o">Why [O]</h2>
<p>By creating new classes, we can avoid inadvertently breaking existing code that is already functioning as intended.</p>
<h2 id="heading-how-o">How [O]</h2>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Item</span> </span>{
    <span class="hljs-keyword">int</span> price;
    String name;
    String type;

    Item(<span class="hljs-keyword">int</span> price, String name, String type) {
        <span class="hljs-keyword">this</span>.price = price;
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.type = type;
    }

    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getTruePrice</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.type == “second”) {
            <span class="hljs-keyword">return</span> price * <span class="hljs-number">80</span>%
        }
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.type == “luxury”) {
            <span class="hljs-keyword">return</span> price * <span class="hljs-number">130</span>%
        }
    }
}
</code></pre>
<p>If we want to add a new type to the <code>getTruePrice()</code> method, we would typically need to modify the code within the Item class. However, by applying the Open/Closed principle, we can transform our approach to look like this:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IType</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getTruePrice</span><span class="hljs-params">(<span class="hljs-keyword">int</span> price)</span></span>;
}
</code></pre>
<p>We create <code>interface IType</code> that used for the implementation of the method <code>getTruePrice</code> for every <code>class Type</code> that we want to add.</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Second</span> <span class="hljs-title">implement</span> <span class="hljs-title">IType</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getTruePrice</span><span class="hljs-params">(price)</span> </span>{
        <span class="hljs-keyword">return</span> price * <span class="hljs-number">80</span>%;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Luxury</span> <span class="hljs-title">implement</span> <span class="hljs-title">IType</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getTruePrice</span><span class="hljs-params">(price)</span> </span>{
        <span class="hljs-keyword">return</span> price * <span class="hljs-number">130</span>%;
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Donation</span> <span class="hljs-title">implement</span> <span class="hljs-title">IType</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getTruePrice</span><span class="hljs-params">(price)</span> </span>{
        <span class="hljs-keyword">return</span> (price + <span class="hljs-number">10000</span>)*<span class="hljs-number">105</span>%;
    }
}
</code></pre>
<p>Now we modify the code for <code>class Item</code></p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Item</span> </span>{
    <span class="hljs-keyword">int</span> price;
    String name;
    IType type;

    Item(<span class="hljs-keyword">int</span> price, String name, IType type) {
        <span class="hljs-keyword">this</span>.price = price;
        <span class="hljs-keyword">this</span>.name = name;
        <span class="hljs-keyword">this</span>.type = type;
    }

    <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getTruePrice</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.type.getTruePrice(<span class="hljs-keyword">this</span>.price)
    }
}
</code></pre>
<p>We can create instances of <code>class Item</code> with the following way:</p>
<pre><code class="lang-java">Donation donationItem = <span class="hljs-keyword">new</span> Donation()
Item item = <span class="hljs-keyword">new</span> Item(<span class="hljs-number">500000</span>, <span class="hljs-string">'Guitar'</span>, donationItem )
</code></pre>
<p>When we need a new item type, we just need to create a new type class that implements <code>interface IType</code></p>
<h1 id="heading-l-liskov-substitution-principle">[L] Liskov Substitution Principle</h1>
<p>When extending a class, remember that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code.</p>
<h2 id="heading-what-l">What [L]</h2>
<p>An approach to creating sub-classes that is always compatible with the parent class" in English</p>
<h2 id="heading-why-l">Why [L]</h2>
<p>Preventing broken code when a subclass is used in code but in fact not compatible with their parent class</p>
<h2 id="heading-how-l">How [L]</h2>
<pre><code class="lang-java"><span class="hljs-comment">//Parent Class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Robot</span></span>{
    moving(Destination destination)
    firing(Object object)
}

<span class="hljs-comment">//Subclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HumanRobot</span> <span class="hljs-title">extend</span> <span class="hljs-title">Robot</span></span>{
    moving(Destination destination)
    firing(Object object) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception (“Human Robot is friendly robot, i made to help human job”)
    }
}
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WingsRobot</span> <span class="hljs-title">extend</span> <span class="hljs-title">Robot</span></span>{
    moving(Destination destination)
    flying(Destionation destination)
    firing(Object object)
}
</code></pre>
<p>When we create a new class by extending <code>class HumanRobot</code></p>
<pre><code class="lang-java">Class ShopKeeperRobot extend HumanRobot {
    <span class="hljs-meta">@override</span>
    moving(Destination destination)
    <span class="hljs-meta">@override</span>
    firing(Object object)
}

ShoperKeeperRobot skRobot = <span class="hljs-keyword">new</span> ShopKeeperRobot()
skRobot.firing(someObject)
</code></pre>
<p><code>skRobot.firing</code> seems like ok to call, but in fact, it makes the program closed. Because <code>method firing()</code> in their parent class <code>HumanRobot</code> throw an exception.</p>
<p>This is not in accordance with the Liskov Substitution principle. We need some change.</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Robot</span> </span>{
    moving()
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LandRobot</span> <span class="hljs-title">extend</span> <span class="hljs-title">Robot</span> </span>{
    moving()
    firing()
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WingsRobot</span> <span class="hljs-title">extend</span> <span class="hljs-title">LandRobot</span></span>{
    moving(Destination destination)
    firing(Object object)
    flying(Destionation destination)
}
</code></pre>
<p>With limiting method on <code>class Robot</code>, it's subclass <code>LandRobot</code> can have more flexibility for adding more methods that it truly needs, without making the parent method obsolete or disabled.</p>
<h1 id="heading-i-interface-segregation-principle">[I] Interface Segregation Principle</h1>
<p>Clients should not be forced to depend on methods they do not use</p>
<h2 id="heading-what-i">What [I]</h2>
<p>Make sure the class implements all methods from the interface they <code>implement</code>. If there is any situation where some method is not needed, separate that method and create a new interface</p>
<h2 id="heading-why-i">Why [I]</h2>
<p>If in the future we need to change, make it smaller or leaner for example delete some method from the interface, it may potentially become a broken code. Therefore, make sure that methods are truly being used by the implementing class to avoid having to overhaul the interface code.</p>
<h2 id="heading-how-i">How [I]</h2>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Phone</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span>
    <span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span>
    <span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span>
}</span>
</code></pre>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SamsungPhone</span> <span class="hljs-title">implement</span> <span class="hljs-title">Phone</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span></span>{...}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">XiaomiPhone</span> <span class="hljs-title">implement</span> <span class="hljs-title">Phone</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span></span>{...}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SmartfrenPhone</span> <span class="hljs-title">implement</span> <span class="hljs-title">Phone</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span></span>{...}
}
</code></pre>
<p>All <code>class</code> implementing <code>interface phone</code>. But on <code>SmartfrenPhone</code> because it only creates the old type of smartphone, it does not have an NFC feature. So <code>method accessNFC()</code> on <code>class SmartfrenPhone</code> cannot be used.</p>
<p>We can fix this by following Interface Segregation Principle:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Phone</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span>
    <span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span>
}

interface NewPhone </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span>
}</span>
</code></pre>
<p>We create a new interface, and separate it by the feature that smartphones can have</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SamsungPhone</span> <span class="hljs-title">implement</span> <span class="hljs-title">Phone</span>, <span class="hljs-title">NewPhone</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span></span>{...}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">XiaomiPhone</span> <span class="hljs-title">implement</span> <span class="hljs-title">Phone</span>, <span class="hljs-title">NewPhone</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessNFC</span><span class="hljs-params">()</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span></span>{...}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SmartfrenPhone</span> <span class="hljs-title">implement</span> <span class="hljs-title">Phone</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessCamera</span><span class="hljs-params">()</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">accessBluetooth</span><span class="hljs-params">()</span></span>{...}
}
</code></pre>
<h1 id="heading-d-dependency-inversion-principle">[D] Dependency Inversion Principle</h1>
<p>High-level classes shouldn’t depend on low-level classes. Both should depend on abstractions. Abstractions shouldn’t depend on details. Details should depend on abstractions.</p>
<h2 id="heading-what-d">What [D]</h2>
<p>When we build a module that contains a high-level (business logic application) that uses the low-level module, it should not depend directly on that low-level module. Create an abstraction between them.</p>
<h2 id="heading-why-d">Why [D]</h2>
<p>Make high-level modules easy to reuse without worrying about the side effects of changes in low-level modules.</p>
<h2 id="heading-how-d">How [D]</h2>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Mysql</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">update</span><span class="hljs-params">()</span></span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">delete</span><span class="hljs-params">()</span></span>{...}
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cart</span> </span>{
    Mysql mysql;
    List&lt;item&gt; items;
    Cart() {
        <span class="hljs-keyword">this</span>.mysql = <span class="hljs-keyword">new</span> Mysql()
    }

    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">save</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">this</span>.mysql.insert(items, <span class="hljs-string">'cart'</span>)
    }
}
</code></pre>
<p><code>class Cart</code> use <code>Mysl</code> to insert items into the database. But when in the future the team wants to change the database to MongoDB, the code will be broken. By following Dependency Inversion Principle, we can change this:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">Database</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(Object object, String table)</span>
    <span class="hljs-keyword">void</span> <span class="hljs-title">update</span><span class="hljs-params">(Object object, String table)</span>
    <span class="hljs-keyword">void</span> <span class="hljs-title">delete</span><span class="hljs-params">(Object object, String table)</span>
}</span>
</code></pre>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MySql</span> <span class="hljs-title">implement</span> <span class="hljs-title">Database</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">update</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">delete</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Mongo</span> <span class="hljs-title">implement</span> <span class="hljs-title">Database</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">update</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">delete</span><span class="hljs-params">(Object object, String table)</span> </span>{...}
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cart</span> </span>{
    Database database;
    List&lt;item&gt; items;
    Cart() {
        <span class="hljs-keyword">this</span>.database = <span class="hljs-keyword">new</span> Mongo()
    }
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">save</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">this</span>.database.insert(items, ‘cart’)
    }
}
</code></pre>
<p>Use <code>interface Database</code> as an abstraction for the database module.</p>
<p>That's all. Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Using Node Js, Chat GPT API, and Whisper API to Generate a Summary of a YouTube Video]]></title><description><![CDATA[Introduction
Everyone is talking about Chat GPT these days, and I'm sure you've already tried it and been amazed by its capability to understand and solve problems, and even perform certain tasks. As of early March, the Open AI Team has released a ne...]]></description><link>https://dev.fandyaditya.com/using-node-js-chat-gpt-api-and-whisper-api-to-generate-a-summary-of-a-youtube-video</link><guid isPermaLink="true">https://dev.fandyaditya.com/using-node-js-chat-gpt-api-and-whisper-api-to-generate-a-summary-of-a-youtube-video</guid><category><![CDATA[Node.js]]></category><category><![CDATA[openai]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[whisper]]></category><dc:creator><![CDATA[Fandy Aditya Wirana]]></dc:creator><pubDate>Thu, 16 Mar 2023 08:36:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1678955500728/6dfa874d-aaec-48d2-8ffc-d554e0ced66f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-introduction">Introduction</h1>
<p>Everyone is talking about Chat GPT these days, and I'm sure you've already tried it and been amazed by its capability to understand and solve problems, and even perform certain tasks. As of early March, the Open AI Team has released a new API for the Chat GPT model, as well as their speech-to-text model, Whisper. The best part? They're easy to use and super cheap. Just think of all the possibilities and fun things we can create with these!</p>
<p>For today Let's make a simple program to summarize a YouTube video using these two APIs in Node.Js.</p>
<p>Let's start with the diagram:</p>
<h1 id="heading-diagram">Diagram</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678955214616/b9e58941-ff39-4d3c-babe-5c5d399c664c.jpeg" alt="Diagram Simple" class="image--center mx-auto" /></p>
<p>The diagram is pretty straightforward and simple.</p>
<h1 id="heading-code">Code</h1>
<p>Initiate the Node.js project and install this library.</p>
<pre><code class="lang-bash">npm install openai
npm install ytdl-core
npm install dotenv
</code></pre>
<ol>
<li><p><code>openai</code> is Node js client library for OpenAi APIS</p>
</li>
<li><p><code>ytdl-core</code> is a library for downloading the youtube video</p>
</li>
<li><p><code>dotenv</code> for read .env file</p>
</li>
</ol>
<p>Our project structure will be like this</p>
<pre><code class="lang-bash">package.json
package-lock.json
index.js
summary.js
.env
node_modules
</code></pre>
<p>First, before we jump into coding, get your API key from <a target="_blank" href="https://platform.openai.com/account/api-keys">Open AI API Keys Page</a></p>
<p>Store it in .env</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//.env</span>
OPENAI_API_KEY=your_key
</code></pre>
<p>First, create the step that we want to code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//index js</span>
<span class="hljs-comment">/*
* 1. Import all necessary library
* 2. Initiate OpenAI Client
* 3. Download the youtube video
* 4. When the download done, transcribe it
* 5. When transcribing done, now summarize it
* 6. Store the summarize text to .txt file
*/</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">//index js</span>

<span class="hljs-comment">//1. Import all necessary library</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-keyword">const</span> { Configuration, OpenAIApi } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"openai"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> ytdl = <span class="hljs-built_in">require</span>(<span class="hljs-string">'ytdl-core'</span>);
<span class="hljs-keyword">const</span> summary = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./summary'</span>);

<span class="hljs-comment">//2. Initiate OpenAI Client</span>
<span class="hljs-keyword">const</span> main = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> configuration = <span class="hljs-keyword">new</span> Configuration({
      <span class="hljs-attr">apiKey</span>: process.env.OPENAI_API_KEY,
    });
    <span class="hljs-keyword">const</span> openai = <span class="hljs-keyword">new</span> OpenAIApi(configuration);
    <span class="hljs-comment">// 3. Download the youtube video</span>
    <span class="hljs-keyword">const</span> videoStream = fs.createWriteStream(<span class="hljs-string">'video.mp4'</span>);
    ytdl(link, { <span class="hljs-attr">quality</span>: <span class="hljs-string">'lowest'</span>}).pipe(videoStream);
}

<span class="hljs-comment">/* 4. When the download done, transcribe it
* 5. When transcribing done, now summarize it
* 6. Store the summarize text to .txt file
*/</span>
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">//index js</span>

<span class="hljs-comment">//1. Import all necessary library</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-keyword">const</span> { Configuration, OpenAIApi } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"openai"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> ytdl = <span class="hljs-built_in">require</span>(<span class="hljs-string">'ytdl-core'</span>);
<span class="hljs-keyword">const</span> summary = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./summary'</span>);

<span class="hljs-comment">//2. Initiate OpenAI Client</span>
<span class="hljs-keyword">const</span> main = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> configuration = <span class="hljs-keyword">new</span> Configuration({
      <span class="hljs-attr">apiKey</span>: process.env.OPENAI_API_KEY,
    });
    <span class="hljs-keyword">const</span> openai = <span class="hljs-keyword">new</span> OpenAIApi(configuration);
    <span class="hljs-comment">// 3. Download the youtube video</span>
    <span class="hljs-keyword">const</span> videoStream = fs.createWriteStream(<span class="hljs-string">'video.mp4'</span>);
    ytdl(link, { <span class="hljs-attr">quality</span>: <span class="hljs-string">'lowest'</span>}).pipe(videoStream);

    <span class="hljs-comment">//4. When the download done, transcribe it</span>
    videoStream.on(<span class="hljs-string">'finish'</span>, <span class="hljs-keyword">async</span> () =&gt; {
            <span class="hljs-keyword">const</span> transcribe = <span class="hljs-keyword">await</span> openai.createTranscription(fs.createReadStream(<span class="hljs-string">'./video.mp4'</span>), <span class="hljs-string">'whisper-1'</span>);
            <span class="hljs-comment">//5. When transcribing done, summarize it</span>
            summary.main(transcribe.data.text)
    })
}

<span class="hljs-comment">/* 
* 
* 6. Store the summarize text to .txt file
*/</span>
</code></pre>
<p>Now let us code the summary function in <code>summary.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-comment">//summary.js</span>
<span class="hljs-keyword">const</span> { Configuration, OpenAIApi } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"openai"</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);

<span class="hljs-keyword">const</span> main = <span class="hljs-keyword">async</span> (text) =&gt; {
    <span class="hljs-keyword">const</span> configuration = <span class="hljs-keyword">new</span> Configuration({
        <span class="hljs-attr">apiKey</span>: process.env.OPENAI_API_KEY,
    });
    <span class="hljs-keyword">const</span> openai = <span class="hljs-keyword">new</span> OpenAIApi(configuration);
    <span class="hljs-keyword">const</span> summaryStream = fs.createWriteStream(<span class="hljs-string">'summary.txt'</span>);

    <span class="hljs-keyword">const</span> prompt = <span class="hljs-string">`Please summary this text: <span class="hljs-subst">${text}</span>`</span>

    <span class="hljs-keyword">const</span> completion = <span class="hljs-keyword">await</span> openai.createChatCompletion({
        <span class="hljs-attr">model</span>: <span class="hljs-string">"gpt-3.5-turbo"</span>,
        <span class="hljs-attr">messages</span>: [{<span class="hljs-attr">role</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}]
    });
    <span class="hljs-comment">//6. Store the summarize text to .txt file</span>
    summaryStream.write(completion.data.choices[<span class="hljs-number">0</span>].message.content)
}

<span class="hljs-built_in">module</span>.exports = {
    main
}
</code></pre>
<p>And done! Our simple youtube summarizer is ready to use.</p>
<p>For example, I use this video <a target="_blank" href="https://youtu.be/KO3h_IoqN3g">link</a> as a parameter in<code>index.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-comment">//index.js</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();

<span class="hljs-keyword">const</span> { Configuration, OpenAIApi } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"openai"</span>);
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);
<span class="hljs-keyword">const</span> ytdl = <span class="hljs-built_in">require</span>(<span class="hljs-string">'ytdl-core'</span>);
<span class="hljs-keyword">const</span> summary = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./summary'</span>);


<span class="hljs-keyword">const</span> main = <span class="hljs-function">(<span class="hljs-params">link</span>) =&gt;</span> {
 <span class="hljs-comment">//...all the code above</span>
}

main(<span class="hljs-string">'https://youtu.be/KO3h_IoqN3g'</span>);
</code></pre>
<p>Then run in the terminal:</p>
<pre><code class="lang-javascript">node index.js
</code></pre>
<p>Voila! I got the summary of that video in my <code>summary.txt</code></p>
<blockquote>
<p>The text discusses how negative character traits can be turned into positive things if you can figure out how to do it. Using the example of Joan Crawford, who had a difficult childhood and suffered abuse, the author explains how she was able to turn her pain into great acting. Trauma survivors tend to be extremely sensitive to the emotions of others, which can be both positive and negative, but Crawford was able to use this sensitivity to read the minds of her directors and turn her negative experiences into something productive.</p>
</blockquote>
<h1 id="heading-limitation">Limitation</h1>
<p>Due to the beta release of Whisper API, the limit of files that can be transcribed is 25 MB. We can split the video/audio first before we transcribe it.</p>
<p>Thanks for reading! Please comment if there are any questions</p>
<blockquote>
<p>I would appreciate any feedback or suggestions that could help enhance the quality and readability of my writing. Thank you!</p>
</blockquote>
]]></content:encoded></item></channel></rss>